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::update_mutation::PbDispatcherUpdate;
34use risingwave_pb::stream_plan::{
35 AddMutation, PbStartFragmentBackfillMutation, PbSubscriptionUpstreamInfo, PbUpdateMutation,
36 PbUpstreamSinkInfo,
37};
38use tracing::warn;
39
40use crate::barrier::cdc_progress::CdcTableBackfillTracker;
41use crate::barrier::checkpoint::{
42 BatchRefreshJobCheckpointControl, BatchRefreshLogicalFragments, CreatingStreamingJobControl,
43 DatabaseCheckpointControl, IndependentCheckpointJobControl,
44};
45use crate::barrier::command::{
46 CreateStreamingJobCommandInfo, PostCollectCommand, ReschedulePlan, ThrottleConfigMap,
47};
48use crate::barrier::context::CreateSnapshotBackfillJobCommandInfo;
49use crate::barrier::edge_builder::{EdgeBuilderFragmentInfo, FragmentEdgeBuilder};
50use crate::barrier::info::{
51 BarrierInfo, CreateStreamingJobStatus, InflightDatabaseInfo, InflightStreamingJobInfo,
52 SubscriberType,
53};
54use crate::barrier::notifier::NotifierStarter;
55use crate::barrier::partial_graph::{PartialGraphBarrierInfo, PartialGraphManager};
56use crate::barrier::rpc::to_partial_graph_id;
57use crate::barrier::{BarrierKind, Command, CreateStreamingJobType, TracedEpoch};
58use crate::controller::fragment::{InflightActorInfo, InflightFragmentInfo};
59use crate::controller::scale::{
60 ComponentFragmentAligner, EnsembleActorTemplate, LoadedFragment, NoShuffleEnsemble,
61 build_no_shuffle_fragment_graph_edges, find_no_shuffle_graphs,
62};
63use crate::model::{
64 ActorId, ActorNewNoShuffle, FragmentDownstreamRelation, FragmentId, StreamActor, StreamContext,
65 StreamJobActorsToCreate, StreamJobFragmentsToCreate,
66};
67use crate::stream::cdc::parallel_cdc_table_backfill_fragment;
68use crate::stream::{
69 GlobalActorIdGen, ReplaceJobSplitPlan, SourceManager, SplitAssignment,
70 fill_snapshot_backfill_epoch,
71};
72use crate::{MetaError, MetaResult};
73
74pub(in crate::barrier) struct BarrierWorkerState {
76 in_flight_prev_epoch: TracedEpoch,
81
82 pending_non_checkpoint_barriers: Vec<u64>,
84
85 is_paused: bool,
87}
88
89impl BarrierWorkerState {
90 pub(super) fn new() -> Self {
91 Self {
92 in_flight_prev_epoch: TracedEpoch::new(Epoch::now()),
93 pending_non_checkpoint_barriers: vec![],
94 is_paused: false,
95 }
96 }
97
98 pub fn recovery(in_flight_prev_epoch: TracedEpoch, is_paused: bool) -> Self {
99 Self {
100 in_flight_prev_epoch,
101 pending_non_checkpoint_barriers: vec![],
102 is_paused,
103 }
104 }
105
106 pub fn is_paused(&self) -> bool {
107 self.is_paused
108 }
109
110 fn set_is_paused(&mut self, is_paused: bool) {
111 if self.is_paused != is_paused {
112 tracing::info!(
113 currently_paused = self.is_paused,
114 newly_paused = is_paused,
115 "update paused state"
116 );
117 self.is_paused = is_paused;
118 }
119 }
120
121 pub fn in_flight_prev_epoch(&self) -> &TracedEpoch {
122 &self.in_flight_prev_epoch
123 }
124
125 pub fn next_barrier_info(
127 &mut self,
128 is_checkpoint: bool,
129 curr_epoch: TracedEpoch,
130 ) -> BarrierInfo {
131 assert!(
132 self.in_flight_prev_epoch.value() < curr_epoch.value(),
133 "curr epoch regress. {} > {}",
134 self.in_flight_prev_epoch.value(),
135 curr_epoch.value()
136 );
137 let prev_epoch = self.in_flight_prev_epoch.clone();
138 self.in_flight_prev_epoch = curr_epoch.clone();
139 self.pending_non_checkpoint_barriers
140 .push(prev_epoch.value().0);
141 let kind = if is_checkpoint {
142 let epochs = take(&mut self.pending_non_checkpoint_barriers);
143 BarrierKind::Checkpoint(epochs)
144 } else {
145 BarrierKind::Barrier
146 };
147 BarrierInfo {
148 prev_epoch,
149 curr_epoch,
150 kind,
151 }
152 }
153}
154
155pub(super) struct ApplyCommandInfo {
156 pub jobs_to_wait: HashSet<JobId>,
157}
158
159type ApplyCommandResult = (
162 Option<Mutation>,
163 HashSet<TableId>,
164 Option<StreamJobActorsToCreate>,
165 HashMap<WorkerId, HashSet<ActorId>>,
166 PostCollectCommand,
167);
168
169pub(crate) struct RenderResult {
171 pub stream_actors: HashMap<FragmentId, Vec<StreamActor>>,
173 pub actor_location: HashMap<ActorId, WorkerId>,
175}
176
177pub(crate) fn resolve_no_shuffle_ensembles(
186 fragments: &StreamJobFragmentsToCreate,
187 upstream_fragment_downstreams: &FragmentDownstreamRelation,
188) -> MetaResult<Vec<NoShuffleEnsemble>> {
189 let mut new_no_shuffle: HashMap<_, HashSet<_>> = HashMap::new();
191
192 for (upstream_fid, relations) in &fragments.downstreams {
194 for rel in relations {
195 if rel.dispatcher_type == DispatcherType::NoShuffle {
196 new_no_shuffle
197 .entry(*upstream_fid)
198 .or_default()
199 .insert(rel.downstream_fragment_id);
200 }
201 }
202 }
203
204 for (upstream_fid, relations) in upstream_fragment_downstreams {
206 for rel in relations {
207 if rel.dispatcher_type == DispatcherType::NoShuffle {
208 new_no_shuffle
209 .entry(*upstream_fid)
210 .or_default()
211 .insert(rel.downstream_fragment_id);
212 }
213 }
214 }
215
216 let mut ensembles = if new_no_shuffle.is_empty() {
217 Vec::new()
218 } else {
219 let no_shuffle_edges: Vec<(FragmentId, FragmentId)> = new_no_shuffle
221 .iter()
222 .flat_map(|(upstream_fid, downstream_fids)| {
223 downstream_fids
224 .iter()
225 .map(move |downstream_fid| (*upstream_fid, *downstream_fid))
226 })
227 .collect();
228
229 let all_fragment_ids: Vec<FragmentId> = no_shuffle_edges
230 .iter()
231 .flat_map(|(u, d)| [*u, *d])
232 .collect::<HashSet<_>>()
233 .into_iter()
234 .collect();
235
236 let (fwd, bwd) = build_no_shuffle_fragment_graph_edges(no_shuffle_edges);
237 find_no_shuffle_graphs(&all_fragment_ids, &fwd, &bwd)?
238 };
239
240 let covered: HashSet<FragmentId> = ensembles
242 .iter()
243 .flat_map(|e| e.component_fragments())
244 .collect();
245 for fragment_id in fragments.inner.fragments.keys() {
246 if !covered.contains(fragment_id) {
247 ensembles.push(NoShuffleEnsemble::singleton(*fragment_id));
248 }
249 }
250
251 Ok(ensembles)
252}
253
254pub(super) fn render_actors(
265 fragments: &StreamJobFragmentsToCreate,
266 database_info: &InflightDatabaseInfo,
267 definition: &str,
268 ctx: &StreamContext,
269 streaming_job_model: &streaming_job::Model,
270 actor_id_counter: &AtomicU32,
271 worker_map: &HashMap<WorkerId, WorkerNode>,
272 ensembles: &[NoShuffleEnsemble],
273 database_resource_group: &str,
274) -> MetaResult<RenderResult> {
275 let mut actor_assignments: HashMap<FragmentId, HashMap<ActorId, (WorkerId, Option<Bitmap>)>> =
278 HashMap::new();
279
280 for ensemble in ensembles {
281 let existing_fragment_ids: Vec<FragmentId> = ensemble
286 .component_fragments()
287 .filter(|fragment_id| !fragments.inner.fragments.contains_key(fragment_id))
288 .collect();
289
290 let actor_template = if let Some(&first_existing) = existing_fragment_ids.first() {
291 let template = EnsembleActorTemplate::from_existing_inflight_fragment(
292 database_info.fragment(first_existing),
293 );
294
295 for &other_fragment_id in &existing_fragment_ids[1..] {
298 let other = EnsembleActorTemplate::from_existing_inflight_fragment(
299 database_info.fragment(other_fragment_id),
300 );
301 template.assert_aligned_with(&other, first_existing, other_fragment_id);
302 }
303
304 template
305 } else {
306 let first_component = ensemble
308 .component_fragments()
309 .next()
310 .expect("ensemble must have at least one component");
311 let fragment = &fragments.inner.fragments[&first_component];
312 let distribution_type: DistributionType = fragment.distribution_type.into();
313 let vnode_count = fragment.vnode_count();
314
315 for fragment_id in ensemble.component_fragments() {
317 let f = &fragments.inner.fragments[&fragment_id];
318 assert_eq!(
319 vnode_count,
320 f.vnode_count(),
321 "component fragments {} and {} in the same no-shuffle ensemble have \
322 different vnode counts: {} vs {}",
323 first_component,
324 fragment_id,
325 vnode_count,
326 f.vnode_count(),
327 );
328 }
329
330 EnsembleActorTemplate::render_new(
331 streaming_job_model,
332 worker_map,
333 None,
334 database_resource_group.to_owned(),
335 distribution_type,
336 vnode_count,
337 )?
338 };
339
340 for fragment_id in ensemble.component_fragments() {
342 if !fragments.inner.fragments.contains_key(&fragment_id) {
343 continue; }
345 let fragment = &fragments.inner.fragments[&fragment_id];
346 let distribution_type: DistributionType = fragment.distribution_type.into();
347 let aligner =
348 ComponentFragmentAligner::new_persistent(&actor_template, actor_id_counter);
349 let assignments = aligner.align_component_actor(distribution_type);
350 actor_assignments.insert(fragment_id, assignments);
351 }
352 }
353
354 let mut result_stream_actors: HashMap<FragmentId, Vec<StreamActor>> = HashMap::new();
356 let mut result_actor_location: HashMap<ActorId, WorkerId> = HashMap::new();
357
358 for (fragment_id, assignments) in &actor_assignments {
359 let mut actors = Vec::with_capacity(assignments.len());
360 for (&actor_id, (worker_id, vnode_bitmap)) in assignments {
361 result_actor_location.insert(actor_id, *worker_id);
362 actors.push(StreamActor {
363 actor_id,
364 fragment_id: *fragment_id,
365 vnode_bitmap: vnode_bitmap.clone(),
366 mview_definition: definition.to_owned(),
367 expr_context: Some(ctx.to_expr_context()),
368 config_override: ctx.config_override.clone(),
369 });
370 }
371 result_stream_actors.insert(*fragment_id, actors);
372 }
373
374 Ok(RenderResult {
375 stream_actors: result_stream_actors,
376 actor_location: result_actor_location,
377 })
378}
379impl DatabaseCheckpointControl {
380 fn collect_base_info(&self) -> (HashSet<TableId>, HashMap<WorkerId, HashSet<ActorId>>) {
382 let table_ids_to_commit = self.database_info.existing_table_ids().collect();
383 let node_actors =
384 InflightFragmentInfo::actor_ids_to_collect(self.database_info.fragment_infos());
385 (table_ids_to_commit, node_actors)
386 }
387
388 fn apply_simple_command(
392 &self,
393 mutation: Option<Mutation>,
394 command_name: &'static str,
395 ) -> ApplyCommandResult {
396 let (table_ids, node_actors) = self.collect_base_info();
397 (
398 mutation,
399 table_ids,
400 None,
401 node_actors,
402 PostCollectCommand::Command(command_name.to_owned()),
403 )
404 }
405
406 pub(super) fn apply_command(
409 &mut self,
410 command: Option<Command>,
411 notifier: &mut Option<NotifierStarter>,
412 barrier_info: BarrierInfo,
413 partial_graph_manager: &mut PartialGraphManager,
414 hummock_version_stats: &HummockVersionStats,
415 worker_nodes: &HashMap<WorkerId, WorkerNode>,
416 ) -> MetaResult<ApplyCommandInfo> {
417 debug_assert!(
418 !matches!(
419 command,
420 Some(Command::RescheduleIntent {
421 reschedule_plan: None,
422 ..
423 })
424 ),
425 "reschedule intent must be resolved before apply"
426 );
427 if matches!(
428 command,
429 Some(Command::RescheduleIntent {
430 reschedule_plan: None,
431 ..
432 })
433 ) {
434 bail!("reschedule intent must be resolved before apply");
435 }
436
437 fn resolve_source_splits(
442 info: &CreateStreamingJobCommandInfo,
443 render_result: &RenderResult,
444 actor_no_shuffle: &ActorNewNoShuffle,
445 database_info: &InflightDatabaseInfo,
446 ) -> MetaResult<SplitAssignment> {
447 let fragment_actor_ids: HashMap<FragmentId, Vec<ActorId>> = render_result
448 .stream_actors
449 .iter()
450 .map(|(fragment_id, actors)| {
451 (
452 *fragment_id,
453 actors.iter().map(|a| a.actor_id).collect::<Vec<_>>(),
454 )
455 })
456 .collect();
457 let mut resolved = SourceManager::resolve_fragment_to_actor_splits(
458 &info.stream_job_fragments,
459 &info.init_split_assignment,
460 &fragment_actor_ids,
461 )?;
462 resolved.extend(SourceManager::resolve_backfill_splits(
463 &info.stream_job_fragments,
464 actor_no_shuffle,
465 |fragment_id, actor_id| {
466 database_info
467 .fragment(fragment_id)
468 .actors
469 .get(&actor_id)
470 .map(|info| info.splits.clone())
471 },
472 )?);
473 Ok(resolved)
474 }
475
476 let mut notify_database_graph = command.is_some();
477 let mut throttle_config: Option<ThrottleConfigMap> = None;
478
479 let (
483 mutation,
484 mut table_ids_to_commit,
485 mut actors_to_create,
486 mut node_actors,
487 post_collect_command,
488 ) = match command {
489 None => self.apply_simple_command(None, "barrier"),
490 Some(Command::CreateStreamingJob {
491 mut info,
492 job_type:
493 CreateStreamingJobType::SnapshotBackfill {
494 mut snapshot_backfill_info,
495 since_epoch,
496 },
497 cross_db_snapshot_backfill_info,
498 }) => {
499 notify_database_graph = false;
500 let ensembles = resolve_no_shuffle_ensembles(
501 &info.stream_job_fragments,
502 &info.upstream_fragment_downstreams,
503 )?;
504 let actors = render_actors(
505 &info.stream_job_fragments,
506 &self.database_info,
507 &info.definition,
508 &info.stream_job_fragments.inner.ctx,
509 &info.streaming_job_model,
510 partial_graph_manager
511 .control_stream_manager()
512 .env
513 .actor_id_generator(),
514 worker_nodes,
515 &ensembles,
516 &info.database_resource_group,
517 )?;
518 {
519 assert!(!self.state.is_paused());
520 let (snapshot_epoch, since_timestamp_upstream_log_epochs) =
521 if let Some(since_epoch) = &since_epoch {
522 let (snapshot_epoch, log_epochs) =
523 since_epoch.resolved.as_ref().ok_or_else(|| {
524 MetaError::from(anyhow::anyhow!(
525 "since_timestamp epoch has not been resolved for snapshot backfill"
526 ))
527 })?;
528 (
529 *snapshot_epoch,
530 Some((
531 log_epochs,
532 to_partial_graph_id(self.database_id, None),
533 barrier_info.prev_epoch(),
534 )),
535 )
536 } else {
537 (barrier_info.prev_epoch(), None)
538 };
539 for snapshot_backfill_epoch in snapshot_backfill_info
541 .upstream_mv_table_id_to_backfill_epoch
542 .values_mut()
543 {
544 assert_eq!(
545 snapshot_backfill_epoch.replace(snapshot_epoch),
546 None,
547 "must not set previously"
548 );
549 }
550 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
551 fill_snapshot_backfill_epoch(
552 &mut fragment.nodes,
553 Some(&snapshot_backfill_info),
554 &cross_db_snapshot_backfill_info,
555 )?;
556 }
557 let job_id = info.stream_job_fragments.stream_job_id();
558 let snapshot_backfill_upstream_tables = snapshot_backfill_info
559 .upstream_mv_table_id_to_backfill_epoch
560 .keys()
561 .cloned()
562 .collect();
563 let mut edges = self.database_info.build_edge(
565 Some((&info, true)),
566 None,
567 None,
568 partial_graph_manager.control_stream_manager(),
569 &actors.stream_actors,
570 &actors.actor_location,
571 );
572 let resolved_split_assignment = resolve_source_splits(
574 &info,
575 &actors,
576 edges.actor_new_no_shuffle(),
577 &self.database_info,
578 )?;
579
580 let Entry::Vacant(entry) =
581 self.independent_checkpoint_job_controls.entry(job_id)
582 else {
583 panic!("duplicated creating snapshot backfill job {job_id}");
584 };
585
586 let term_id = self.term_id.as_str();
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 notifier.as_mut(),
597 snapshot_backfill_upstream_tables,
598 snapshot_epoch,
599 since_timestamp_upstream_log_epochs,
600 hummock_version_stats,
601 term_id,
602 partial_graph_manager,
603 &mut edges,
604 &resolved_split_assignment,
605 &actors,
606 )?;
607
608 if let Some(fragment_infos) = job.fragment_infos() {
609 self.database_info.shared_actor_infos.upsert(
610 self.database_id,
611 fragment_infos.values().map(|f| (f, job_id)),
612 );
613 }
614
615 for upstream_mv_table_id in snapshot_backfill_info
616 .upstream_mv_table_id_to_backfill_epoch
617 .keys()
618 {
619 self.database_info.register_subscriber(
620 upstream_mv_table_id.as_job_id(),
621 info.streaming_job.id().as_subscriber_id(),
622 SubscriberType::SnapshotBackfill,
623 );
624 }
625
626 let mutation = Command::create_streaming_job_to_mutation(
627 &info,
628 &CreateStreamingJobType::SnapshotBackfill {
629 snapshot_backfill_info,
630 since_epoch,
631 },
632 [],
633 self.state.is_paused(),
634 &mut edges,
635 partial_graph_manager.control_stream_manager(),
636 None,
637 &resolved_split_assignment,
638 &actors.stream_actors,
639 &actors.actor_location,
640 )?;
641
642 let (table_ids, node_actors) = self.collect_base_info();
643 (
644 Some(mutation),
645 table_ids,
646 None,
647 node_actors,
648 PostCollectCommand::barrier(),
649 )
650 }
651 }
652 Some(Command::CreateStreamingJob {
653 mut info,
654 job_type: CreateStreamingJobType::BatchRefresh(mut batch_refresh_info),
655 cross_db_snapshot_backfill_info,
656 }) => {
657 notify_database_graph = false;
658 {
659 if self.state.is_paused() {
660 bail!("cannot create batch refresh job while database barrier is paused");
661 }
662 let snapshot_epoch = barrier_info.prev_epoch();
663 let job_id = info.stream_job_fragments.stream_job_id();
664 let database_id = info.streaming_job.database_id();
665
666 let snapshot_backfill_info = &mut batch_refresh_info.snapshot_backfill_info;
668 for snapshot_backfill_epoch in snapshot_backfill_info
669 .upstream_mv_table_id_to_backfill_epoch
670 .values_mut()
671 {
672 assert_eq!(
673 snapshot_backfill_epoch.replace(snapshot_epoch),
674 None,
675 "must not set previously"
676 );
677 }
678 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
679 fill_snapshot_backfill_epoch(
680 &mut fragment.nodes,
681 Some(snapshot_backfill_info),
682 &cross_db_snapshot_backfill_info,
683 )?;
684 }
685 let snapshot_backfill_upstream_tables: HashSet<TableId> =
686 snapshot_backfill_info
687 .upstream_mv_table_id_to_backfill_epoch
688 .keys()
689 .cloned()
690 .collect();
691
692 let logical = BatchRefreshLogicalFragments {
694 fragments: info
695 .stream_job_fragments
696 .inner
697 .fragments
698 .iter()
699 .map(|(&fid, fragment)| {
700 (
701 fid,
702 LoadedFragment {
703 fragment_id: fid,
704 job_id,
705 fragment_type_mask: fragment.fragment_type_mask,
706 distribution_type: fragment.distribution_type.into(),
707 vnode_count: fragment.vnode_count(),
708 nodes: fragment.nodes.clone(),
709 state_table_ids: fragment
710 .state_table_ids
711 .iter()
712 .cloned()
713 .collect(),
714 parallelism: None,
715 },
716 )
717 })
718 .collect(),
719 downstreams: info.stream_job_fragments.downstreams.clone(),
720 };
721
722 assert!(
726 !self
727 .independent_checkpoint_job_controls
728 .contains_key(&job_id),
729 "duplicated creating batch refresh job {job_id}"
730 );
731
732 let snapshot_backfill_info_clone =
733 batch_refresh_info.snapshot_backfill_info.clone();
734 let refresh_interval_sec = batch_refresh_info.refresh_interval_sec;
735
736 let subscriber_id =
740 info.stream_job_fragments.stream_job_id().as_subscriber_id();
741 let mutation = Mutation::Add(AddMutation {
742 actor_dispatchers: Default::default(),
743 added_actors: Default::default(),
744 actor_splits: Default::default(),
745 pause: false,
746 subscriptions_to_add: snapshot_backfill_info_clone
747 .upstream_mv_table_id_to_backfill_epoch
748 .keys()
749 .map(|table_id| PbSubscriptionUpstreamInfo {
750 subscriber_id,
751 upstream_mv_table_id: *table_id,
752 })
753 .collect(),
754 backfill_nodes_to_pause: Default::default(),
755 actor_cdc_table_snapshot_splits: None,
756 new_upstream_sinks: Default::default(),
757 dropped_actors: Default::default(),
758 sink_log_store_flush: Default::default(),
759 });
760
761 let job = BatchRefreshJobCheckpointControl::new(
762 database_id,
763 job_id,
764 CreateSnapshotBackfillJobCommandInfo {
765 info: info.clone(),
766 snapshot_backfill_info: snapshot_backfill_info_clone.clone(),
767 cross_db_snapshot_backfill_info,
768 resolved_split_assignment: Default::default(),
769 refresh_interval_sec: Some(refresh_interval_sec),
770 },
771 notifier.as_mut(),
772 snapshot_backfill_upstream_tables,
773 snapshot_epoch,
774 hummock_version_stats,
775 self.term_id(),
776 partial_graph_manager,
777 &logical,
778 worker_nodes,
779 refresh_interval_sec,
780 )?;
781
782 if let Some(fragment_infos) = job.fragment_infos() {
783 self.database_info.shared_actor_infos.upsert(
784 self.database_id,
785 fragment_infos.values().map(|f| (f, job_id)),
786 );
787 }
788
789 self.independent_checkpoint_job_controls
790 .insert(job_id, IndependentCheckpointJobControl::BatchRefresh(job));
791
792 for upstream_mv_table_id in snapshot_backfill_info_clone
794 .upstream_mv_table_id_to_backfill_epoch
795 .keys()
796 {
797 self.database_info.register_subscriber(
798 upstream_mv_table_id.as_job_id(),
799 info.streaming_job.id().as_subscriber_id(),
800 SubscriberType::SnapshotBackfill,
801 );
802 }
803
804 let (table_ids, node_actors) = self.collect_base_info();
805 (
806 Some(mutation),
807 table_ids,
808 None,
809 node_actors,
810 PostCollectCommand::barrier(),
811 )
812 }
813 }
814 Some(Command::CreateStreamingJob {
815 mut info,
816 job_type,
817 cross_db_snapshot_backfill_info,
818 }) => {
819 let ensembles = resolve_no_shuffle_ensembles(
820 &info.stream_job_fragments,
821 &info.upstream_fragment_downstreams,
822 )?;
823 let actors = render_actors(
824 &info.stream_job_fragments,
825 &self.database_info,
826 &info.definition,
827 &info.stream_job_fragments.inner.ctx,
828 &info.streaming_job_model,
829 partial_graph_manager
830 .control_stream_manager()
831 .env
832 .actor_id_generator(),
833 worker_nodes,
834 &ensembles,
835 &info.database_resource_group,
836 )?;
837 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
838 fill_snapshot_backfill_epoch(
839 &mut fragment.nodes,
840 None,
841 &cross_db_snapshot_backfill_info,
842 )?;
843 }
844
845 let new_upstream_sink =
847 if let CreateStreamingJobType::SinkIntoTable(ref ctx) = job_type {
848 Some(ctx)
849 } else {
850 None
851 };
852
853 let mut edges = self.database_info.build_edge(
854 Some((&info, false)),
855 None,
856 new_upstream_sink,
857 partial_graph_manager.control_stream_manager(),
858 &actors.stream_actors,
859 &actors.actor_location,
860 );
861 let resolved_split_assignment = resolve_source_splits(
863 &info,
864 &actors,
865 edges.actor_new_no_shuffle(),
866 &self.database_info,
867 )?;
868
869 let old_sink_job_id = info
870 .replace_sink
871 .as_ref()
872 .map(|old_sink_id| old_sink_id.as_job_id());
873 if old_sink_job_id.is_some()
874 && matches!(
875 job_type,
876 CreateStreamingJobType::SnapshotBackfill { .. }
877 | CreateStreamingJobType::BatchRefresh(_)
878 )
879 {
880 bail!("replace sink must not use snapshot backfill");
881 }
882
883 let cdc_tracker = if let Some(splits) = &info.cdc_table_snapshot_splits {
885 let (fragment, _) =
886 parallel_cdc_table_backfill_fragment(info.stream_job_fragments.fragments())
887 .expect("should have parallel cdc fragment");
888 Some(CdcTableBackfillTracker::new(
889 fragment.fragment_id,
890 splits.clone(),
891 ))
892 } else {
893 None
894 };
895 self.database_info
896 .pre_apply_new_job(info.streaming_job.id(), cdc_tracker);
897 self.database_info.pre_apply_new_fragments(
898 info.stream_job_fragments
899 .new_fragment_info(
900 &actors.stream_actors,
901 &actors.actor_location,
902 &resolved_split_assignment,
903 )
904 .map(|(fragment_id, fragment_infos)| {
905 (fragment_id, info.streaming_job.id(), fragment_infos)
906 }),
907 );
908 if let CreateStreamingJobType::SinkIntoTable(ref ctx) = job_type {
909 let downstream_fragment_id = ctx.new_sink_downstream.downstream_fragment_id;
910 self.database_info.pre_apply_add_node_upstream(
911 downstream_fragment_id,
912 &PbUpstreamSinkInfo {
913 upstream_fragment_id: ctx.sink_fragment_id,
914 sink_output_schema: ctx.sink_output_fields.clone(),
915 project_exprs: ctx.project_exprs.clone(),
916 },
917 );
918 }
919
920 let (table_ids, node_actors) = self.collect_base_info();
921 let dropped_actors = if let Some(old_sink_job_id) = old_sink_job_id {
922 let Some(job) = self.database_info.post_apply_remove_job(old_sink_job_id)
923 else {
924 bail!(
925 "old sink job {} not found in barrier state",
926 old_sink_job_id
927 );
928 };
929 job.fragment_infos
930 .values()
931 .flat_map(|fragment| fragment.actors.keys().copied())
932 .collect()
933 } else {
934 vec![]
935 };
936
937 let actors_to_create = Some(Command::create_streaming_job_actors_to_create(
939 &info,
940 &mut edges,
941 &actors.stream_actors,
942 &actors.actor_location,
943 ));
944
945 let actor_cdc_table_snapshot_splits = self
947 .database_info
948 .assign_cdc_backfill_splits(info.stream_job_fragments.stream_job_id())?;
949
950 let is_currently_paused = self.state.is_paused();
952 let mutation = Command::create_streaming_job_to_mutation(
953 &info,
954 &job_type,
955 dropped_actors,
956 is_currently_paused,
957 &mut edges,
958 partial_graph_manager.control_stream_manager(),
959 actor_cdc_table_snapshot_splits,
960 &resolved_split_assignment,
961 &actors.stream_actors,
962 &actors.actor_location,
963 )?;
964
965 (
966 Some(mutation),
967 table_ids,
968 actors_to_create,
969 node_actors,
970 PostCollectCommand::CreateStreamingJob {
971 info,
972 job_type,
973 cross_db_snapshot_backfill_info,
974 resolved_split_assignment,
975 },
976 )
977 }
978
979 Some(Command::Flush) => self.apply_simple_command(None, "Flush"),
980
981 Some(Command::Pause) => {
982 let prev_is_paused = self.state.is_paused();
983 self.state.set_is_paused(true);
984 let mutation = Command::pause_to_mutation(prev_is_paused);
985 let (table_ids, node_actors) = self.collect_base_info();
986 (
987 mutation,
988 table_ids,
989 None,
990 node_actors,
991 PostCollectCommand::Command("Pause".to_owned()),
992 )
993 }
994
995 Some(Command::Resume) => {
996 let prev_is_paused = self.state.is_paused();
997 self.state.set_is_paused(false);
998 let mutation = Command::resume_to_mutation(prev_is_paused);
999 let (table_ids, node_actors) = self.collect_base_info();
1000 (
1001 mutation,
1002 table_ids,
1003 None,
1004 node_actors,
1005 PostCollectCommand::Command("Resume".to_owned()),
1006 )
1007 }
1008
1009 Some(Command::Throttle { mut config }) => {
1010 let mutation = self.database_info.pre_apply_throttle(&mut config);
1011 notify_database_graph = mutation.is_some();
1012 throttle_config = Some(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(partial_graph_manager)
1495 {
1496 if throttle_config
1500 .as_mut()
1501 .and_then(|config| creating_job.pre_apply_throttle(config))
1502 .is_some()
1503 {
1504 notify_database_graph = true;
1505 }
1506 let info = creating_job
1507 .start_consume_upstream(partial_graph_manager, &barrier_info)?;
1508 finished_snapshot_backfill_job_info
1509 .try_insert(job_id, info)
1510 .expect("non-duplicated");
1511 }
1512 }
1513 }
1514
1515 if !finished_snapshot_backfill_job_info.is_empty() {
1516 let actors_to_create = actors_to_create.get_or_insert_default();
1517 let mut subscriptions_to_drop = vec![];
1518 let mut dispatcher_update = vec![];
1519 let mut actor_splits = HashMap::new();
1520 for (job_id, info) in finished_snapshot_backfill_job_info {
1521 finished_snapshot_backfill_jobs.insert(job_id);
1522 subscriptions_to_drop.extend(
1523 info.snapshot_backfill_upstream_tables.iter().map(
1524 |upstream_table_id| PbSubscriptionUpstreamInfo {
1525 subscriber_id: job_id.as_subscriber_id(),
1526 upstream_mv_table_id: *upstream_table_id,
1527 },
1528 ),
1529 );
1530 for upstream_mv_table_id in &info.snapshot_backfill_upstream_tables {
1531 assert_matches!(
1532 self.database_info.unregister_subscriber(
1533 upstream_mv_table_id.as_job_id(),
1534 job_id.as_subscriber_id()
1535 ),
1536 Some(SubscriberType::SnapshotBackfill)
1537 );
1538 }
1539
1540 table_ids_to_commit.extend(
1541 info.fragment_infos
1542 .values()
1543 .flat_map(|fragment| fragment.state_table_ids.iter())
1544 .copied(),
1545 );
1546
1547 let actor_len = info
1548 .fragment_infos
1549 .values()
1550 .map(|fragment| fragment.actors.len() as u64)
1551 .sum();
1552 let id_gen = GlobalActorIdGen::new(
1553 partial_graph_manager
1554 .control_stream_manager()
1555 .env
1556 .actor_id_generator(),
1557 actor_len,
1558 );
1559 let mut next_local_actor_id = 0;
1560 let actor_mapping: HashMap<_, _> = info
1562 .fragment_infos
1563 .values()
1564 .flat_map(|fragment| fragment.actors.keys())
1565 .map(|old_actor_id| {
1566 let new_actor_id = id_gen.to_global_id(next_local_actor_id);
1567 next_local_actor_id += 1;
1568 (*old_actor_id, new_actor_id.as_global_id())
1569 })
1570 .collect();
1571 let actor_mapping = &actor_mapping;
1572 let new_stream_actors: HashMap<_, _> = info
1573 .stream_actors
1574 .into_iter()
1575 .map(|(old_actor_id, mut actor)| {
1576 let new_actor_id = actor_mapping[&old_actor_id];
1577 actor.actor_id = new_actor_id;
1578 (new_actor_id, actor)
1579 })
1580 .collect();
1581 let new_fragment_info: HashMap<_, _> = info
1582 .fragment_infos
1583 .into_iter()
1584 .map(|(fragment_id, mut fragment)| {
1585 let actors = take(&mut fragment.actors);
1586 fragment.actors = actors
1587 .into_iter()
1588 .map(|(old_actor_id, actor)| {
1589 let new_actor_id = actor_mapping[&old_actor_id];
1590 (new_actor_id, actor)
1591 })
1592 .collect();
1593 (fragment_id, fragment)
1594 })
1595 .collect();
1596 actor_splits.extend(
1597 new_fragment_info
1598 .values()
1599 .flat_map(|fragment| &fragment.actors)
1600 .map(|(actor_id, actor)| {
1601 (
1602 *actor_id,
1603 ConnectorSplits {
1604 splits: actor
1605 .splits
1606 .iter()
1607 .map(ConnectorSplit::from)
1608 .collect(),
1609 },
1610 )
1611 }),
1612 );
1613 let partial_graph_id = to_partial_graph_id(self.database_id, None);
1615 let mut edge_builder = FragmentEdgeBuilder::new(
1616 info.upstream_fragment_downstreams
1617 .keys()
1618 .map(|upstream_fragment_id| {
1619 self.database_info.fragment(*upstream_fragment_id)
1620 })
1621 .chain(new_fragment_info.values())
1622 .map(|fragment| {
1623 (
1624 fragment.fragment_id,
1625 EdgeBuilderFragmentInfo::from_inflight(
1626 fragment,
1627 partial_graph_id,
1628 partial_graph_manager.control_stream_manager(),
1629 ),
1630 )
1631 }),
1632 );
1633 edge_builder.add_relations(&info.upstream_fragment_downstreams);
1634 edge_builder.add_relations(&info.downstreams);
1635 let mut edges = edge_builder.build();
1636 let new_actors_to_create = edges.collect_actors_to_create(
1637 new_fragment_info.values().map(|fragment| {
1638 (
1639 fragment.fragment_id,
1640 &fragment.nodes,
1641 fragment.actors.iter().map(|(actor_id, actor)| {
1642 (&new_stream_actors[actor_id], actor.worker_id)
1643 }),
1644 [], )
1646 }),
1647 );
1648 dispatcher_update.extend(
1649 info.upstream_fragment_downstreams.keys().flat_map(
1650 |upstream_fragment_id| {
1651 let new_actor_dispatchers = edges
1652 .dispatchers
1653 .remove(upstream_fragment_id)
1654 .expect("should exist");
1655 new_actor_dispatchers.into_iter().flat_map(
1656 |(upstream_actor_id, dispatchers)| {
1657 dispatchers.into_iter().map(move |dispatcher| {
1658 PbDispatcherUpdate {
1659 actor_id: upstream_actor_id,
1660 dispatcher_id: dispatcher.dispatcher_id,
1661 hash_mapping: dispatcher.hash_mapping,
1662 removed_downstream_actor_id: dispatcher
1663 .downstream_actor_id
1664 .iter()
1665 .map(|new_downstream_actor_id| {
1666 actor_mapping
1667 .iter()
1668 .find_map(
1669 |(old_actor_id, new_actor_id)| {
1670 (new_downstream_actor_id
1671 == new_actor_id)
1672 .then_some(*old_actor_id)
1673 },
1674 )
1675 .expect("should exist")
1676 })
1677 .collect(),
1678 added_downstream_actor_id: dispatcher
1679 .downstream_actor_id,
1680 }
1681 })
1682 },
1683 )
1684 },
1685 ),
1686 );
1687 assert!(edges.is_empty(), "remaining edges: {:?}", edges);
1688 for (worker_id, worker_actors) in new_actors_to_create {
1689 node_actors.entry(worker_id).or_default().extend(
1690 worker_actors.values().flat_map(|(_, actors, _)| {
1691 actors.iter().map(|(actor, _, _)| actor.actor_id)
1692 }),
1693 );
1694 actors_to_create
1695 .entry(worker_id)
1696 .or_default()
1697 .extend(worker_actors);
1698 }
1699 self.database_info.add_existing(InflightStreamingJobInfo {
1700 job_id,
1701 fragment_infos: new_fragment_info,
1702 subscribers: Default::default(), status: CreateStreamingJobStatus::Created,
1704 cdc_table_backfill_tracker: None, });
1706 }
1707
1708 Some(PbMutation::Update(PbUpdateMutation {
1709 dispatcher_update,
1710 merge_update: vec![], actor_vnode_bitmap_update: Default::default(), dropped_actors: vec![], actor_splits,
1714 actor_new_dispatchers: Default::default(), actor_cdc_table_snapshot_splits: None, sink_schema_change: Default::default(), subscriptions_to_drop,
1718 }))
1719 } else {
1720 let fragment_ids = self.database_info.take_pending_backfill_nodes();
1721 if fragment_ids.is_empty() {
1722 None
1723 } else {
1724 Some(PbMutation::StartFragmentBackfill(
1725 PbStartFragmentBackfillMutation { fragment_ids },
1726 ))
1727 }
1728 }
1729 }
1730 };
1731
1732 for (job_id, job) in &mut self.independent_checkpoint_job_controls {
1734 match job {
1735 IndependentCheckpointJobControl::CreatingStreamingJob(creating_job) => {
1736 if finished_snapshot_backfill_jobs.contains(job_id) {
1737 continue;
1738 }
1739 let throttle_mutation = throttle_config.as_mut().and_then(|config| {
1740 creating_job
1741 .pre_apply_throttle(config)
1742 .map(|mutation| (mutation, notifier.as_mut()))
1743 });
1744 creating_job.on_new_upstream_barrier(
1745 partial_graph_manager,
1746 &barrier_info,
1747 throttle_mutation,
1748 )?;
1749 }
1750 IndependentCheckpointJobControl::BatchRefresh(batch_refresh_job) => {
1751 let throttle_mutation = throttle_config.as_mut().and_then(|config| {
1752 batch_refresh_job
1753 .pre_apply_throttle(config)
1754 .map(|mutation| (mutation, notifier.as_mut()))
1755 });
1756 batch_refresh_job.on_new_upstream_barrier(
1757 partial_graph_manager,
1758 &barrier_info,
1759 throttle_mutation,
1760 )?;
1761 }
1762 }
1763 }
1764
1765 let database_notifier = if notify_database_graph {
1766 notifier.as_mut()
1767 } else {
1768 None
1769 };
1770 partial_graph_manager.inject_barrier(
1771 to_partial_graph_id(self.database_id, None),
1772 mutation,
1773 None,
1774 &node_actors,
1775 InflightFragmentInfo::existing_table_ids(self.database_info.fragment_infos()),
1776 InflightFragmentInfo::workers(self.database_info.fragment_infos()),
1777 actors_to_create,
1778 PartialGraphBarrierInfo::new(
1779 post_collect_command,
1780 barrier_info,
1781 database_notifier,
1782 table_ids_to_commit,
1783 ),
1784 )?;
1785
1786 if let Some(notifier) = notifier.take() {
1789 notifier.started();
1790 }
1791
1792 Ok(ApplyCommandInfo {
1793 jobs_to_wait: finished_snapshot_backfill_jobs,
1794 })
1795 }
1796}