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