1use anyhow::anyhow;
16use itertools::Itertools;
17use risingwave_connector::source::fill_adaptive_split;
18
19use super::*;
20use crate::model::{ActorNewNoShuffle, FragmentReplaceUpstream, StreamJobFragments};
21
22#[derive(Debug, Clone)]
23pub struct SplitState {
24 pub split_assignment: SplitAssignment,
25}
26
27impl SourceManager {
28 pub fn migrate_splits_for_backfill_actors(
30 &self,
31 fragment_id: FragmentId,
32 upstream_source_fragment_id: FragmentId,
33 curr_actor_ids: &[ActorId],
34 fragment_actor_splits: &HashMap<FragmentId, HashMap<ActorId, Vec<SplitImpl>>>,
35 no_shuffle_upstream_actor_map: &HashMap<ActorId, HashMap<FragmentId, ActorId>>,
36 ) -> MetaResult<HashMap<ActorId, Vec<SplitImpl>>> {
37 let actors = no_shuffle_upstream_actor_map
39 .iter()
40 .filter(|(id, _)| curr_actor_ids.contains(id))
41 .map(|(id, upstream_fragment_actors)| {
42 (
43 *id,
44 *upstream_fragment_actors
45 .get(&upstream_source_fragment_id)
46 .unwrap(),
47 )
48 });
49 let upstream_assignment = fragment_actor_splits
50 .get(&upstream_source_fragment_id)
51 .unwrap();
52 tracing::info!(
53 %fragment_id,
54 %upstream_source_fragment_id,
55 ?upstream_assignment,
56 "migrate_splits_for_backfill_actors"
57 );
58 Ok(align_splits(
59 actors,
60 |upstream_actor_id| upstream_assignment.get(&upstream_actor_id).cloned(),
61 fragment_id,
62 upstream_source_fragment_id,
63 )?)
64 }
65
66 #[await_tree::instrument]
70 pub async fn discover_splits(
71 &self,
72 table_fragments: &StreamJobFragments,
73 ) -> MetaResult<SourceSplitAssignment> {
74 let source_fragments = table_fragments.stream_source_fragments();
75 if source_fragments.is_empty() {
77 return Ok(HashMap::new());
78 }
79
80 let core = self.core.lock().await;
81
82 let mut assigned = HashMap::new();
83
84 for (source_id, _fragments) in source_fragments {
85 let handle = core
86 .managed_sources
87 .get(&source_id)
88 .with_context(|| format!("could not find source {}", source_id))?;
89
90 if handle.splits.lock().await.splits.is_none() {
91 handle.force_tick().await?;
92 }
93
94 let Some(discovered) = handle.discovered_splits(source_id).await? else {
95 tracing::warn!(%source_id, "no splits detected (not ready)");
96 continue;
97 };
98 if let DiscoveredSplits::Fixed(ref splits) = discovered
99 && splits.is_empty()
100 {
101 tracing::warn!(%source_id, "no splits detected");
102 continue;
103 }
104 assigned.insert(source_id, discovered);
108 }
109
110 Ok(assigned)
111 }
112
113 pub fn resolve_fragment_to_actor_splits(
121 table_fragments: &StreamJobFragments,
122 source_assignment: &SourceSplitAssignment,
123 fragment_actor_ids: &HashMap<FragmentId, Vec<ActorId>>,
124 ) -> MetaResult<SplitAssignment> {
125 let source_fragments = table_fragments.stream_source_fragments();
126 let mut result: SplitAssignment = HashMap::new();
127 for (source_id, discovered) in source_assignment {
128 let fragment_ids = source_fragments.get(source_id).unwrap_or_else(|| {
129 panic!("source {} not found in stream job fragments", source_id)
130 });
131 for fragment_id in fragment_ids {
132 let actor_ids = fragment_actor_ids.get(fragment_id).unwrap_or_else(|| {
133 panic!("fragment {} not found in rendered actor IDs", fragment_id)
134 });
135 let actor_count = actor_ids.len();
136 match discovered {
137 DiscoveredSplits::Fixed(splits) => {
138 let empty_actor_splits: HashMap<ActorId, Vec<SplitImpl>> = actor_ids
139 .iter()
140 .map(|actor_id| (*actor_id, vec![]))
141 .collect();
142 if let Some(diff) = reassign_splits(
143 *fragment_id,
144 empty_actor_splits,
145 splits,
146 SplitDiffOptions::default(),
147 ) {
148 result.insert(*fragment_id, diff);
149 }
150 }
151 DiscoveredSplits::Adaptive(template) => {
152 let expanded = fill_adaptive_split(template, actor_count)?;
153 let expanded_splits: Vec<SplitImpl> = expanded.into_values().collect();
154 let mut actor_splits = HashMap::new();
155 for (i, actor_id) in actor_ids.iter().enumerate() {
156 actor_splits.insert(*actor_id, vec![expanded_splits[i].clone()]);
157 }
158 result.insert(*fragment_id, actor_splits);
159 }
160 }
161 }
162 }
163 Ok(result)
164 }
165
166 pub async fn discover_splits_for_replace_source(
174 &self,
175 table_fragments: &StreamJobFragments,
176 upstream_updates: &FragmentReplaceUpstream,
177 ) -> MetaResult<Option<SourceSplitAssignment>> {
178 if upstream_updates.is_empty() {
179 return Ok(Some(self.discover_splits(table_fragments).await?));
181 }
182 Ok(None)
185 }
186
187 pub fn resolve_replace_source_splits(
191 table_fragments: &StreamJobFragments,
192 upstream_updates: &FragmentReplaceUpstream,
193 actor_no_shuffle: &ActorNewNoShuffle,
199 get_upstream_actor_splits: impl Fn(FragmentId, ActorId) -> Option<Vec<SplitImpl>>,
200 ) -> MetaResult<SplitAssignment> {
201 tracing::debug!(?upstream_updates, "allocate_splits_for_replace_source");
202 assert!(!upstream_updates.is_empty());
203
204 let source_fragments = table_fragments.stream_source_fragments();
205 assert_eq!(
206 source_fragments.len(),
207 1,
208 "replace source job should only have one source"
209 );
210 let (_source_id, fragments) = source_fragments.into_iter().next().unwrap();
211 assert_eq!(
212 fragments.len(),
213 1,
214 "replace source job should only have one fragment"
215 );
216 let fragment_id = fragments.into_iter().next().unwrap();
217
218 debug_assert!(
219 upstream_updates.values().flatten().next().is_some()
220 && upstream_updates
221 .values()
222 .flatten()
223 .all(|(_, new_upstream_fragment_id)| {
224 *new_upstream_fragment_id == fragment_id
225 })
226 && upstream_updates
227 .values()
228 .flatten()
229 .map(|(upstream_fragment_id, _)| upstream_fragment_id)
230 .all_equal(),
231 "upstream update should only replace one fragment: {:?}",
232 upstream_updates
233 );
234 let prev_fragment_id = upstream_updates
235 .values()
236 .flatten()
237 .next()
238 .map(|(upstream_fragment_id, _)| *upstream_fragment_id)
239 .expect("non-empty");
240 let aligned_actors: HashMap<ActorId, ActorId> = actor_no_shuffle
253 .get(&fragment_id)
254 .map(HashMap::values)
255 .into_iter()
256 .flatten()
257 .flatten()
258 .map(|(upstream_actor_id, actor_id)| (*upstream_actor_id, *actor_id))
259 .collect();
260 let assignment = align_splits(
261 aligned_actors,
262 |actor_id| get_upstream_actor_splits(prev_fragment_id, actor_id),
263 fragment_id,
264 prev_fragment_id,
265 )?;
266 Ok(HashMap::from([(fragment_id, assignment)]))
267 }
268
269 pub fn resolve_backfill_splits(
273 table_fragments: &StreamJobFragments,
274 actor_no_shuffle: &ActorNewNoShuffle,
275 get_upstream_actor_splits: impl Fn(FragmentId, ActorId) -> Option<Vec<SplitImpl>>,
276 ) -> MetaResult<SplitAssignment> {
277 let source_backfill_fragments = table_fragments.source_backfill_fragments();
278
279 let mut assigned = HashMap::new();
280
281 for (_source_id, fragments) in source_backfill_fragments {
282 for (fragment_id, upstream_source_fragment_id) in fragments {
283 let upstream_actors: HashSet<ActorId> = actor_no_shuffle
285 .get(&upstream_source_fragment_id)
286 .and_then(|m| m.get(&fragment_id))
287 .ok_or_else(|| {
288 anyhow!(
289 "no upstream actors found from fragment {} to upstream source fragment {}",
290 fragment_id,
291 upstream_source_fragment_id
292 )
293 })?.keys().copied().collect();
294
295 let mut backfill_actors = vec![];
296 let Some(source_new_no_shuffle) = actor_no_shuffle
297 .get(&upstream_source_fragment_id)
298 .and_then(|source_upstream_actor_no_shuffle| {
299 source_upstream_actor_no_shuffle.get(&fragment_id)
300 })
301 else {
302 return Err(anyhow::anyhow!(
303 "source backfill fragment's upstream fragment should have one-on-one no_shuffle mapping, fragment_id: {fragment_id}, upstream_fragment_id: {upstream_source_fragment_id}, actor_no_shuffle: {actor_no_shuffle:?}",
304 fragment_id = fragment_id,
305 upstream_source_fragment_id = upstream_source_fragment_id,
306 actor_no_shuffle = actor_no_shuffle,
307 ).into());
308 };
309 for upstream_actor in &upstream_actors {
310 let Some(no_shuffle_backfill_actor) = source_new_no_shuffle.get(upstream_actor)
311 else {
312 return Err(anyhow::anyhow!(
313 "source backfill fragment's upstream fragment should have one-on-one no_shuffle mapping, fragment_id: {fragment_id}, upstream_fragment_id: {upstream_source_fragment_id}, upstream_actor: {upstream_actor}, source_new_no_shuffle: {source_new_no_shuffle:?}",
314 fragment_id = fragment_id,
315 upstream_source_fragment_id = upstream_source_fragment_id,
316 upstream_actor = upstream_actor,
317 source_new_no_shuffle = source_new_no_shuffle
318 ).into());
319 };
320 backfill_actors.push((*no_shuffle_backfill_actor, *upstream_actor));
321 }
322 assigned.insert(
323 fragment_id,
324 align_splits(
325 backfill_actors,
326 |actor_id| get_upstream_actor_splits(upstream_source_fragment_id, actor_id),
327 fragment_id,
328 upstream_source_fragment_id,
329 )?,
330 );
331 }
332 }
333
334 Ok(assigned)
335 }
336}
337
338impl SourceManagerCore {
339 pub async fn reassign_splits(&self) -> MetaResult<HashMap<DatabaseId, SplitState>> {
345 let mut split_assignment: SplitAssignment = HashMap::new();
346
347 'loop_source: for (source_id, handle) in &self.managed_sources {
348 let source_fragment_ids = match self.source_fragments.get(source_id) {
349 Some(fragment_ids) if !fragment_ids.is_empty() => fragment_ids,
350 _ => {
351 continue;
352 }
353 };
354 let backfill_fragment_ids = self.backfill_fragments.get(source_id);
355
356 'loop_fragment: for &fragment_id in source_fragment_ids {
357 let actors = match self
358 .metadata_manager
359 .get_running_actors_of_fragment(fragment_id)
360 {
361 Ok(actors) => {
362 if actors.is_empty() {
363 tracing::warn!("No actors found for fragment {}", fragment_id);
364 continue 'loop_fragment;
365 }
366 actors
367 }
368 Err(err) => {
369 tracing::warn!(error = %err.as_report(), "Failed to get the actor of the fragment, maybe the fragment doesn't exist anymore");
370 continue 'loop_fragment;
371 }
372 };
373
374 let Some(discovered) = handle.discovered_splits(*source_id).await? else {
375 continue 'loop_source;
377 };
378 let discovered_splits = match discovered {
379 DiscoveredSplits::Fixed(splits) => {
380 if splits.is_empty() {
381 continue 'loop_source;
382 }
383 splits
384 }
385 DiscoveredSplits::Adaptive(template) => {
386 fill_adaptive_split(&template, actors.len())?
387 }
388 };
389
390 let prev_actor_splits = {
391 let guard = self.env.shared_actor_infos().read_guard();
392
393 guard
394 .get_fragment(fragment_id)
395 .and_then(|info| {
396 info.actors
397 .iter()
398 .map(|(actor_id, actor_info)| {
399 (*actor_id, actor_info.splits.clone())
400 })
401 .collect::<HashMap<_, _>>()
402 .into()
403 })
404 .unwrap_or_default()
405 };
406
407 if let Some(new_assignment) = reassign_splits(
408 fragment_id,
409 prev_actor_splits,
410 &discovered_splits,
411 SplitDiffOptions {
412 enable_scale_in: handle.enable_drop_split,
413 enable_adaptive: handle.enable_adaptive_splits,
414 },
415 ) {
416 split_assignment.insert(fragment_id, new_assignment);
417 }
418 }
419
420 if let Some(backfill_fragment_ids) = backfill_fragment_ids {
421 for (fragment_id, upstream_fragment_id) in backfill_fragment_ids {
423 let Some(upstream_assignment): Option<&HashMap<ActorId, Vec<SplitImpl>>> =
424 split_assignment.get(upstream_fragment_id)
425 else {
426 continue;
428 };
429 let actors = match self
430 .metadata_manager
431 .get_running_actors_for_source_backfill(*fragment_id, *upstream_fragment_id)
432 .await
433 {
434 Ok(actors) => {
435 if actors.is_empty() {
436 tracing::warn!("No actors found for fragment {}", fragment_id);
437 continue;
438 }
439 actors
440 }
441 Err(err) => {
442 tracing::warn!(error = %err.as_report(),"Failed to get the actor of the fragment, maybe the fragment doesn't exist anymore");
443 continue;
444 }
445 };
446 split_assignment.insert(
447 *fragment_id,
448 align_splits(
449 actors,
450 |upstream_actor_id| {
451 upstream_assignment.get(&upstream_actor_id).cloned()
452 },
453 *fragment_id,
454 *upstream_fragment_id,
455 )?,
456 );
457 }
458 }
459 }
460
461 let assignments = self
462 .metadata_manager
463 .split_fragment_map_by_database(split_assignment)
464 .await?;
465
466 let mut result = HashMap::new();
467 for (database_id, assignment) in assignments {
468 result.insert(
469 database_id,
470 SplitState {
471 split_assignment: assignment,
472 },
473 );
474 }
475
476 Ok(result)
477 }
478}
479
480pub fn reassign_splits<I, T>(
509 fragment_id: FragmentId,
510 actor_splits: HashMap<I, Vec<T>>,
511 discovered_splits: &BTreeMap<SplitId, T>,
512 opts: SplitDiffOptions,
513) -> Option<HashMap<I, Vec<T>>>
514where
515 I: Ord + std::hash::Hash + Eq,
516 T: SplitMetaData + Clone,
517{
518 if actor_splits.is_empty() {
520 return None;
521 }
522
523 let prev_split_ids: HashSet<_> = actor_splits
524 .values()
525 .flat_map(|splits| splits.iter().map(SplitMetaData::id))
526 .collect();
527
528 tracing::trace!(%fragment_id, prev_split_ids = ?prev_split_ids, "previous splits");
529 tracing::trace!(%fragment_id, prev_split_ids = ?discovered_splits.keys(), "discovered splits");
530
531 let discovered_split_ids: HashSet<_> = discovered_splits.keys().cloned().collect();
532
533 let dropped_splits: HashSet<_> = prev_split_ids
534 .difference(&discovered_split_ids)
535 .cloned()
536 .collect();
537
538 if !dropped_splits.is_empty() {
539 if opts.enable_scale_in {
540 tracing::info!(%fragment_id, dropped_spltis = ?dropped_splits, "new dropped splits");
541 } else {
542 tracing::warn!(%fragment_id, dropped_spltis = ?dropped_splits, "split dropping happened, but it is not allowed");
543 }
544 }
545
546 let new_discovered_splits: BTreeSet<_> = discovered_split_ids
547 .into_iter()
548 .filter(|split_id| !prev_split_ids.contains(split_id))
549 .collect();
550
551 if opts.enable_scale_in || opts.enable_adaptive {
552 if dropped_splits.is_empty()
556 && new_discovered_splits.is_empty()
557 && !discovered_splits.is_empty()
558 {
559 return None;
560 }
561 } else {
562 if new_discovered_splits.is_empty() && !discovered_splits.is_empty() {
564 return None;
565 }
566 }
567
568 tracing::info!(%fragment_id, new_discovered_splits = ?new_discovered_splits, "new discovered splits");
569
570 let mut heap = BinaryHeap::with_capacity(actor_splits.len());
571
572 for (actor_id, mut splits) in actor_splits {
573 if opts.enable_scale_in || opts.enable_adaptive {
574 splits.retain(|split| !dropped_splits.contains(&split.id()));
575 }
576
577 heap.push(SplitsAssignment { actor_id, splits })
578 }
579
580 for split_id in new_discovered_splits {
581 let mut peek_ref = heap.peek_mut().unwrap();
590 peek_ref
591 .splits
592 .push(discovered_splits.get(&split_id).cloned().unwrap());
593 }
594
595 Some(
596 heap.into_iter()
597 .map(|SplitsAssignment { actor_id, splits }| (actor_id, splits))
598 .collect(),
599 )
600}
601
602pub fn align_splits(
617 aligned_actors: impl IntoIterator<Item = (ActorId, ActorId)>,
619 get_upstream_actor_splits: impl Fn(ActorId) -> Option<Vec<SplitImpl>>,
620 fragment_id: FragmentId,
621 upstream_source_fragment_id: FragmentId,
622) -> anyhow::Result<HashMap<ActorId, Vec<SplitImpl>>> {
623 aligned_actors
624 .into_iter()
625 .map(|(actor_id, upstream_actor_id)| {
626 let Some(splits) = get_upstream_actor_splits(upstream_actor_id) else {
627 return Err(anyhow::anyhow!("upstream assignment not found, fragment_id: {fragment_id}, upstream_fragment_id: {upstream_source_fragment_id}, actor_id: {actor_id}, upstream_actor_id: {upstream_actor_id:?}"));
628 };
629
630 Ok((
631 actor_id,
632 splits,
633 ))
634 })
635 .collect()
636}
637
638#[derive(Debug)]
640struct SplitsAssignment<I, T: SplitMetaData> {
641 actor_id: I,
642 splits: Vec<T>,
643}
644
645impl<I, T: SplitMetaData + Clone> Eq for SplitsAssignment<I, T> {}
646
647impl<I, T: SplitMetaData + Clone> PartialEq<Self> for SplitsAssignment<I, T> {
648 fn eq(&self, other: &Self) -> bool {
649 self.splits.len() == other.splits.len()
650 }
651}
652
653impl<I: Ord, T: SplitMetaData + Clone> PartialOrd<Self> for SplitsAssignment<I, T> {
654 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
655 Some(self.cmp(other))
656 }
657}
658
659impl<I: Ord, T: SplitMetaData + Clone> Ord for SplitsAssignment<I, T> {
660 fn cmp(&self, other: &Self) -> Ordering {
661 other
663 .splits
664 .len()
665 .cmp(&self.splits.len())
666 .then(self.actor_id.cmp(&other.actor_id))
667 }
668}
669
670#[derive(Debug)]
671pub struct SplitDiffOptions {
672 pub enable_scale_in: bool,
673
674 pub enable_adaptive: bool,
676}
677
678#[expect(clippy::derivable_impls)]
679impl Default for SplitDiffOptions {
680 fn default() -> Self {
681 SplitDiffOptions {
682 enable_scale_in: false,
683 enable_adaptive: false,
684 }
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use std::collections::{BTreeMap, HashMap, HashSet};
691
692 use risingwave_common::types::JsonbVal;
693 use risingwave_connector::error::ConnectorResult;
694 use risingwave_connector::source::{SplitId, SplitMetaData};
695 use serde::{Deserialize, Serialize};
696
697 use super::*;
698 use crate::model::{ActorId, FragmentId};
699
700 #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
701 struct TestSplit {
702 id: u32,
703 }
704
705 impl SplitMetaData for TestSplit {
706 fn id(&self) -> SplitId {
707 format!("{}", self.id).into()
708 }
709
710 fn encode_to_json(&self) -> JsonbVal {
711 serde_json::to_value(*self).unwrap().into()
712 }
713
714 fn restore_from_json(value: JsonbVal) -> ConnectorResult<Self> {
715 serde_json::from_value(value.take()).map_err(Into::into)
716 }
717
718 fn update_offset(&mut self, _last_read_offset: String) -> ConnectorResult<()> {
719 Ok(())
720 }
721 }
722
723 fn check_all_splits(
724 discovered_splits: &BTreeMap<SplitId, TestSplit>,
725 diff: &HashMap<ActorId, Vec<TestSplit>>,
726 ) {
727 let mut split_ids: HashSet<_> = discovered_splits.keys().cloned().collect();
728
729 for splits in diff.values() {
730 for split in splits {
731 assert!(split_ids.remove(&split.id()))
732 }
733 }
734
735 assert!(split_ids.is_empty());
736 }
737
738 #[test]
739 fn test_drop_splits() {
740 let mut actor_splits: HashMap<ActorId, _> = HashMap::new();
741 actor_splits.insert(0.into(), vec![TestSplit { id: 0 }, TestSplit { id: 1 }]);
742 actor_splits.insert(1.into(), vec![TestSplit { id: 2 }, TestSplit { id: 3 }]);
743 actor_splits.insert(2.into(), vec![TestSplit { id: 4 }, TestSplit { id: 5 }]);
744
745 let mut prev_split_to_actor = HashMap::new();
746 for (actor_id, splits) in &actor_splits {
747 for split in splits {
748 prev_split_to_actor.insert(split.id(), *actor_id);
749 }
750 }
751
752 let discovered_splits: BTreeMap<SplitId, TestSplit> = (1..5)
753 .map(|i| {
754 let split = TestSplit { id: i };
755 (split.id(), split)
756 })
757 .collect();
758
759 let opts = SplitDiffOptions {
760 enable_scale_in: true,
761 enable_adaptive: false,
762 };
763
764 let prev_split_ids: HashSet<_> = actor_splits
765 .values()
766 .flat_map(|splits| splits.iter().map(|split| split.id()))
767 .collect();
768
769 let diff = reassign_splits(
770 FragmentId::default(),
771 actor_splits,
772 &discovered_splits,
773 opts,
774 )
775 .unwrap();
776 check_all_splits(&discovered_splits, &diff);
777
778 let mut after_split_to_actor = HashMap::new();
779 for (actor_id, splits) in &diff {
780 for split in splits {
781 after_split_to_actor.insert(split.id(), *actor_id);
782 }
783 }
784
785 let discovered_split_ids: HashSet<_> = discovered_splits.keys().cloned().collect();
786
787 let retained_split_ids: HashSet<_> =
788 prev_split_ids.intersection(&discovered_split_ids).collect();
789
790 for retained_split_id in retained_split_ids {
791 assert_eq!(
792 prev_split_to_actor.get(retained_split_id),
793 after_split_to_actor.get(retained_split_id)
794 )
795 }
796 }
797
798 #[test]
799 fn test_drop_splits_to_empty() {
800 let mut actor_splits: HashMap<ActorId, _> = HashMap::new();
801 actor_splits.insert(0.into(), vec![TestSplit { id: 0 }]);
802
803 let discovered_splits: BTreeMap<SplitId, TestSplit> = BTreeMap::new();
804
805 let opts = SplitDiffOptions {
806 enable_scale_in: true,
807 enable_adaptive: false,
808 };
809
810 let diff = reassign_splits(
811 FragmentId::default(),
812 actor_splits,
813 &discovered_splits,
814 opts,
815 )
816 .unwrap();
817
818 assert!(!diff.is_empty())
819 }
820
821 #[test]
822 fn test_reassign_splits() {
823 let actor_splits: HashMap<u32, _> = HashMap::new();
824 let discovered_splits: BTreeMap<SplitId, TestSplit> = BTreeMap::new();
825 assert!(
826 reassign_splits(
827 FragmentId::default(),
828 actor_splits,
829 &discovered_splits,
830 Default::default()
831 )
832 .is_none()
833 );
834
835 let actor_splits = (0..3).map(|i| (i, vec![])).collect();
836 let discovered_splits: BTreeMap<SplitId, TestSplit> = BTreeMap::new();
837 let diff = reassign_splits(
838 FragmentId::default(),
839 actor_splits,
840 &discovered_splits,
841 Default::default(),
842 )
843 .unwrap();
844 assert_eq!(diff.len(), 3);
845 for splits in diff.values() {
846 assert!(splits.is_empty())
847 }
848
849 let actor_splits = (0..3).map(|i| (i.into(), vec![])).collect();
850 let discovered_splits: BTreeMap<SplitId, TestSplit> = (0..3)
851 .map(|i| {
852 let split = TestSplit { id: i };
853 (split.id(), split)
854 })
855 .collect();
856
857 let diff = reassign_splits(
858 FragmentId::default(),
859 actor_splits,
860 &discovered_splits,
861 Default::default(),
862 )
863 .unwrap();
864 assert_eq!(diff.len(), 3);
865 for splits in diff.values() {
866 assert_eq!(splits.len(), 1);
867 }
868
869 check_all_splits(&discovered_splits, &diff);
870
871 let actor_splits = (0..3)
872 .map(|i| (i.into(), vec![TestSplit { id: i }]))
873 .collect();
874 let discovered_splits: BTreeMap<SplitId, TestSplit> = (0..5)
875 .map(|i| {
876 let split = TestSplit { id: i };
877 (split.id(), split)
878 })
879 .collect();
880
881 let diff = reassign_splits(
882 FragmentId::default(),
883 actor_splits,
884 &discovered_splits,
885 Default::default(),
886 )
887 .unwrap();
888 assert_eq!(diff.len(), 3);
889 for splits in diff.values() {
890 let len = splits.len();
891 assert!(len == 1 || len == 2);
892 }
893
894 check_all_splits(&discovered_splits, &diff);
895
896 let mut actor_splits: HashMap<ActorId, Vec<TestSplit>> = (0..3)
897 .map(|i| (i.into(), vec![TestSplit { id: i }]))
898 .collect();
899 actor_splits.insert(3.into(), vec![]);
900 actor_splits.insert(4.into(), vec![]);
901
902 let discovered_splits: BTreeMap<SplitId, TestSplit> = (0..5)
903 .map(|i| {
904 let split = TestSplit { id: i };
905 (split.id(), split)
906 })
907 .collect();
908
909 let diff = reassign_splits(
910 FragmentId::default(),
911 actor_splits,
912 &discovered_splits,
913 Default::default(),
914 )
915 .unwrap();
916 assert_eq!(diff.len(), 5);
917 for splits in diff.values() {
918 assert_eq!(splits.len(), 1);
919 }
920
921 check_all_splits(&discovered_splits, &diff);
922 }
923}