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