1use std::assert_matches;
16use std::collections::hash_map::Entry;
17use std::collections::{HashMap, HashSet};
18use std::mem::take;
19use std::sync::atomic::AtomicU32;
20
21use risingwave_common::bail;
22use risingwave_common::bitmap::Bitmap;
23use risingwave_common::catalog::TableId;
24use risingwave_common::hash::VnodeCountCompat;
25use risingwave_common::id::JobId;
26use risingwave_common::util::epoch::Epoch;
27use risingwave_meta_model::fragment::DistributionType;
28use risingwave_meta_model::{DispatcherType, WorkerId, streaming_job};
29use risingwave_pb::common::WorkerNode;
30use risingwave_pb::hummock::HummockVersionStats;
31use risingwave_pb::source::{ConnectorSplit, ConnectorSplits};
32use risingwave_pb::stream_plan::barrier_mutation::{Mutation, PbMutation};
33use risingwave_pb::stream_plan::throttle_mutation::ThrottleConfig;
34use risingwave_pb::stream_plan::update_mutation::PbDispatcherUpdate;
35use risingwave_pb::stream_plan::{
36 AddMutation, PbStartFragmentBackfillMutation, PbSubscriptionUpstreamInfo, PbUpdateMutation,
37 PbUpstreamSinkInfo, ThrottleMutation,
38};
39use tracing::warn;
40
41use crate::barrier::cdc_progress::CdcTableBackfillTracker;
42use crate::barrier::checkpoint::{
43 BatchRefreshJobCheckpointControl, BatchRefreshLogicalFragments, CreatingStreamingJobControl,
44 DatabaseCheckpointControl, IndependentCheckpointJobControl,
45};
46use crate::barrier::command::{CreateStreamingJobCommandInfo, PostCollectCommand, ReschedulePlan};
47use crate::barrier::context::CreateSnapshotBackfillJobCommandInfo;
48use crate::barrier::edge_builder::{EdgeBuilderFragmentInfo, FragmentEdgeBuilder};
49use crate::barrier::info::{
50 BarrierInfo, CreateStreamingJobStatus, InflightDatabaseInfo, InflightStreamingJobInfo,
51 SubscriberType,
52};
53use crate::barrier::notifier::Notifier;
54use crate::barrier::partial_graph::{PartialGraphBarrierInfo, PartialGraphManager};
55use crate::barrier::rpc::to_partial_graph_id;
56use crate::barrier::{BarrierKind, Command, CreateStreamingJobType, TracedEpoch};
57use crate::controller::fragment::{InflightActorInfo, InflightFragmentInfo};
58use crate::controller::scale::{
59 ComponentFragmentAligner, EnsembleActorTemplate, LoadedFragment, NoShuffleEnsemble,
60 build_no_shuffle_fragment_graph_edges, find_no_shuffle_graphs,
61};
62use crate::model::{
63 ActorId, ActorNewNoShuffle, FragmentDownstreamRelation, FragmentId, StreamActor, StreamContext,
64 StreamJobActorsToCreate, StreamJobFragmentsToCreate,
65};
66use crate::stream::cdc::parallel_cdc_table_backfill_fragment;
67use crate::stream::{
68 GlobalActorIdGen, ReplaceJobSplitPlan, SourceManager, SplitAssignment,
69 fill_snapshot_backfill_epoch,
70};
71use crate::{MetaError, MetaResult};
72
73pub(in crate::barrier) struct BarrierWorkerState {
75 in_flight_prev_epoch: TracedEpoch,
80
81 pending_non_checkpoint_barriers: Vec<u64>,
83
84 is_paused: bool,
86}
87
88impl BarrierWorkerState {
89 pub(super) fn new() -> Self {
90 Self {
91 in_flight_prev_epoch: TracedEpoch::new(Epoch::now()),
92 pending_non_checkpoint_barriers: vec![],
93 is_paused: false,
94 }
95 }
96
97 pub fn recovery(in_flight_prev_epoch: TracedEpoch, is_paused: bool) -> Self {
98 Self {
99 in_flight_prev_epoch,
100 pending_non_checkpoint_barriers: vec![],
101 is_paused,
102 }
103 }
104
105 pub fn is_paused(&self) -> bool {
106 self.is_paused
107 }
108
109 fn set_is_paused(&mut self, is_paused: bool) {
110 if self.is_paused != is_paused {
111 tracing::info!(
112 currently_paused = self.is_paused,
113 newly_paused = is_paused,
114 "update paused state"
115 );
116 self.is_paused = is_paused;
117 }
118 }
119
120 pub fn in_flight_prev_epoch(&self) -> &TracedEpoch {
121 &self.in_flight_prev_epoch
122 }
123
124 pub fn next_barrier_info(
126 &mut self,
127 is_checkpoint: bool,
128 curr_epoch: TracedEpoch,
129 ) -> BarrierInfo {
130 assert!(
131 self.in_flight_prev_epoch.value() < curr_epoch.value(),
132 "curr epoch regress. {} > {}",
133 self.in_flight_prev_epoch.value(),
134 curr_epoch.value()
135 );
136 let prev_epoch = self.in_flight_prev_epoch.clone();
137 self.in_flight_prev_epoch = curr_epoch.clone();
138 self.pending_non_checkpoint_barriers
139 .push(prev_epoch.value().0);
140 let kind = if is_checkpoint {
141 let epochs = take(&mut self.pending_non_checkpoint_barriers);
142 BarrierKind::Checkpoint(epochs)
143 } else {
144 BarrierKind::Barrier
145 };
146 BarrierInfo {
147 prev_epoch,
148 curr_epoch,
149 kind,
150 }
151 }
152}
153
154pub(super) struct ApplyCommandInfo {
155 pub jobs_to_wait: HashSet<JobId>,
156}
157
158type ApplyCommandResult = (
161 Option<Mutation>,
162 HashSet<TableId>,
163 Option<StreamJobActorsToCreate>,
164 HashMap<WorkerId, HashSet<ActorId>>,
165 PostCollectCommand,
166);
167
168pub(crate) struct RenderResult {
170 pub stream_actors: HashMap<FragmentId, Vec<StreamActor>>,
172 pub actor_location: HashMap<ActorId, WorkerId>,
174}
175
176pub(crate) fn resolve_no_shuffle_ensembles(
185 fragments: &StreamJobFragmentsToCreate,
186 upstream_fragment_downstreams: &FragmentDownstreamRelation,
187) -> MetaResult<Vec<NoShuffleEnsemble>> {
188 let mut new_no_shuffle: HashMap<_, HashSet<_>> = HashMap::new();
190
191 for (upstream_fid, relations) in &fragments.downstreams {
193 for rel in relations {
194 if rel.dispatcher_type == DispatcherType::NoShuffle {
195 new_no_shuffle
196 .entry(*upstream_fid)
197 .or_default()
198 .insert(rel.downstream_fragment_id);
199 }
200 }
201 }
202
203 for (upstream_fid, relations) in upstream_fragment_downstreams {
205 for rel in relations {
206 if rel.dispatcher_type == DispatcherType::NoShuffle {
207 new_no_shuffle
208 .entry(*upstream_fid)
209 .or_default()
210 .insert(rel.downstream_fragment_id);
211 }
212 }
213 }
214
215 let mut ensembles = if new_no_shuffle.is_empty() {
216 Vec::new()
217 } else {
218 let no_shuffle_edges: Vec<(FragmentId, FragmentId)> = new_no_shuffle
220 .iter()
221 .flat_map(|(upstream_fid, downstream_fids)| {
222 downstream_fids
223 .iter()
224 .map(move |downstream_fid| (*upstream_fid, *downstream_fid))
225 })
226 .collect();
227
228 let all_fragment_ids: Vec<FragmentId> = no_shuffle_edges
229 .iter()
230 .flat_map(|(u, d)| [*u, *d])
231 .collect::<HashSet<_>>()
232 .into_iter()
233 .collect();
234
235 let (fwd, bwd) = build_no_shuffle_fragment_graph_edges(no_shuffle_edges);
236 find_no_shuffle_graphs(&all_fragment_ids, &fwd, &bwd)?
237 };
238
239 let covered: HashSet<FragmentId> = ensembles
241 .iter()
242 .flat_map(|e| e.component_fragments())
243 .collect();
244 for fragment_id in fragments.inner.fragments.keys() {
245 if !covered.contains(fragment_id) {
246 ensembles.push(NoShuffleEnsemble::singleton(*fragment_id));
247 }
248 }
249
250 Ok(ensembles)
251}
252
253pub(super) fn render_actors(
264 fragments: &StreamJobFragmentsToCreate,
265 database_info: &InflightDatabaseInfo,
266 definition: &str,
267 ctx: &StreamContext,
268 streaming_job_model: &streaming_job::Model,
269 actor_id_counter: &AtomicU32,
270 worker_map: &HashMap<WorkerId, WorkerNode>,
271 ensembles: &[NoShuffleEnsemble],
272 database_resource_group: &str,
273) -> MetaResult<RenderResult> {
274 let mut actor_assignments: HashMap<FragmentId, HashMap<ActorId, (WorkerId, Option<Bitmap>)>> =
277 HashMap::new();
278
279 for ensemble in ensembles {
280 let existing_fragment_ids: Vec<FragmentId> = ensemble
285 .component_fragments()
286 .filter(|fragment_id| !fragments.inner.fragments.contains_key(fragment_id))
287 .collect();
288
289 let actor_template = if let Some(&first_existing) = existing_fragment_ids.first() {
290 let template = EnsembleActorTemplate::from_existing_inflight_fragment(
291 database_info.fragment(first_existing),
292 );
293
294 for &other_fragment_id in &existing_fragment_ids[1..] {
297 let other = EnsembleActorTemplate::from_existing_inflight_fragment(
298 database_info.fragment(other_fragment_id),
299 );
300 template.assert_aligned_with(&other, first_existing, other_fragment_id);
301 }
302
303 template
304 } else {
305 let first_component = ensemble
307 .component_fragments()
308 .next()
309 .expect("ensemble must have at least one component");
310 let fragment = &fragments.inner.fragments[&first_component];
311 let distribution_type: DistributionType = fragment.distribution_type.into();
312 let vnode_count = fragment.vnode_count();
313
314 for fragment_id in ensemble.component_fragments() {
316 let f = &fragments.inner.fragments[&fragment_id];
317 assert_eq!(
318 vnode_count,
319 f.vnode_count(),
320 "component fragments {} and {} in the same no-shuffle ensemble have \
321 different vnode counts: {} vs {}",
322 first_component,
323 fragment_id,
324 vnode_count,
325 f.vnode_count(),
326 );
327 }
328
329 EnsembleActorTemplate::render_new(
330 streaming_job_model,
331 worker_map,
332 None,
333 database_resource_group.to_owned(),
334 distribution_type,
335 vnode_count,
336 )?
337 };
338
339 for fragment_id in ensemble.component_fragments() {
341 if !fragments.inner.fragments.contains_key(&fragment_id) {
342 continue; }
344 let fragment = &fragments.inner.fragments[&fragment_id];
345 let distribution_type: DistributionType = fragment.distribution_type.into();
346 let aligner =
347 ComponentFragmentAligner::new_persistent(&actor_template, actor_id_counter);
348 let assignments = aligner.align_component_actor(distribution_type);
349 actor_assignments.insert(fragment_id, assignments);
350 }
351 }
352
353 let mut result_stream_actors: HashMap<FragmentId, Vec<StreamActor>> = HashMap::new();
355 let mut result_actor_location: HashMap<ActorId, WorkerId> = HashMap::new();
356
357 for (fragment_id, assignments) in &actor_assignments {
358 let mut actors = Vec::with_capacity(assignments.len());
359 for (&actor_id, (worker_id, vnode_bitmap)) in assignments {
360 result_actor_location.insert(actor_id, *worker_id);
361 actors.push(StreamActor {
362 actor_id,
363 fragment_id: *fragment_id,
364 vnode_bitmap: vnode_bitmap.clone(),
365 mview_definition: definition.to_owned(),
366 expr_context: Some(ctx.to_expr_context()),
367 config_override: ctx.config_override.clone(),
368 });
369 }
370 result_stream_actors.insert(*fragment_id, actors);
371 }
372
373 Ok(RenderResult {
374 stream_actors: result_stream_actors,
375 actor_location: result_actor_location,
376 })
377}
378impl DatabaseCheckpointControl {
379 fn collect_base_info(&self) -> (HashSet<TableId>, HashMap<WorkerId, HashSet<ActorId>>) {
381 let table_ids_to_commit = self.database_info.existing_table_ids().collect();
382 let node_actors =
383 InflightFragmentInfo::actor_ids_to_collect(self.database_info.fragment_infos());
384 (table_ids_to_commit, node_actors)
385 }
386
387 fn apply_simple_command(
391 &self,
392 mutation: Option<Mutation>,
393 command_name: &'static str,
394 ) -> ApplyCommandResult {
395 let (table_ids, node_actors) = self.collect_base_info();
396 (
397 mutation,
398 table_ids,
399 None,
400 node_actors,
401 PostCollectCommand::Command(command_name.to_owned()),
402 )
403 }
404
405 pub(super) fn apply_command(
408 &mut self,
409 command: Option<Command>,
410 notifiers: &mut Vec<Notifier>,
411 barrier_info: BarrierInfo,
412 partial_graph_manager: &mut PartialGraphManager,
413 hummock_version_stats: &HummockVersionStats,
414 worker_nodes: &HashMap<WorkerId, WorkerNode>,
415 ) -> MetaResult<ApplyCommandInfo> {
416 debug_assert!(
417 !matches!(
418 command,
419 Some(Command::RescheduleIntent {
420 reschedule_plan: None,
421 ..
422 })
423 ),
424 "reschedule intent must be resolved before apply"
425 );
426 if matches!(
427 command,
428 Some(Command::RescheduleIntent {
429 reschedule_plan: None,
430 ..
431 })
432 ) {
433 bail!("reschedule intent must be resolved before apply");
434 }
435
436 fn resolve_source_splits(
441 info: &CreateStreamingJobCommandInfo,
442 render_result: &RenderResult,
443 actor_no_shuffle: &ActorNewNoShuffle,
444 database_info: &InflightDatabaseInfo,
445 ) -> MetaResult<SplitAssignment> {
446 let fragment_actor_ids: HashMap<FragmentId, Vec<ActorId>> = render_result
447 .stream_actors
448 .iter()
449 .map(|(fragment_id, actors)| {
450 (
451 *fragment_id,
452 actors.iter().map(|a| a.actor_id).collect::<Vec<_>>(),
453 )
454 })
455 .collect();
456 let mut resolved = SourceManager::resolve_fragment_to_actor_splits(
457 &info.stream_job_fragments,
458 &info.init_split_assignment,
459 &fragment_actor_ids,
460 )?;
461 resolved.extend(SourceManager::resolve_backfill_splits(
462 &info.stream_job_fragments,
463 actor_no_shuffle,
464 |fragment_id, actor_id| {
465 database_info
466 .fragment(fragment_id)
467 .actors
468 .get(&actor_id)
469 .map(|info| info.splits.clone())
470 },
471 )?);
472 Ok(resolved)
473 }
474
475 let mut throttle_for_creating_jobs: Option<(
477 HashSet<JobId>,
478 HashMap<FragmentId, ThrottleConfig>,
479 )> = None;
480
481 let (
485 mutation,
486 mut table_ids_to_commit,
487 mut actors_to_create,
488 mut node_actors,
489 post_collect_command,
490 ) = match command {
491 None => self.apply_simple_command(None, "barrier"),
492 Some(Command::CreateStreamingJob {
493 mut info,
494 job_type:
495 CreateStreamingJobType::SnapshotBackfill {
496 mut snapshot_backfill_info,
497 since_epoch,
498 },
499 cross_db_snapshot_backfill_info,
500 }) => {
501 let ensembles = resolve_no_shuffle_ensembles(
502 &info.stream_job_fragments,
503 &info.upstream_fragment_downstreams,
504 )?;
505 let actors = render_actors(
506 &info.stream_job_fragments,
507 &self.database_info,
508 &info.definition,
509 &info.stream_job_fragments.inner.ctx,
510 &info.streaming_job_model,
511 partial_graph_manager
512 .control_stream_manager()
513 .env
514 .actor_id_generator(),
515 worker_nodes,
516 &ensembles,
517 &info.database_resource_group,
518 )?;
519 {
520 assert!(!self.state.is_paused());
521 let (snapshot_epoch, since_timestamp_upstream_log_epochs) =
522 if let Some(since_epoch) = &since_epoch {
523 let (snapshot_epoch, log_epochs) =
524 since_epoch.resolved.as_ref().ok_or_else(|| {
525 MetaError::from(anyhow::anyhow!(
526 "since_timestamp epoch has not been resolved for snapshot backfill"
527 ))
528 })?;
529 (
530 *snapshot_epoch,
531 Some((
532 log_epochs,
533 to_partial_graph_id(self.database_id, None),
534 barrier_info.prev_epoch(),
535 )),
536 )
537 } else {
538 (barrier_info.prev_epoch(), None)
539 };
540 for snapshot_backfill_epoch in snapshot_backfill_info
542 .upstream_mv_table_id_to_backfill_epoch
543 .values_mut()
544 {
545 assert_eq!(
546 snapshot_backfill_epoch.replace(snapshot_epoch),
547 None,
548 "must not set previously"
549 );
550 }
551 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
552 fill_snapshot_backfill_epoch(
553 &mut fragment.nodes,
554 Some(&snapshot_backfill_info),
555 &cross_db_snapshot_backfill_info,
556 )?;
557 }
558 let job_id = info.stream_job_fragments.stream_job_id();
559 let snapshot_backfill_upstream_tables = snapshot_backfill_info
560 .upstream_mv_table_id_to_backfill_epoch
561 .keys()
562 .cloned()
563 .collect();
564 let mut edges = self.database_info.build_edge(
566 Some((&info, true)),
567 None,
568 None,
569 partial_graph_manager.control_stream_manager(),
570 &actors.stream_actors,
571 &actors.actor_location,
572 );
573 let resolved_split_assignment = resolve_source_splits(
575 &info,
576 &actors,
577 edges.actor_new_no_shuffle(),
578 &self.database_info,
579 )?;
580
581 let Entry::Vacant(entry) =
582 self.independent_checkpoint_job_controls.entry(job_id)
583 else {
584 panic!("duplicated creating snapshot backfill job {job_id}");
585 };
586
587 let job = CreatingStreamingJobControl::new(
588 entry,
589 CreateSnapshotBackfillJobCommandInfo {
590 info: info.clone(),
591 snapshot_backfill_info: snapshot_backfill_info.clone(),
592 cross_db_snapshot_backfill_info,
593 resolved_split_assignment: resolved_split_assignment.clone(),
594 refresh_interval_sec: None,
595 },
596 take(notifiers),
597 snapshot_backfill_upstream_tables,
598 snapshot_epoch,
599 since_timestamp_upstream_log_epochs,
600 hummock_version_stats,
601 partial_graph_manager,
602 &mut edges,
603 &resolved_split_assignment,
604 &actors,
605 )?;
606
607 if let Some(fragment_infos) = job.fragment_infos() {
608 self.database_info.shared_actor_infos.upsert(
609 self.database_id,
610 fragment_infos.values().map(|f| (f, job_id)),
611 );
612 }
613
614 for upstream_mv_table_id in snapshot_backfill_info
615 .upstream_mv_table_id_to_backfill_epoch
616 .keys()
617 {
618 self.database_info.register_subscriber(
619 upstream_mv_table_id.as_job_id(),
620 info.streaming_job.id().as_subscriber_id(),
621 SubscriberType::SnapshotBackfill,
622 );
623 }
624
625 let mutation = Command::create_streaming_job_to_mutation(
626 &info,
627 &CreateStreamingJobType::SnapshotBackfill {
628 snapshot_backfill_info,
629 since_epoch,
630 },
631 [],
632 self.state.is_paused(),
633 &mut edges,
634 partial_graph_manager.control_stream_manager(),
635 None,
636 &resolved_split_assignment,
637 &actors.stream_actors,
638 &actors.actor_location,
639 )?;
640
641 let (table_ids, node_actors) = self.collect_base_info();
642 (
643 Some(mutation),
644 table_ids,
645 None,
646 node_actors,
647 PostCollectCommand::barrier(),
648 )
649 }
650 }
651 Some(Command::CreateStreamingJob {
652 mut info,
653 job_type: CreateStreamingJobType::BatchRefresh(mut batch_refresh_info),
654 cross_db_snapshot_backfill_info,
655 }) => {
656 {
657 if self.state.is_paused() {
658 bail!("cannot create batch refresh job while database barrier is paused");
659 }
660 let snapshot_epoch = barrier_info.prev_epoch();
661 let job_id = info.stream_job_fragments.stream_job_id();
662 let database_id = info.streaming_job.database_id();
663
664 let snapshot_backfill_info = &mut batch_refresh_info.snapshot_backfill_info;
666 for snapshot_backfill_epoch in snapshot_backfill_info
667 .upstream_mv_table_id_to_backfill_epoch
668 .values_mut()
669 {
670 assert_eq!(
671 snapshot_backfill_epoch.replace(snapshot_epoch),
672 None,
673 "must not set previously"
674 );
675 }
676 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
677 fill_snapshot_backfill_epoch(
678 &mut fragment.nodes,
679 Some(snapshot_backfill_info),
680 &cross_db_snapshot_backfill_info,
681 )?;
682 }
683 let snapshot_backfill_upstream_tables: HashSet<TableId> =
684 snapshot_backfill_info
685 .upstream_mv_table_id_to_backfill_epoch
686 .keys()
687 .cloned()
688 .collect();
689
690 let logical = BatchRefreshLogicalFragments {
692 fragments: info
693 .stream_job_fragments
694 .inner
695 .fragments
696 .iter()
697 .map(|(&fid, fragment)| {
698 (
699 fid,
700 LoadedFragment {
701 fragment_id: fid,
702 job_id,
703 fragment_type_mask: fragment.fragment_type_mask,
704 distribution_type: fragment.distribution_type.into(),
705 vnode_count: fragment.vnode_count(),
706 nodes: fragment.nodes.clone(),
707 state_table_ids: fragment
708 .state_table_ids
709 .iter()
710 .cloned()
711 .collect(),
712 parallelism: None,
713 },
714 )
715 })
716 .collect(),
717 downstreams: info.stream_job_fragments.downstreams.clone(),
718 };
719
720 assert!(
724 !self
725 .independent_checkpoint_job_controls
726 .contains_key(&job_id),
727 "duplicated creating batch refresh job {job_id}"
728 );
729
730 let snapshot_backfill_info_clone =
731 batch_refresh_info.snapshot_backfill_info.clone();
732 let refresh_interval_sec = batch_refresh_info.refresh_interval_sec;
733
734 let subscriber_id =
738 info.stream_job_fragments.stream_job_id().as_subscriber_id();
739 let mutation = Mutation::Add(AddMutation {
740 actor_dispatchers: Default::default(),
741 added_actors: Default::default(),
742 actor_splits: Default::default(),
743 pause: false,
744 subscriptions_to_add: snapshot_backfill_info_clone
745 .upstream_mv_table_id_to_backfill_epoch
746 .keys()
747 .map(|table_id| PbSubscriptionUpstreamInfo {
748 subscriber_id,
749 upstream_mv_table_id: *table_id,
750 })
751 .collect(),
752 backfill_nodes_to_pause: Default::default(),
753 actor_cdc_table_snapshot_splits: None,
754 new_upstream_sinks: Default::default(),
755 dropped_actors: Default::default(),
756 sink_log_store_flush: Default::default(),
757 });
758
759 let job = BatchRefreshJobCheckpointControl::new(
760 database_id,
761 job_id,
762 CreateSnapshotBackfillJobCommandInfo {
763 info: info.clone(),
764 snapshot_backfill_info: snapshot_backfill_info_clone.clone(),
765 cross_db_snapshot_backfill_info,
766 resolved_split_assignment: Default::default(),
767 refresh_interval_sec: Some(refresh_interval_sec),
768 },
769 take(notifiers),
770 snapshot_backfill_upstream_tables,
771 snapshot_epoch,
772 hummock_version_stats,
773 partial_graph_manager,
774 &logical,
775 worker_nodes,
776 refresh_interval_sec,
777 )?;
778
779 if let Some(fragment_infos) = job.fragment_infos() {
780 self.database_info.shared_actor_infos.upsert(
781 self.database_id,
782 fragment_infos.values().map(|f| (f, job_id)),
783 );
784 }
785
786 self.independent_checkpoint_job_controls
787 .insert(job_id, IndependentCheckpointJobControl::BatchRefresh(job));
788
789 for upstream_mv_table_id in snapshot_backfill_info_clone
791 .upstream_mv_table_id_to_backfill_epoch
792 .keys()
793 {
794 self.database_info.register_subscriber(
795 upstream_mv_table_id.as_job_id(),
796 info.streaming_job.id().as_subscriber_id(),
797 SubscriberType::SnapshotBackfill,
798 );
799 }
800
801 let (table_ids, node_actors) = self.collect_base_info();
802 (
803 Some(mutation),
804 table_ids,
805 None,
806 node_actors,
807 PostCollectCommand::barrier(),
808 )
809 }
810 }
811 Some(Command::CreateStreamingJob {
812 mut info,
813 job_type,
814 cross_db_snapshot_backfill_info,
815 }) => {
816 let ensembles = resolve_no_shuffle_ensembles(
817 &info.stream_job_fragments,
818 &info.upstream_fragment_downstreams,
819 )?;
820 let actors = render_actors(
821 &info.stream_job_fragments,
822 &self.database_info,
823 &info.definition,
824 &info.stream_job_fragments.inner.ctx,
825 &info.streaming_job_model,
826 partial_graph_manager
827 .control_stream_manager()
828 .env
829 .actor_id_generator(),
830 worker_nodes,
831 &ensembles,
832 &info.database_resource_group,
833 )?;
834 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
835 fill_snapshot_backfill_epoch(
836 &mut fragment.nodes,
837 None,
838 &cross_db_snapshot_backfill_info,
839 )?;
840 }
841
842 let new_upstream_sink =
844 if let CreateStreamingJobType::SinkIntoTable(ref ctx) = job_type {
845 Some(ctx)
846 } else {
847 None
848 };
849
850 let mut edges = self.database_info.build_edge(
851 Some((&info, false)),
852 None,
853 new_upstream_sink,
854 partial_graph_manager.control_stream_manager(),
855 &actors.stream_actors,
856 &actors.actor_location,
857 );
858 let resolved_split_assignment = resolve_source_splits(
860 &info,
861 &actors,
862 edges.actor_new_no_shuffle(),
863 &self.database_info,
864 )?;
865
866 let old_sink_job_id = info
867 .replace_sink
868 .as_ref()
869 .map(|old_sink_id| old_sink_id.as_job_id());
870 if old_sink_job_id.is_some()
871 && matches!(
872 job_type,
873 CreateStreamingJobType::SnapshotBackfill { .. }
874 | CreateStreamingJobType::BatchRefresh(_)
875 )
876 {
877 bail!("replace sink must not use snapshot backfill");
878 }
879
880 let cdc_tracker = if let Some(splits) = &info.cdc_table_snapshot_splits {
882 let (fragment, _) =
883 parallel_cdc_table_backfill_fragment(info.stream_job_fragments.fragments())
884 .expect("should have parallel cdc fragment");
885 Some(CdcTableBackfillTracker::new(
886 fragment.fragment_id,
887 splits.clone(),
888 ))
889 } else {
890 None
891 };
892 self.database_info
893 .pre_apply_new_job(info.streaming_job.id(), cdc_tracker);
894 self.database_info.pre_apply_new_fragments(
895 info.stream_job_fragments
896 .new_fragment_info(
897 &actors.stream_actors,
898 &actors.actor_location,
899 &resolved_split_assignment,
900 )
901 .map(|(fragment_id, fragment_infos)| {
902 (fragment_id, info.streaming_job.id(), fragment_infos)
903 }),
904 );
905 if let CreateStreamingJobType::SinkIntoTable(ref ctx) = job_type {
906 let downstream_fragment_id = ctx.new_sink_downstream.downstream_fragment_id;
907 self.database_info.pre_apply_add_node_upstream(
908 downstream_fragment_id,
909 &PbUpstreamSinkInfo {
910 upstream_fragment_id: ctx.sink_fragment_id,
911 sink_output_schema: ctx.sink_output_fields.clone(),
912 project_exprs: ctx.project_exprs.clone(),
913 },
914 );
915 }
916
917 let (table_ids, node_actors) = self.collect_base_info();
918 let dropped_actors = if let Some(old_sink_job_id) = old_sink_job_id {
919 let Some(job) = self.database_info.post_apply_remove_job(old_sink_job_id)
920 else {
921 bail!(
922 "old sink job {} not found in barrier state",
923 old_sink_job_id
924 );
925 };
926 job.fragment_infos
927 .values()
928 .flat_map(|fragment| fragment.actors.keys().copied())
929 .collect()
930 } else {
931 vec![]
932 };
933
934 let actors_to_create = Some(Command::create_streaming_job_actors_to_create(
936 &info,
937 &mut edges,
938 &actors.stream_actors,
939 &actors.actor_location,
940 ));
941
942 let actor_cdc_table_snapshot_splits = self
944 .database_info
945 .assign_cdc_backfill_splits(info.stream_job_fragments.stream_job_id())?;
946
947 let is_currently_paused = self.state.is_paused();
949 let mutation = Command::create_streaming_job_to_mutation(
950 &info,
951 &job_type,
952 dropped_actors,
953 is_currently_paused,
954 &mut edges,
955 partial_graph_manager.control_stream_manager(),
956 actor_cdc_table_snapshot_splits,
957 &resolved_split_assignment,
958 &actors.stream_actors,
959 &actors.actor_location,
960 )?;
961
962 (
963 Some(mutation),
964 table_ids,
965 actors_to_create,
966 node_actors,
967 PostCollectCommand::CreateStreamingJob {
968 info,
969 job_type,
970 cross_db_snapshot_backfill_info,
971 resolved_split_assignment,
972 },
973 )
974 }
975
976 Some(Command::Flush) => self.apply_simple_command(None, "Flush"),
977
978 Some(Command::Pause) => {
979 let prev_is_paused = self.state.is_paused();
980 self.state.set_is_paused(true);
981 let mutation = Command::pause_to_mutation(prev_is_paused);
982 let (table_ids, node_actors) = self.collect_base_info();
983 (
984 mutation,
985 table_ids,
986 None,
987 node_actors,
988 PostCollectCommand::Command("Pause".to_owned()),
989 )
990 }
991
992 Some(Command::Resume) => {
993 let prev_is_paused = self.state.is_paused();
994 self.state.set_is_paused(false);
995 let mutation = Command::resume_to_mutation(prev_is_paused);
996 let (table_ids, node_actors) = self.collect_base_info();
997 (
998 mutation,
999 table_ids,
1000 None,
1001 node_actors,
1002 PostCollectCommand::Command("Resume".to_owned()),
1003 )
1004 }
1005
1006 Some(Command::Throttle { jobs, config }) => {
1007 let mutation = Some(Command::throttle_to_mutation(&config));
1008 for (fragment_id, throttle_config) in &config {
1009 self.database_info
1010 .pre_apply_throttle(*fragment_id, throttle_config);
1011 }
1012 throttle_for_creating_jobs = Some((jobs, config));
1013 self.apply_simple_command(mutation, "Throttle")
1014 }
1015
1016 Some(Command::DropStreamingJobs {
1017 streaming_job_ids,
1018 unregistered_state_table_ids: _,
1019 dropped_sink_fragment_by_targets,
1020 }) => {
1021 for (target_fragment, sink_fragments) in &dropped_sink_fragment_by_targets {
1023 self.database_info
1024 .pre_apply_drop_node_upstream(*target_fragment, sink_fragments);
1025 }
1026
1027 let (table_ids, node_actors) = self.collect_base_info();
1028
1029 let mut actors = Vec::new();
1030 for job_id in streaming_job_ids {
1031 let Some(job) = self.database_info.post_apply_remove_job(job_id) else {
1032 warn!(
1033 %job_id,
1034 "skip drop payload for streaming job that has already been removed from barrier worker"
1035 );
1036 continue;
1037 };
1038
1039 for fragment in job.fragment_infos.values() {
1040 actors.extend(fragment.actors.keys().copied());
1041 }
1042 }
1043
1044 let mutation = Some(Command::drop_streaming_jobs_to_mutation(
1045 &actors,
1046 &dropped_sink_fragment_by_targets,
1047 ));
1048 (
1049 mutation,
1050 table_ids,
1051 None,
1052 node_actors,
1053 PostCollectCommand::DropStreamingJobs,
1054 )
1055 }
1056
1057 Some(Command::RescheduleIntent {
1058 reschedule_plan, ..
1059 }) => {
1060 let ReschedulePlan {
1061 reschedules,
1062 fragment_actors,
1063 } = reschedule_plan
1064 .as_ref()
1065 .expect("reschedule intent should be resolved in global barrier worker");
1066
1067 for (fragment_id, reschedule) in reschedules {
1069 self.database_info.pre_apply_reschedule(
1070 *fragment_id,
1071 reschedule
1072 .added_actors
1073 .iter()
1074 .flat_map(|(node_id, actors): (&WorkerId, &Vec<ActorId>)| {
1075 actors.iter().map(|actor_id| {
1076 (
1077 *actor_id,
1078 InflightActorInfo {
1079 worker_id: *node_id,
1080 vnode_bitmap: reschedule
1081 .newly_created_actors
1082 .get(actor_id)
1083 .expect("should exist")
1084 .0
1085 .0
1086 .vnode_bitmap
1087 .clone(),
1088 splits: reschedule
1089 .actor_splits
1090 .get(actor_id)
1091 .cloned()
1092 .unwrap_or_default(),
1093 },
1094 )
1095 })
1096 })
1097 .collect(),
1098 reschedule
1099 .vnode_bitmap_updates
1100 .iter()
1101 .filter(|(actor_id, _)| {
1102 !reschedule.newly_created_actors.contains_key(*actor_id)
1103 })
1104 .map(|(actor_id, bitmap)| (*actor_id, bitmap.clone()))
1105 .collect(),
1106 reschedule.actor_splits.clone(),
1107 );
1108 }
1109
1110 let (table_ids, node_actors) = self.collect_base_info();
1111
1112 let actors_to_create = Some(Command::reschedule_actors_to_create(
1114 reschedules,
1115 fragment_actors,
1116 &self.database_info,
1117 partial_graph_manager.control_stream_manager(),
1118 ));
1119
1120 self.database_info
1122 .post_apply_reschedules(reschedules.iter().map(|(fragment_id, reschedule)| {
1123 (
1124 *fragment_id,
1125 reschedule.removed_actors.iter().cloned().collect(),
1126 )
1127 }));
1128
1129 let mutation = Command::reschedule_to_mutation(
1131 reschedules,
1132 fragment_actors,
1133 partial_graph_manager.control_stream_manager(),
1134 &mut self.database_info,
1135 )?;
1136
1137 let reschedules = reschedule_plan
1138 .expect("reschedule intent should be resolved in global barrier worker")
1139 .reschedules;
1140 (
1141 mutation,
1142 table_ids,
1143 actors_to_create,
1144 node_actors,
1145 PostCollectCommand::Reschedule { reschedules },
1146 )
1147 }
1148
1149 Some(Command::ReplaceStreamJob(plan)) => {
1150 let ensembles = resolve_no_shuffle_ensembles(
1151 &plan.new_fragments,
1152 &plan.upstream_fragment_downstreams,
1153 )?;
1154 let mut render_result = render_actors(
1155 &plan.new_fragments,
1156 &self.database_info,
1157 "", &plan.new_fragments.inner.ctx,
1159 &plan.streaming_job_model,
1160 partial_graph_manager
1161 .control_stream_manager()
1162 .env
1163 .actor_id_generator(),
1164 worker_nodes,
1165 &ensembles,
1166 &plan.database_resource_group,
1167 )?;
1168
1169 if let Some(sinks) = &plan.auto_refresh_schema_sinks {
1172 let actor_id_counter = partial_graph_manager
1173 .control_stream_manager()
1174 .env
1175 .actor_id_generator();
1176 for sink_ctx in sinks {
1177 let original_fragment_id = sink_ctx.original_fragment.fragment_id;
1178 let original_frag_info = self.database_info.fragment(original_fragment_id);
1179 let actor_template = EnsembleActorTemplate::from_existing_inflight_fragment(
1180 original_frag_info,
1181 );
1182 let new_aligner = ComponentFragmentAligner::new_persistent(
1183 &actor_template,
1184 actor_id_counter,
1185 );
1186 let distribution_type: DistributionType =
1187 sink_ctx.new_fragment.distribution_type.into();
1188 let actor_assignments =
1189 new_aligner.align_component_actor(distribution_type);
1190 let new_fragment_id = sink_ctx.new_fragment.fragment_id;
1191 let mut actors = Vec::with_capacity(actor_assignments.len());
1192 for (&actor_id, (worker_id, vnode_bitmap)) in &actor_assignments {
1193 render_result.actor_location.insert(actor_id, *worker_id);
1194 actors.push(StreamActor {
1195 actor_id,
1196 fragment_id: new_fragment_id,
1197 vnode_bitmap: vnode_bitmap.clone(),
1198 mview_definition: String::new(),
1199 expr_context: Some(sink_ctx.ctx.to_expr_context()),
1200 config_override: sink_ctx.ctx.config_override.clone(),
1201 });
1202 }
1203 render_result.stream_actors.insert(new_fragment_id, actors);
1204 }
1205 }
1206
1207 let mut edges = self.database_info.build_edge(
1209 None,
1210 Some(&plan),
1211 None,
1212 partial_graph_manager.control_stream_manager(),
1213 &render_result.stream_actors,
1214 &render_result.actor_location,
1215 );
1216
1217 let fragment_actor_ids: HashMap<FragmentId, Vec<ActorId>> = render_result
1219 .stream_actors
1220 .iter()
1221 .map(|(fragment_id, actors)| {
1222 (
1223 *fragment_id,
1224 actors.iter().map(|a| a.actor_id).collect::<Vec<_>>(),
1225 )
1226 })
1227 .collect();
1228 let resolved_split_assignment = match &plan.split_plan {
1229 ReplaceJobSplitPlan::Discovered(discovered) => {
1230 SourceManager::resolve_fragment_to_actor_splits(
1231 &plan.new_fragments,
1232 discovered,
1233 &fragment_actor_ids,
1234 )?
1235 }
1236 ReplaceJobSplitPlan::AlignFromPrevious => {
1237 SourceManager::resolve_replace_source_splits(
1238 &plan.new_fragments,
1239 &plan.replace_upstream,
1240 edges.actor_new_no_shuffle(),
1241 |_fragment_id, actor_id| {
1242 self.database_info.fragment_infos().find_map(|fragment| {
1243 fragment
1244 .actors
1245 .get(&actor_id)
1246 .map(|info| info.splits.clone())
1247 })
1248 },
1249 )?
1250 }
1251 };
1252
1253 self.database_info.pre_apply_new_fragments(
1255 plan.new_fragments
1256 .new_fragment_info(
1257 &render_result.stream_actors,
1258 &render_result.actor_location,
1259 &resolved_split_assignment,
1260 )
1261 .map(|(fragment_id, new_fragment)| {
1262 (fragment_id, plan.streaming_job.id(), new_fragment)
1263 }),
1264 );
1265 for (fragment_id, replace_map) in &plan.replace_upstream {
1266 self.database_info
1267 .pre_apply_replace_node_upstream(*fragment_id, replace_map);
1268 }
1269 if let Some(sinks) = &plan.auto_refresh_schema_sinks {
1270 self.database_info
1271 .pre_apply_new_fragments(sinks.iter().map(|sink| {
1272 (
1273 sink.new_fragment.fragment_id,
1274 sink.original_sink.id.as_job_id(),
1275 sink.new_fragment_info(
1276 &render_result.stream_actors,
1277 &render_result.actor_location,
1278 ),
1279 )
1280 }));
1281 }
1282
1283 let (table_ids, node_actors) = self.collect_base_info();
1284
1285 let actors_to_create = Some(Command::replace_stream_job_actors_to_create(
1287 &plan,
1288 &mut edges,
1289 &self.database_info,
1290 &render_result.stream_actors,
1291 &render_result.actor_location,
1292 ));
1293
1294 let mutation = Command::replace_stream_job_to_mutation(
1297 &plan,
1298 &mut edges,
1299 &mut self.database_info,
1300 &resolved_split_assignment,
1301 )?;
1302
1303 {
1305 let mut fragment_ids_to_remove: Vec<_> = plan
1306 .old_fragments
1307 .fragments
1308 .values()
1309 .map(|f| f.fragment_id)
1310 .collect();
1311 if let Some(sinks) = &plan.auto_refresh_schema_sinks {
1312 fragment_ids_to_remove
1313 .extend(sinks.iter().map(|sink| sink.original_fragment.fragment_id));
1314 }
1315 self.database_info
1316 .post_apply_remove_fragments(fragment_ids_to_remove);
1317 }
1318
1319 (
1320 mutation,
1321 table_ids,
1322 actors_to_create,
1323 node_actors,
1324 PostCollectCommand::ReplaceStreamJob {
1325 plan,
1326 resolved_split_assignment,
1327 },
1328 )
1329 }
1330
1331 Some(Command::SourceChangeSplit(split_state)) => {
1332 self.database_info.pre_apply_split_assignments(
1334 split_state
1335 .split_assignment
1336 .iter()
1337 .map(|(&fragment_id, splits)| (fragment_id, splits.clone())),
1338 );
1339
1340 let mutation = Some(Command::source_change_split_to_mutation(
1341 &split_state.split_assignment,
1342 ));
1343 let (table_ids, node_actors) = self.collect_base_info();
1344 (
1345 mutation,
1346 table_ids,
1347 None,
1348 node_actors,
1349 PostCollectCommand::SourceChangeSplit {
1350 split_assignment: split_state.split_assignment,
1351 },
1352 )
1353 }
1354
1355 Some(Command::CreateSubscription {
1356 subscription_id,
1357 upstream_mv_table_id,
1358 retention_second,
1359 }) => {
1360 self.database_info.register_subscriber(
1361 upstream_mv_table_id.as_job_id(),
1362 subscription_id.as_subscriber_id(),
1363 SubscriberType::Subscription(retention_second),
1364 );
1365 let mutation = Some(Command::create_subscription_to_mutation(
1366 upstream_mv_table_id,
1367 subscription_id,
1368 ));
1369 let (table_ids, node_actors) = self.collect_base_info();
1370 (
1371 mutation,
1372 table_ids,
1373 None,
1374 node_actors,
1375 PostCollectCommand::CreateSubscription { subscription_id },
1376 )
1377 }
1378
1379 Some(Command::DropSubscription {
1380 subscription_id,
1381 upstream_mv_table_id,
1382 }) => {
1383 if self
1384 .database_info
1385 .unregister_subscriber(
1386 upstream_mv_table_id.as_job_id(),
1387 subscription_id.as_subscriber_id(),
1388 )
1389 .is_none()
1390 {
1391 warn!(%subscription_id, %upstream_mv_table_id, "no subscription to drop");
1392 }
1393 let mutation = Some(Command::drop_subscription_to_mutation(
1394 upstream_mv_table_id,
1395 subscription_id,
1396 ));
1397 let (table_ids, node_actors) = self.collect_base_info();
1398 (
1399 mutation,
1400 table_ids,
1401 None,
1402 node_actors,
1403 PostCollectCommand::Command("DropSubscription".to_owned()),
1404 )
1405 }
1406
1407 Some(Command::AlterSubscriptionRetention {
1408 subscription_id,
1409 upstream_mv_table_id,
1410 retention_second,
1411 }) => {
1412 self.database_info.update_subscription_retention(
1413 upstream_mv_table_id.as_job_id(),
1414 subscription_id.as_subscriber_id(),
1415 retention_second,
1416 );
1417 self.apply_simple_command(None, "AlterSubscriptionRetention")
1418 }
1419
1420 Some(Command::ConnectorPropsChange(config)) => {
1421 let mutation = Some(Command::connector_props_change_to_mutation(&config));
1422 let (table_ids, node_actors) = self.collect_base_info();
1423 (
1424 mutation,
1425 table_ids,
1426 None,
1427 node_actors,
1428 PostCollectCommand::ConnectorPropsChange(config),
1429 )
1430 }
1431
1432 Some(Command::Refresh {
1433 table_id,
1434 associated_source_id,
1435 }) => {
1436 let mutation = Some(Command::refresh_to_mutation(table_id, associated_source_id));
1437 self.apply_simple_command(mutation, "Refresh")
1438 }
1439
1440 Some(Command::ListFinish {
1441 table_id: _,
1442 associated_source_id,
1443 }) => {
1444 let mutation = Some(Command::list_finish_to_mutation(associated_source_id));
1445 self.apply_simple_command(mutation, "ListFinish")
1446 }
1447
1448 Some(Command::LoadFinish {
1449 table_id: _,
1450 associated_source_id,
1451 }) => {
1452 let mutation = Some(Command::load_finish_to_mutation(associated_source_id));
1453 self.apply_simple_command(mutation, "LoadFinish")
1454 }
1455
1456 Some(Command::ResetSource { source_id }) => {
1457 let mutation = Some(Command::reset_source_to_mutation(source_id));
1458 self.apply_simple_command(mutation, "ResetSource")
1459 }
1460
1461 Some(Command::ResumeBackfill { target }) => {
1462 let mutation = Command::resume_backfill_to_mutation(&target, &self.database_info)?;
1463 let (table_ids, node_actors) = self.collect_base_info();
1464 (
1465 mutation,
1466 table_ids,
1467 None,
1468 node_actors,
1469 PostCollectCommand::ResumeBackfill { target },
1470 )
1471 }
1472
1473 Some(Command::InjectSourceOffsets {
1474 source_id,
1475 split_offsets,
1476 }) => {
1477 let mutation = Some(Command::inject_source_offsets_to_mutation(
1478 source_id,
1479 &split_offsets,
1480 ));
1481 self.apply_simple_command(mutation, "InjectSourceOffsets")
1482 }
1483 };
1484
1485 let mut finished_snapshot_backfill_jobs = HashSet::new();
1486 let mutation = match mutation {
1487 Some(mutation) => Some(mutation),
1488 None => {
1489 let mut finished_snapshot_backfill_job_info = HashMap::new();
1490 if barrier_info.kind.is_checkpoint() {
1491 for (&job_id, job) in &mut self.independent_checkpoint_job_controls {
1492 if let IndependentCheckpointJobControl::CreatingStreamingJob(creating_job) =
1493 job
1494 && creating_job.should_merge_to_upstream()
1495 {
1496 let info = creating_job
1497 .start_consume_upstream(partial_graph_manager, &barrier_info)?;
1498 finished_snapshot_backfill_job_info
1499 .try_insert(job_id, info)
1500 .expect("non-duplicated");
1501 }
1502 }
1503 }
1504
1505 if !finished_snapshot_backfill_job_info.is_empty() {
1506 let actors_to_create = actors_to_create.get_or_insert_default();
1507 let mut subscriptions_to_drop = vec![];
1508 let mut dispatcher_update = vec![];
1509 let mut actor_splits = HashMap::new();
1510 for (job_id, info) in finished_snapshot_backfill_job_info {
1511 finished_snapshot_backfill_jobs.insert(job_id);
1512 subscriptions_to_drop.extend(
1513 info.snapshot_backfill_upstream_tables.iter().map(
1514 |upstream_table_id| PbSubscriptionUpstreamInfo {
1515 subscriber_id: job_id.as_subscriber_id(),
1516 upstream_mv_table_id: *upstream_table_id,
1517 },
1518 ),
1519 );
1520 for upstream_mv_table_id in &info.snapshot_backfill_upstream_tables {
1521 assert_matches!(
1522 self.database_info.unregister_subscriber(
1523 upstream_mv_table_id.as_job_id(),
1524 job_id.as_subscriber_id()
1525 ),
1526 Some(SubscriberType::SnapshotBackfill)
1527 );
1528 }
1529
1530 table_ids_to_commit.extend(
1531 info.fragment_infos
1532 .values()
1533 .flat_map(|fragment| fragment.state_table_ids.iter())
1534 .copied(),
1535 );
1536
1537 let actor_len = info
1538 .fragment_infos
1539 .values()
1540 .map(|fragment| fragment.actors.len() as u64)
1541 .sum();
1542 let id_gen = GlobalActorIdGen::new(
1543 partial_graph_manager
1544 .control_stream_manager()
1545 .env
1546 .actor_id_generator(),
1547 actor_len,
1548 );
1549 let mut next_local_actor_id = 0;
1550 let actor_mapping: HashMap<_, _> = info
1552 .fragment_infos
1553 .values()
1554 .flat_map(|fragment| fragment.actors.keys())
1555 .map(|old_actor_id| {
1556 let new_actor_id = id_gen.to_global_id(next_local_actor_id);
1557 next_local_actor_id += 1;
1558 (*old_actor_id, new_actor_id.as_global_id())
1559 })
1560 .collect();
1561 let actor_mapping = &actor_mapping;
1562 let new_stream_actors: HashMap<_, _> = info
1563 .stream_actors
1564 .into_iter()
1565 .map(|(old_actor_id, mut actor)| {
1566 let new_actor_id = actor_mapping[&old_actor_id];
1567 actor.actor_id = new_actor_id;
1568 (new_actor_id, actor)
1569 })
1570 .collect();
1571 let new_fragment_info: HashMap<_, _> = info
1572 .fragment_infos
1573 .into_iter()
1574 .map(|(fragment_id, mut fragment)| {
1575 let actors = take(&mut fragment.actors);
1576 fragment.actors = actors
1577 .into_iter()
1578 .map(|(old_actor_id, actor)| {
1579 let new_actor_id = actor_mapping[&old_actor_id];
1580 (new_actor_id, actor)
1581 })
1582 .collect();
1583 (fragment_id, fragment)
1584 })
1585 .collect();
1586 actor_splits.extend(
1587 new_fragment_info
1588 .values()
1589 .flat_map(|fragment| &fragment.actors)
1590 .map(|(actor_id, actor)| {
1591 (
1592 *actor_id,
1593 ConnectorSplits {
1594 splits: actor
1595 .splits
1596 .iter()
1597 .map(ConnectorSplit::from)
1598 .collect(),
1599 },
1600 )
1601 }),
1602 );
1603 let partial_graph_id = to_partial_graph_id(self.database_id, None);
1605 let mut edge_builder = FragmentEdgeBuilder::new(
1606 info.upstream_fragment_downstreams
1607 .keys()
1608 .map(|upstream_fragment_id| {
1609 self.database_info.fragment(*upstream_fragment_id)
1610 })
1611 .chain(new_fragment_info.values())
1612 .map(|fragment| {
1613 (
1614 fragment.fragment_id,
1615 EdgeBuilderFragmentInfo::from_inflight(
1616 fragment,
1617 partial_graph_id,
1618 partial_graph_manager.control_stream_manager(),
1619 ),
1620 )
1621 }),
1622 );
1623 edge_builder.add_relations(&info.upstream_fragment_downstreams);
1624 edge_builder.add_relations(&info.downstreams);
1625 let mut edges = edge_builder.build();
1626 let new_actors_to_create = edges.collect_actors_to_create(
1627 new_fragment_info.values().map(|fragment| {
1628 (
1629 fragment.fragment_id,
1630 &fragment.nodes,
1631 fragment.actors.iter().map(|(actor_id, actor)| {
1632 (&new_stream_actors[actor_id], actor.worker_id)
1633 }),
1634 [], )
1636 }),
1637 );
1638 dispatcher_update.extend(
1639 info.upstream_fragment_downstreams.keys().flat_map(
1640 |upstream_fragment_id| {
1641 let new_actor_dispatchers = edges
1642 .dispatchers
1643 .remove(upstream_fragment_id)
1644 .expect("should exist");
1645 new_actor_dispatchers.into_iter().flat_map(
1646 |(upstream_actor_id, dispatchers)| {
1647 dispatchers.into_iter().map(move |dispatcher| {
1648 PbDispatcherUpdate {
1649 actor_id: upstream_actor_id,
1650 dispatcher_id: dispatcher.dispatcher_id,
1651 hash_mapping: dispatcher.hash_mapping,
1652 removed_downstream_actor_id: dispatcher
1653 .downstream_actor_id
1654 .iter()
1655 .map(|new_downstream_actor_id| {
1656 actor_mapping
1657 .iter()
1658 .find_map(
1659 |(old_actor_id, new_actor_id)| {
1660 (new_downstream_actor_id
1661 == new_actor_id)
1662 .then_some(*old_actor_id)
1663 },
1664 )
1665 .expect("should exist")
1666 })
1667 .collect(),
1668 added_downstream_actor_id: dispatcher
1669 .downstream_actor_id,
1670 }
1671 })
1672 },
1673 )
1674 },
1675 ),
1676 );
1677 assert!(edges.is_empty(), "remaining edges: {:?}", edges);
1678 for (worker_id, worker_actors) in new_actors_to_create {
1679 node_actors.entry(worker_id).or_default().extend(
1680 worker_actors.values().flat_map(|(_, actors, _)| {
1681 actors.iter().map(|(actor, _, _)| actor.actor_id)
1682 }),
1683 );
1684 actors_to_create
1685 .entry(worker_id)
1686 .or_default()
1687 .extend(worker_actors);
1688 }
1689 self.database_info.add_existing(InflightStreamingJobInfo {
1690 job_id,
1691 fragment_infos: new_fragment_info,
1692 subscribers: Default::default(), status: CreateStreamingJobStatus::Created,
1694 cdc_table_backfill_tracker: None, });
1696 }
1697
1698 Some(PbMutation::Update(PbUpdateMutation {
1699 dispatcher_update,
1700 merge_update: vec![], actor_vnode_bitmap_update: Default::default(), dropped_actors: vec![], actor_splits,
1704 actor_new_dispatchers: Default::default(), actor_cdc_table_snapshot_splits: None, sink_schema_change: Default::default(), subscriptions_to_drop,
1708 }))
1709 } else {
1710 let fragment_ids = self.database_info.take_pending_backfill_nodes();
1711 if fragment_ids.is_empty() {
1712 None
1713 } else {
1714 Some(PbMutation::StartFragmentBackfill(
1715 PbStartFragmentBackfillMutation { fragment_ids },
1716 ))
1717 }
1718 }
1719 }
1720 };
1721
1722 for (job_id, job) in &mut self.independent_checkpoint_job_controls {
1724 match job {
1725 IndependentCheckpointJobControl::CreatingStreamingJob(creating_job) => {
1726 if !finished_snapshot_backfill_jobs.contains(job_id) {
1727 let throttle_mutation = if let Some((ref jobs, ref config)) =
1728 throttle_for_creating_jobs
1729 && jobs.contains(job_id)
1730 {
1731 assert_eq!(
1732 jobs.len(),
1733 1,
1734 "should not alter rate limit of snapshot backfill job with other jobs"
1735 );
1736 Some((
1737 Mutation::Throttle(ThrottleMutation {
1738 fragment_throttle: config
1739 .iter()
1740 .map(|(fragment_id, config)| (*fragment_id, *config))
1741 .collect(),
1742 }),
1743 take(notifiers),
1744 ))
1745 } else {
1746 None
1747 };
1748 creating_job.on_new_upstream_barrier(
1749 partial_graph_manager,
1750 &barrier_info,
1751 throttle_mutation,
1752 )?;
1753 }
1754 }
1755 IndependentCheckpointJobControl::BatchRefresh(batch_refresh_job) => {
1756 batch_refresh_job.on_new_upstream_barrier(
1757 partial_graph_manager,
1758 &barrier_info,
1759 None, )?;
1761 }
1762 }
1763 }
1764
1765 partial_graph_manager.inject_barrier(
1766 to_partial_graph_id(self.database_id, None),
1767 mutation,
1768 &node_actors,
1769 InflightFragmentInfo::existing_table_ids(self.database_info.fragment_infos()),
1770 InflightFragmentInfo::workers(self.database_info.fragment_infos()),
1771 actors_to_create,
1772 PartialGraphBarrierInfo::new(
1773 post_collect_command,
1774 barrier_info,
1775 take(notifiers),
1776 table_ids_to_commit,
1777 ),
1778 )?;
1779
1780 Ok(ApplyCommandInfo {
1781 jobs_to_wait: finished_snapshot_backfill_jobs,
1782 })
1783 }
1784}