1use std::collections::{BTreeMap, HashMap};
16
17use anyhow::Context;
18use enum_as_inner::EnumAsInner;
19use itertools::Itertools;
20use risingwave_common::bail;
21use risingwave_common::hash::{ActorAlignmentId, VnodeCountCompat};
22use risingwave_common::util::stream_graph_visitor::visit_fragment;
23use risingwave_connector::source::cdc::{CDC_BACKFILL_MAX_PARALLELISM, CdcScanOptions};
24use risingwave_meta_model::WorkerId;
25use risingwave_pb::common::WorkerNode;
26use risingwave_pb::meta::table_fragments::fragment::{
27 FragmentDistributionType, PbFragmentDistributionType,
28};
29use risingwave_pb::stream_plan::DispatcherType::{self, *};
30
31use crate::MetaResult;
32use crate::model::{ActorId, Fragment};
33use crate::stream::stream_graph::fragment::CompleteStreamFragmentGraph;
34use crate::stream::stream_graph::id::GlobalFragmentId as Id;
35
36type HashMappingId = usize;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40enum Req {
41 Singleton,
43 Hash(HashMappingId),
45 AnyVnodeCount(usize),
48}
49
50impl Req {
51 #[expect(non_upper_case_globals)]
53 const AnySingleton: Self = Self::AnyVnodeCount(1);
54
55 fn merge(a: Self, b: Self, mapping_len: impl Fn(HashMappingId) -> usize) -> MetaResult<Self> {
59 let merge = |a, b| match (a, b) {
61 (Self::AnySingleton, Self::Singleton) => Some(Self::Singleton),
62 (Self::AnyVnodeCount(count), Self::Hash(id)) if mapping_len(id) == count => {
63 Some(Self::Hash(id))
64 }
65 _ => None,
66 };
67
68 match merge(a, b).or_else(|| merge(b, a)) {
69 Some(req) => Ok(req),
70 None => bail!("incompatible requirements `{a:?}` and `{b:?}`"),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77enum Fact {
78 Edge {
80 from: Id,
81 to: Id,
82 dt: DispatcherType,
83 },
84 Req { id: Id, req: Req },
86}
87
88crepe::crepe! {
89 @input
90 struct Input(Fact);
91
92 struct Edge(Id, Id, DispatcherType);
93 struct ExternalReq(Id, Req);
94
95 @output
96 struct Requirement(Id, Req);
97
98 Edge(from, to, dt) <- Input(f), let Fact::Edge { from, to, dt } = f;
100 Requirement(id, req) <- Input(f), let Fact::Req { id, req } = f;
101
102 Requirement(y, Req::AnySingleton) <- Edge(_, y, Simple);
104 Requirement(x, d) <- Edge(x, y, NoShuffle), Requirement(y, d);
106 Requirement(y, d) <- Edge(x, y, NoShuffle), Requirement(x, d);
107}
108
109#[derive(Debug, Clone, EnumAsInner)]
111pub(super) enum Distribution {
112 Singleton,
114
115 Hash(usize),
117}
118
119impl Distribution {
120 pub fn vnode_count(&self) -> usize {
122 match self {
123 Distribution::Singleton => 1, Distribution::Hash(vnode_count) => *vnode_count,
125 }
126 }
127
128 pub fn from_fragment(fragment: &Fragment) -> Self {
130 match fragment.distribution_type {
131 FragmentDistributionType::Single => Distribution::Singleton,
132 FragmentDistributionType::Hash => Distribution::Hash(fragment.vnode_count()),
133 PbFragmentDistributionType::Unspecified => {
134 unreachable!()
135 }
136 }
137 }
138
139 pub fn to_distribution_type(&self) -> PbFragmentDistributionType {
141 match self {
142 Distribution::Singleton => PbFragmentDistributionType::Single,
143 Distribution::Hash(_) => PbFragmentDistributionType::Hash,
144 }
145 }
146}
147
148pub(super) struct Scheduler {
150 default_vnode_count: usize,
152}
153
154impl Scheduler {
155 pub fn new(expected_vnode_count: usize) -> MetaResult<Self> {
157 Ok(Self {
158 default_vnode_count: expected_vnode_count,
159 })
160 }
161
162 pub fn schedule(
165 &self,
166 graph: &CompleteStreamFragmentGraph,
167 ) -> MetaResult<HashMap<Id, Distribution>> {
168 let existing_distribution = graph.existing_distribution();
169
170 let all_hash_mappings = existing_distribution
172 .values()
173 .flat_map(|dist| dist.as_hash())
174 .cloned()
175 .unique()
176 .collect_vec();
177 let hash_mapping_id: HashMap<_, _> = all_hash_mappings
178 .iter()
179 .enumerate()
180 .map(|(i, m)| (*m, i))
181 .collect();
182
183 let mut facts = Vec::new();
184
185 for (&id, fragment) in graph.building_fragments() {
187 if fragment.requires_singleton {
188 facts.push(Fact::Req {
189 id,
190 req: Req::AnySingleton,
191 });
192 }
193 }
194 let mut force_parallelism_fragment_ids: HashMap<_, _> = HashMap::default();
195 for (&id, fragment) in graph.building_fragments() {
198 visit_fragment(fragment, |node| {
199 use risingwave_pb::stream_plan::stream_node::NodeBody;
200 let vnode_count = match node {
201 NodeBody::StreamScan(node) => {
202 if let Some(table) = &node.arrangement_table {
203 table.vnode_count()
204 } else if let Some(table) = &node.table_desc {
205 table.vnode_count()
206 } else {
207 return;
208 }
209 }
210 NodeBody::TemporalJoin(node) => node.get_table_desc().unwrap().vnode_count(),
211 NodeBody::BatchPlan(node) => node.get_table_desc().unwrap().vnode_count(),
212 NodeBody::Lookup(node) => node
213 .get_arrangement_table_info()
214 .unwrap()
215 .get_table_desc()
216 .unwrap()
217 .vnode_count(),
218 NodeBody::StreamCdcScan(node) => {
219 let Some(ref options) = node.options else {
220 return;
221 };
222 let options = CdcScanOptions::from_proto(options);
223 if options.is_parallelized_backfill() {
224 force_parallelism_fragment_ids
225 .insert(id, options.backfill_parallelism as usize);
226 CDC_BACKFILL_MAX_PARALLELISM as usize
227 } else {
228 return;
229 }
230 }
231 _ => return,
232 };
233 facts.push(Fact::Req {
234 id,
235 req: Req::AnyVnodeCount(vnode_count),
236 });
237 });
238 }
239 for (id, dist) in existing_distribution {
241 let req = match dist {
242 Distribution::Singleton => Req::Singleton,
243 Distribution::Hash(mapping) => Req::Hash(hash_mapping_id[&mapping]),
244 };
245 facts.push(Fact::Req { id, req });
246 }
247 for (from, to, edge) in graph.all_edges() {
249 facts.push(Fact::Edge {
250 from,
251 to,
252 dt: edge.dispatch_strategy.r#type(),
253 });
254 }
255
256 let mut crepe = Crepe::new();
258 crepe.extend(facts.into_iter().map(Input));
259 let (reqs,) = crepe.run();
260 let reqs = reqs
261 .into_iter()
262 .map(|Requirement(id, req)| (id, req))
263 .into_group_map();
264
265 let mut distributions = HashMap::new();
267 for &id in graph.building_fragments().keys() {
268 let dist = match reqs.get(&id) {
269 Some(reqs) => {
271 let req = (reqs.iter().copied())
272 .try_reduce(|a, b| Req::merge(a, b, |id| all_hash_mappings[id]))
273 .with_context(|| {
274 format!("cannot fulfill scheduling requirements for fragment {id:?}")
275 })?
276 .unwrap();
277
278 match req {
280 Req::Singleton => Distribution::Singleton,
281 Req::Hash(mapping) => Distribution::Hash(all_hash_mappings[mapping]),
282 Req::AnySingleton => Distribution::Singleton,
283 Req::AnyVnodeCount(vnode_count) => Distribution::Hash(vnode_count),
284 }
285 }
286 None => Distribution::Hash(self.default_vnode_count),
288 };
289
290 distributions.insert(id, dist);
291 }
292
293 tracing::debug!(?distributions, "schedule fragments");
294
295 Ok(distributions)
296 }
297}
298
299#[cfg_attr(test, derive(Default))]
301pub struct Locations {
302 pub actor_locations: BTreeMap<ActorId, ActorAlignmentId>,
304 pub worker_locations: HashMap<WorkerId, WorkerNode>,
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[derive(Debug)]
313 enum Result {
314 DefaultHash,
315 Required(Req),
316 }
317
318 impl Result {
319 #[expect(non_upper_case_globals)]
320 const DefaultSingleton: Self = Self::Required(Req::AnySingleton);
321 }
322
323 fn run_and_merge(
324 facts: impl IntoIterator<Item = Fact>,
325 mapping_len: impl Fn(HashMappingId) -> usize,
326 ) -> MetaResult<HashMap<Id, Req>> {
327 let mut crepe = Crepe::new();
328 crepe.extend(facts.into_iter().map(Input));
329 let (reqs,) = crepe.run();
330
331 let reqs = reqs
332 .into_iter()
333 .map(|Requirement(id, req)| (id, req))
334 .into_group_map();
335
336 let mut merged = HashMap::new();
337 for (id, reqs) in reqs {
338 let req = (reqs.iter().copied())
339 .try_reduce(|a, b| Req::merge(a, b, &mapping_len))
340 .with_context(|| {
341 format!("cannot fulfill scheduling requirements for fragment {id:?}")
342 })?
343 .unwrap();
344 merged.insert(id, req);
345 }
346
347 Ok(merged)
348 }
349
350 fn test_success(facts: impl IntoIterator<Item = Fact>, expected: HashMap<Id, Result>) {
351 test_success_with_mapping_len(facts, expected, |_| 0);
352 }
353
354 fn test_success_with_mapping_len(
355 facts: impl IntoIterator<Item = Fact>,
356 expected: HashMap<Id, Result>,
357 mapping_len: impl Fn(HashMappingId) -> usize,
358 ) {
359 let reqs = run_and_merge(facts, mapping_len).unwrap();
360
361 for (id, expected) in expected {
362 match (reqs.get(&id), expected) {
363 (None, Result::DefaultHash) => {}
364 (Some(actual), Result::Required(expected)) if *actual == expected => {}
365 (actual, expected) => panic!(
366 "unexpected result for fragment {id:?}\nactual: {actual:?}\nexpected: {expected:?}"
367 ),
368 }
369 }
370 }
371
372 fn test_failed(facts: impl IntoIterator<Item = Fact>) {
373 run_and_merge(facts, |_| 0).unwrap_err();
374 }
375
376 #[test]
378 fn test_single_fragment_hash() {
379 #[rustfmt::skip]
380 let facts = [];
381
382 let expected = maplit::hashmap! {
383 101.into() => Result::DefaultHash,
384 };
385
386 test_success(facts, expected);
387 }
388
389 #[test]
391 fn test_single_fragment_singleton() {
392 #[rustfmt::skip]
393 let facts = [
394 Fact::Req { id: 101.into(), req: Req::AnySingleton },
395 ];
396
397 let expected = maplit::hashmap! {
398 101.into() => Result::DefaultSingleton,
399 };
400
401 test_success(facts, expected);
402 }
403
404 #[test]
408 fn test_scheduling_mv_on_mv() {
409 #[rustfmt::skip]
410 let facts = [
411 Fact::Req { id: 1.into(), req: Req::Hash(1) },
412 Fact::Req { id: 2.into(), req: Req::Singleton },
413 Fact::Edge { from: 1.into(), to: 101.into(), dt: NoShuffle },
414 Fact::Edge { from: 2.into(), to: 102.into(), dt: NoShuffle },
415 Fact::Edge { from: 101.into(), to: 103.into(), dt: Hash },
416 Fact::Edge { from: 102.into(), to: 103.into(), dt: Hash },
417 Fact::Edge { from: 103.into(), to: 104.into(), dt: Simple },
418 ];
419
420 let expected = maplit::hashmap! {
421 101.into() => Result::Required(Req::Hash(1)),
422 102.into() => Result::Required(Req::Singleton),
423 103.into() => Result::DefaultHash,
424 104.into() => Result::DefaultSingleton,
425 };
426
427 test_success(facts, expected);
428 }
429
430 #[test]
434 fn test_delta_join() {
435 #[rustfmt::skip]
436 let facts = [
437 Fact::Req { id: 1.into(), req: Req::Hash(1) },
438 Fact::Req { id: 2.into(), req: Req::Hash(2) },
439 Fact::Edge { from: 1.into(), to: 101.into(), dt: NoShuffle },
440 Fact::Edge { from: 2.into(), to: 102.into(), dt: NoShuffle },
441 Fact::Edge { from: 101.into(), to: 103.into(), dt: NoShuffle },
442 Fact::Edge { from: 102.into(), to: 104.into(), dt: NoShuffle },
443 Fact::Edge { from: 101.into(), to: 104.into(), dt: Hash },
444 Fact::Edge { from: 102.into(), to: 103.into(), dt: Hash },
445 Fact::Edge { from: 103.into(), to: 105.into(), dt: Hash },
446 Fact::Edge { from: 104.into(), to: 105.into(), dt: Hash },
447 ];
448
449 let expected = maplit::hashmap! {
450 101.into() => Result::Required(Req::Hash(1)),
451 102.into() => Result::Required(Req::Hash(2)),
452 103.into() => Result::Required(Req::Hash(1)),
453 104.into() => Result::Required(Req::Hash(2)),
454 105.into() => Result::DefaultHash,
455 };
456
457 test_success(facts, expected);
458 }
459
460 #[test]
464 fn test_singleton_leaf() {
465 #[rustfmt::skip]
466 let facts = [
467 Fact::Req { id: 1.into(), req: Req::Hash(1) },
468 Fact::Edge { from: 1.into(), to: 101.into(), dt: NoShuffle },
469 Fact::Req { id: 102.into(), req: Req::AnySingleton }, Fact::Edge { from: 101.into(), to: 103.into(), dt: Hash },
471 Fact::Edge { from: 102.into(), to: 103.into(), dt: Broadcast },
472 ];
473
474 let expected = maplit::hashmap! {
475 101.into() => Result::Required(Req::Hash(1)),
476 102.into() => Result::DefaultSingleton,
477 103.into() => Result::DefaultHash,
478 };
479
480 test_success(facts, expected);
481 }
482
483 #[test]
487 fn test_upstream_hash_shard_failed() {
488 #[rustfmt::skip]
489 let facts = [
490 Fact::Req { id: 1.into(), req: Req::Hash(1) },
491 Fact::Req { id: 2.into(), req: Req::Hash(2) },
492 Fact::Edge { from: 1.into(), to: 101.into(), dt: NoShuffle },
493 Fact::Edge { from: 2.into(), to: 101.into(), dt: NoShuffle },
494 ];
495
496 test_failed(facts);
497 }
498
499 #[test]
501 fn test_arrangement_backfill_vnode_count() {
502 #[rustfmt::skip]
503 let facts = [
504 Fact::Req { id: 1.into(), req: Req::Hash(1) },
505 Fact::Req { id: 101.into(), req: Req::AnyVnodeCount(128) },
506 Fact::Edge { from: 1.into(), to: 101.into(), dt: Hash },
507 ];
508
509 let expected = maplit::hashmap! {
510 101.into() => Result::Required(Req::AnyVnodeCount(128)),
511 };
512
513 test_success(facts, expected);
514 }
515
516 #[test]
518 fn test_no_shuffle_backfill_vnode_count() {
519 #[rustfmt::skip]
520 let facts = [
521 Fact::Req { id: 1.into(), req: Req::Hash(1) },
522 Fact::Req { id: 101.into(), req: Req::AnyVnodeCount(128) },
523 Fact::Edge { from: 1.into(), to: 101.into(), dt: NoShuffle },
524 ];
525
526 let expected = maplit::hashmap! {
527 101.into() => Result::Required(Req::Hash(1)),
528 };
529
530 test_success_with_mapping_len(facts, expected, |id| {
531 assert_eq!(id, 1);
532 128
533 });
534 }
535
536 #[test]
538 fn test_no_shuffle_backfill_mismatched_vnode_count() {
539 #[rustfmt::skip]
540 let facts = [
541 Fact::Req { id: 1.into(), req: Req::Hash(1) },
542 Fact::Req { id: 101.into(), req: Req::AnyVnodeCount(128) },
543 Fact::Edge { from: 1.into(), to: 101.into(), dt: NoShuffle },
544 ];
545
546 test_failed(facts);
548 }
549
550 #[test]
552 fn test_backfill_singleton_vnode_count() {
553 #[rustfmt::skip]
554 let facts = [
555 Fact::Req { id: 1.into(), req: Req::Singleton },
556 Fact::Req { id: 101.into(), req: Req::AnySingleton },
557 Fact::Edge { from: 1.into(), to: 101.into(), dt: NoShuffle }, ];
559
560 let expected = maplit::hashmap! {
561 101.into() => Result::Required(Req::Singleton),
562 };
563
564 test_success(facts, expected);
565 }
566}