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,
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
532 let (
536 mutation,
537 mut table_ids_to_commit,
538 mut actors_to_create,
539 mut node_actors,
540 post_collect_command,
541 ) = match command {
542 None => self.apply_simple_command(None, "barrier"),
543 Some(Command::CreateStreamingJob {
544 mut info,
545 job_type:
546 CreateStreamingJobType::Independent {
547 mut snapshot_backfill_info,
548 kind: IndependentStreamingJobType::SnapshotBackfill { since_epoch },
549 },
550 cross_db_snapshot_backfill_info,
551 }) => {
552 notify_database_graph = false;
553 let ensembles = resolve_no_shuffle_ensembles(
554 &info.stream_job_fragments,
555 &info.upstream_fragment_downstreams,
556 )?;
557 let actors = render_actors(
558 &info.stream_job_fragments,
559 &self.database_info,
560 &info.definition,
561 &info.stream_job_fragments.inner.ctx,
562 &info.streaming_job_model,
563 partial_graph_manager
564 .control_stream_manager()
565 .env
566 .actor_id_generator(),
567 worker_nodes,
568 &ensembles,
569 &info.database_resource_group,
570 )?;
571 {
572 assert!(!self.state.is_paused());
573 let (snapshot_epoch, since_timestamp_upstream_log_epochs) =
574 if let Some(since_epoch) = &since_epoch {
575 let (snapshot_epoch, log_epochs) =
576 since_epoch.resolved.as_ref().ok_or_else(|| {
577 MetaError::from(anyhow::anyhow!(
578 "since_timestamp epoch has not been resolved for snapshot backfill"
579 ))
580 })?;
581 (
582 *snapshot_epoch,
583 Some((
584 log_epochs,
585 to_partial_graph_id(self.database_id, None),
586 barrier_info.prev_epoch(),
587 )),
588 )
589 } else {
590 (barrier_info.prev_epoch(), None)
591 };
592 for snapshot_backfill_epoch in snapshot_backfill_info
594 .upstream_mv_table_id_to_backfill_epoch
595 .values_mut()
596 {
597 assert_eq!(
598 snapshot_backfill_epoch.replace(snapshot_epoch),
599 None,
600 "must not set previously"
601 );
602 }
603 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
604 fill_snapshot_backfill_epoch(
605 &mut fragment.nodes,
606 Some(&snapshot_backfill_info),
607 &cross_db_snapshot_backfill_info,
608 )?;
609 }
610 let job_id = info.stream_job_fragments.stream_job_id();
611 let snapshot_backfill_upstream_tables = snapshot_backfill_info
612 .upstream_mv_table_id_to_backfill_epoch
613 .keys()
614 .cloned()
615 .collect();
616 let partial_graph_id =
620 to_partial_graph_id(info.streaming_job.database_id(), Some(job_id));
621 let (mut edges, actor_new_no_shuffle) = FragmentEdgeBuilder::new()
622 .add_new_logical_fragments(
623 info.stream_job_fragments
624 .fragments
625 .values()
626 .map(|fragment| (partial_graph_id, fragment)),
627 &actors.stream_actors,
628 &actors.actor_location,
629 partial_graph_manager.control_stream_manager(),
630 )
631 .finish_fragments()
632 .add_relations(&info.stream_job_fragments.downstreams)?
633 .build();
634 let resolved_split_assignment = resolve_source_splits(
636 &info,
637 &actors,
638 &actor_new_no_shuffle,
639 &self.database_info,
640 )?;
641 let Entry::Vacant(entry) =
642 self.independent_checkpoint_job_controls.entry(job_id)
643 else {
644 panic!("duplicated creating snapshot backfill job {job_id}");
645 };
646
647 let term_id = self.term_id.as_str();
648 let job = CreatingStreamingJobControl::new(
649 entry,
650 CreateIndependentStreamingJobCommandInfo {
651 info: info.clone(),
652 snapshot_backfill_info: snapshot_backfill_info.clone(),
653 cross_db_snapshot_backfill_info,
654 resolved_split_assignment: resolved_split_assignment.clone(),
655 kind: IndependentStreamingJobType::SnapshotBackfill {
656 since_epoch: None,
657 },
658 },
659 notifier.as_mut(),
660 snapshot_backfill_upstream_tables,
661 snapshot_epoch,
662 since_timestamp_upstream_log_epochs,
663 hummock_version_stats,
664 term_id,
665 partial_graph_manager,
666 &mut edges,
667 &resolved_split_assignment,
668 &actors,
669 )?;
670
671 if let Some(fragment_infos) = job.fragment_infos() {
672 self.database_info.shared_actor_infos.upsert(
673 self.database_id,
674 fragment_infos.values().map(|f| (f, job_id)),
675 );
676 }
677
678 for upstream_mv_table_id in snapshot_backfill_info
679 .upstream_mv_table_id_to_backfill_epoch
680 .keys()
681 {
682 self.database_info.register_subscriber(
683 upstream_mv_table_id.as_job_id(),
684 info.streaming_job.id().as_subscriber_id(),
685 SubscriberType::SnapshotBackfill,
686 );
687 }
688
689 let create_job_type = CreateStreamingJobType::Independent {
690 snapshot_backfill_info,
691 kind: IndependentStreamingJobType::SnapshotBackfill { since_epoch },
692 };
693 let mutation = Command::create_streaming_job_to_mutation(
694 &info,
695 &create_job_type,
696 [],
697 self.state.is_paused(),
698 edges,
699 None,
700 &resolved_split_assignment,
701 &actors.stream_actors,
702 )?;
703
704 let (table_ids, node_actors) = self.collect_base_info();
705 (
706 Some(mutation),
707 table_ids,
708 None,
709 node_actors,
710 PostCollectCommand::barrier(),
711 )
712 }
713 }
714 Some(Command::CreateStreamingJob {
715 mut info,
716 job_type:
717 CreateStreamingJobType::Independent {
718 mut snapshot_backfill_info,
719 kind: IndependentStreamingJobType::IcebergV3,
720 },
721 cross_db_snapshot_backfill_info,
722 }) => {
723 notify_database_graph = false;
724 assert!(!self.state.is_paused());
725 let snapshot_epoch = barrier_info.prev_epoch();
726 for snapshot_backfill_epoch in snapshot_backfill_info
727 .upstream_mv_table_id_to_backfill_epoch
728 .values_mut()
729 {
730 assert_eq!(
731 snapshot_backfill_epoch.replace(snapshot_epoch),
732 None,
733 "must not set previously"
734 );
735 }
736 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
737 fill_snapshot_backfill_epoch(
738 &mut fragment.nodes,
739 Some(&snapshot_backfill_info),
740 &cross_db_snapshot_backfill_info,
741 )?;
742 }
743
744 let fragments = load_independent_job_fragments(&info.stream_job_fragments);
745 let job_id = info.stream_job_fragments.stream_job_id();
746 let render_result =
747 IcebergV3JobCheckpointControl::render_actors_and_build_job_info(
748 &fragments,
749 &info.stream_job_fragments.downstreams,
750 &info.definition,
751 partial_graph_manager
752 .control_stream_manager()
753 .env
754 .actor_id_generator(),
755 worker_nodes,
756 partial_graph_manager.control_stream_manager(),
757 &info.database_resource_group,
758 &info.streaming_job_model,
759 to_partial_graph_id(self.database_id, Some(job_id)),
760 )?;
761 let snapshot_backfill_upstream_tables = snapshot_backfill_info
762 .upstream_mv_table_id_to_backfill_epoch
763 .keys()
764 .copied()
765 .collect();
766
767 let Entry::Vacant(entry) = self.independent_checkpoint_job_controls.entry(job_id)
768 else {
769 panic!("duplicated creating Iceberg V3 job {job_id}");
770 };
771 let job = IcebergV3JobCheckpointControl::create(
772 entry,
773 CreateIndependentStreamingJobCommandInfo {
774 info: info.clone(),
775 snapshot_backfill_info: snapshot_backfill_info.clone(),
776 cross_db_snapshot_backfill_info,
777 resolved_split_assignment: Default::default(),
778 kind: IndependentStreamingJobType::IcebergV3,
779 },
780 notifier.as_mut(),
781 snapshot_backfill_upstream_tables,
782 snapshot_epoch,
783 hummock_version_stats,
784 self.term_id.as_str(),
785 partial_graph_manager,
786 render_result,
787 )?;
788 self.database_info.shared_actor_infos.upsert(
789 self.database_id,
790 job.fragment_infos()
791 .values()
792 .chain(std::iter::once(job.resolver_fragment_info()))
793 .map(|fragment| (fragment, job_id)),
794 );
795 for upstream_mv_table_id in snapshot_backfill_info
796 .upstream_mv_table_id_to_backfill_epoch
797 .keys()
798 {
799 self.database_info.register_subscriber(
800 upstream_mv_table_id.as_job_id(),
801 info.streaming_job.id().as_subscriber_id(),
802 SubscriberType::SnapshotBackfill,
803 );
804 }
805
806 let subscriber_id = job_id.as_subscriber_id();
807 let mutation = Mutation::Add(AddMutation {
808 actor_dispatchers: Default::default(),
809 added_actors: Default::default(),
810 actor_splits: Default::default(),
811 pause: false,
812 subscriptions_to_add: snapshot_backfill_info
813 .upstream_mv_table_id_to_backfill_epoch
814 .keys()
815 .map(|table_id| PbSubscriptionUpstreamInfo {
816 subscriber_id,
817 upstream_mv_table_id: *table_id,
818 })
819 .collect(),
820 backfill_nodes_to_pause: Default::default(),
821 actor_cdc_table_snapshot_splits: None,
822 new_upstream_sinks: Default::default(),
823 dropped_actors: Default::default(),
824 sink_log_store_flush: Default::default(),
825 });
826 let (table_ids, node_actors) = self.collect_base_info();
827 (
828 Some(mutation),
829 table_ids,
830 None,
831 node_actors,
832 PostCollectCommand::barrier(),
833 )
834 }
835 Some(Command::CreateStreamingJob {
836 mut info,
837 job_type:
838 CreateStreamingJobType::Independent {
839 mut snapshot_backfill_info,
840 kind:
841 IndependentStreamingJobType::BatchRefresh {
842 refresh_interval_sec,
843 },
844 },
845 cross_db_snapshot_backfill_info,
846 }) => {
847 notify_database_graph = false;
848 {
849 if self.state.is_paused() {
850 bail!("cannot create batch refresh job while database barrier is paused");
851 }
852 let snapshot_epoch = barrier_info.prev_epoch();
853 let job_id = info.stream_job_fragments.stream_job_id();
854 let database_id = info.streaming_job.database_id();
855
856 for snapshot_backfill_epoch in snapshot_backfill_info
858 .upstream_mv_table_id_to_backfill_epoch
859 .values_mut()
860 {
861 assert_eq!(
862 snapshot_backfill_epoch.replace(snapshot_epoch),
863 None,
864 "must not set previously"
865 );
866 }
867 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
868 fill_snapshot_backfill_epoch(
869 &mut fragment.nodes,
870 Some(&snapshot_backfill_info),
871 &cross_db_snapshot_backfill_info,
872 )?;
873 }
874 let snapshot_backfill_upstream_tables: HashSet<TableId> =
875 snapshot_backfill_info
876 .upstream_mv_table_id_to_backfill_epoch
877 .keys()
878 .cloned()
879 .collect();
880
881 let logical = BatchRefreshLogicalFragments {
883 fragments: info
884 .stream_job_fragments
885 .inner
886 .fragments
887 .iter()
888 .map(|(&fragment_id, fragment)| {
889 (
890 fragment_id,
891 LoadedFragment {
892 fragment_id,
893 job_id,
894 fragment_type_mask: fragment.fragment_type_mask,
895 distribution_type: fragment.distribution_type.into(),
896 vnode_count: fragment.vnode_count(),
897 nodes: fragment.nodes.clone(),
898 state_table_ids: fragment
899 .state_table_ids
900 .iter()
901 .copied()
902 .collect(),
903 parallelism: None,
904 },
905 )
906 })
907 .collect(),
908 downstreams: info.stream_job_fragments.downstreams.clone(),
909 };
910
911 let term_id = self.term_id.as_str();
915 let Entry::Vacant(entry) =
916 self.independent_checkpoint_job_controls.entry(job_id)
917 else {
918 panic!("duplicated creating batch refresh job {job_id}");
919 };
920
921 let snapshot_backfill_info_clone = snapshot_backfill_info.clone();
922
923 let subscriber_id =
927 info.stream_job_fragments.stream_job_id().as_subscriber_id();
928 let mutation = Mutation::Add(AddMutation {
929 actor_dispatchers: Default::default(),
930 added_actors: Default::default(),
931 actor_splits: Default::default(),
932 pause: false,
933 subscriptions_to_add: snapshot_backfill_info_clone
934 .upstream_mv_table_id_to_backfill_epoch
935 .keys()
936 .map(|table_id| PbSubscriptionUpstreamInfo {
937 subscriber_id,
938 upstream_mv_table_id: *table_id,
939 })
940 .collect(),
941 backfill_nodes_to_pause: Default::default(),
942 actor_cdc_table_snapshot_splits: None,
943 new_upstream_sinks: Default::default(),
944 dropped_actors: Default::default(),
945 sink_log_store_flush: Default::default(),
946 });
947
948 let job = BatchRefreshJobCheckpointControl::new(
949 entry,
950 database_id,
951 job_id,
952 CreateIndependentStreamingJobCommandInfo {
953 info: info.clone(),
954 snapshot_backfill_info: snapshot_backfill_info_clone.clone(),
955 cross_db_snapshot_backfill_info,
956 resolved_split_assignment: Default::default(),
957 kind: IndependentStreamingJobType::BatchRefresh {
958 refresh_interval_sec,
959 },
960 },
961 notifier.as_mut(),
962 snapshot_backfill_upstream_tables,
963 snapshot_epoch,
964 hummock_version_stats,
965 term_id,
966 partial_graph_manager,
967 &logical,
968 worker_nodes,
969 refresh_interval_sec,
970 )?;
971
972 if let Some(fragment_infos) = job.fragment_infos() {
973 self.database_info.shared_actor_infos.upsert(
974 self.database_id,
975 fragment_infos.values().map(|f| (f, job_id)),
976 );
977 }
978
979 for upstream_mv_table_id in snapshot_backfill_info_clone
981 .upstream_mv_table_id_to_backfill_epoch
982 .keys()
983 {
984 self.database_info.register_subscriber(
985 upstream_mv_table_id.as_job_id(),
986 info.streaming_job.id().as_subscriber_id(),
987 SubscriberType::SnapshotBackfill,
988 );
989 }
990
991 let (table_ids, node_actors) = self.collect_base_info();
992 (
993 Some(mutation),
994 table_ids,
995 None,
996 node_actors,
997 PostCollectCommand::barrier(),
998 )
999 }
1000 }
1001 Some(Command::CreateStreamingJob {
1002 mut info,
1003 job_type:
1004 job_type @ (CreateStreamingJobType::Normal
1005 | CreateStreamingJobType::SinkIntoTable(_)),
1006 cross_db_snapshot_backfill_info,
1007 }) => {
1008 let ensembles = resolve_no_shuffle_ensembles(
1009 &info.stream_job_fragments,
1010 &info.upstream_fragment_downstreams,
1011 )?;
1012 let actors = render_actors(
1013 &info.stream_job_fragments,
1014 &self.database_info,
1015 &info.definition,
1016 &info.stream_job_fragments.inner.ctx,
1017 &info.streaming_job_model,
1018 partial_graph_manager
1019 .control_stream_manager()
1020 .env
1021 .actor_id_generator(),
1022 worker_nodes,
1023 &ensembles,
1024 &info.database_resource_group,
1025 )?;
1026 for fragment in info.stream_job_fragments.inner.fragments.values_mut() {
1027 fill_snapshot_backfill_epoch(
1028 &mut fragment.nodes,
1029 None,
1030 &cross_db_snapshot_backfill_info,
1031 )?;
1032 }
1033
1034 let new_upstream_sink =
1036 if let CreateStreamingJobType::SinkIntoTable(ref ctx) = job_type {
1037 Some(ctx)
1038 } else {
1039 None
1040 };
1041
1042 let (mut edges, actor_new_no_shuffle) = self.database_info.build_edge(
1043 Some(&info),
1044 None,
1045 new_upstream_sink,
1046 partial_graph_manager.control_stream_manager(),
1047 &actors.stream_actors,
1048 &actors.actor_location,
1049 )?;
1050 let resolved_split_assignment = resolve_source_splits(
1052 &info,
1053 &actors,
1054 &actor_new_no_shuffle,
1055 &self.database_info,
1056 )?;
1057
1058 let old_sink_job_id = info
1059 .replace_sink
1060 .as_ref()
1061 .map(|old_sink_id| old_sink_id.as_job_id());
1062 let cdc_tracker = if let Some(splits) = &info.cdc_table_snapshot_splits {
1064 let (fragment, _) =
1065 parallel_cdc_table_backfill_fragment(info.stream_job_fragments.fragments())
1066 .expect("should have parallel cdc fragment");
1067 Some(CdcTableBackfillTracker::new(
1068 fragment.fragment_id,
1069 splits.clone(),
1070 ))
1071 } else {
1072 None
1073 };
1074 self.database_info
1075 .pre_apply_new_job(info.streaming_job.id(), cdc_tracker);
1076 self.database_info.pre_apply_new_fragments(
1077 info.stream_job_fragments
1078 .new_fragment_info(
1079 &actors.stream_actors,
1080 &actors.actor_location,
1081 &resolved_split_assignment,
1082 )
1083 .map(|(fragment_id, fragment_infos)| {
1084 (fragment_id, info.streaming_job.id(), fragment_infos)
1085 }),
1086 );
1087 if let CreateStreamingJobType::SinkIntoTable(ref ctx) = job_type {
1088 let downstream_fragment_id = ctx.new_sink_downstream.downstream_fragment_id;
1089 self.database_info.pre_apply_add_node_upstream(
1090 downstream_fragment_id,
1091 &PbUpstreamSinkInfo {
1092 upstream_fragment_id: ctx.sink_fragment_id,
1093 sink_output_schema: ctx.sink_output_fields.clone(),
1094 project_exprs: ctx.project_exprs.clone(),
1095 },
1096 );
1097 }
1098
1099 let (table_ids, node_actors) = self.collect_base_info();
1100 let dropped_actors = if let Some(old_sink_job_id) = old_sink_job_id {
1101 let Some(job) = self.database_info.post_apply_remove_job(old_sink_job_id)
1102 else {
1103 bail!(
1104 "old sink job {} not found in barrier state",
1105 old_sink_job_id
1106 );
1107 };
1108 job.fragment_infos
1109 .values()
1110 .flat_map(|fragment| fragment.actors.keys().copied())
1111 .collect()
1112 } else {
1113 vec![]
1114 };
1115
1116 let actors_to_create = Some(Command::create_streaming_job_actors_to_create(
1118 &info,
1119 &mut edges,
1120 &actors.stream_actors,
1121 &actors.actor_location,
1122 ));
1123
1124 let actor_cdc_table_snapshot_splits = self
1126 .database_info
1127 .assign_cdc_backfill_splits(info.stream_job_fragments.stream_job_id())?;
1128
1129 let is_currently_paused = self.state.is_paused();
1131 let mutation = Command::create_streaming_job_to_mutation(
1132 &info,
1133 &job_type,
1134 dropped_actors,
1135 is_currently_paused,
1136 edges,
1137 actor_cdc_table_snapshot_splits,
1138 &resolved_split_assignment,
1139 &actors.stream_actors,
1140 )?;
1141
1142 (
1143 Some(mutation),
1144 table_ids,
1145 actors_to_create,
1146 node_actors,
1147 PostCollectCommand::CreateStreamingJob {
1148 info,
1149 job_type,
1150 cross_db_snapshot_backfill_info,
1151 resolved_split_assignment,
1152 },
1153 )
1154 }
1155
1156 Some(Command::Flush) => self.apply_simple_command(None, "Flush"),
1157
1158 Some(Command::Pause) => {
1159 let prev_is_paused = self.state.is_paused();
1160 self.state.set_is_paused(true);
1161 let mutation = Command::pause_to_mutation(prev_is_paused);
1162 let (table_ids, node_actors) = self.collect_base_info();
1163 (
1164 mutation,
1165 table_ids,
1166 None,
1167 node_actors,
1168 PostCollectCommand::Command("Pause".to_owned()),
1169 )
1170 }
1171
1172 Some(Command::Resume) => {
1173 let prev_is_paused = self.state.is_paused();
1174 self.state.set_is_paused(false);
1175 let mutation = Command::resume_to_mutation(prev_is_paused);
1176 let (table_ids, node_actors) = self.collect_base_info();
1177 (
1178 mutation,
1179 table_ids,
1180 None,
1181 node_actors,
1182 PostCollectCommand::Command("Resume".to_owned()),
1183 )
1184 }
1185
1186 Some(Command::Throttle { mut config }) => {
1187 let mutation = self.database_info.pre_apply_throttle(&mut config);
1188 notify_database_graph = mutation.is_some();
1189 throttle_config = Some(config);
1190 self.apply_simple_command(mutation, "Throttle")
1191 }
1192
1193 Some(Command::DropStreamingJobs {
1194 streaming_job_ids,
1195 unregistered_state_table_ids: _,
1196 dropped_sink_fragment_by_targets,
1197 }) => {
1198 for (target_fragment, sink_fragments) in &dropped_sink_fragment_by_targets {
1200 self.database_info
1201 .pre_apply_drop_node_upstream(*target_fragment, sink_fragments);
1202 }
1203
1204 let (table_ids, node_actors) = self.collect_base_info();
1205
1206 let mut actors = Vec::new();
1207 for job_id in streaming_job_ids {
1208 let Some(job) = self.database_info.post_apply_remove_job(job_id) else {
1209 warn!(
1210 %job_id,
1211 "skip drop payload for streaming job that has already been removed from barrier worker"
1212 );
1213 continue;
1214 };
1215
1216 for fragment in job.fragment_infos.values() {
1217 actors.extend(fragment.actors.keys().copied());
1218 }
1219 }
1220
1221 let mutation = Some(Command::drop_streaming_jobs_to_mutation(
1222 &actors,
1223 &dropped_sink_fragment_by_targets,
1224 ));
1225 (
1226 mutation,
1227 table_ids,
1228 None,
1229 node_actors,
1230 PostCollectCommand::DropStreamingJobs,
1231 )
1232 }
1233
1234 Some(Command::RescheduleIntent {
1235 reschedule_plan, ..
1236 }) => {
1237 let ReschedulePlan {
1238 reschedules,
1239 fragment_actors,
1240 } = reschedule_plan
1241 .as_ref()
1242 .expect("reschedule intent should be resolved in global barrier worker");
1243
1244 for (fragment_id, reschedule) in reschedules {
1246 self.database_info.pre_apply_reschedule(
1247 *fragment_id,
1248 reschedule
1249 .added_actors
1250 .iter()
1251 .flat_map(|(node_id, actors): (&WorkerId, &Vec<ActorId>)| {
1252 actors.iter().map(|actor_id| {
1253 (
1254 *actor_id,
1255 InflightActorInfo {
1256 worker_id: *node_id,
1257 vnode_bitmap: reschedule
1258 .newly_created_actors
1259 .get(actor_id)
1260 .expect("should exist")
1261 .0
1262 .0
1263 .vnode_bitmap
1264 .clone(),
1265 splits: reschedule
1266 .actor_splits
1267 .get(actor_id)
1268 .cloned()
1269 .unwrap_or_default(),
1270 },
1271 )
1272 })
1273 })
1274 .collect(),
1275 reschedule
1276 .vnode_bitmap_updates
1277 .iter()
1278 .filter(|(actor_id, _)| {
1279 !reschedule.newly_created_actors.contains_key(*actor_id)
1280 })
1281 .map(|(actor_id, bitmap)| (*actor_id, bitmap.clone()))
1282 .collect(),
1283 reschedule.actor_splits.clone(),
1284 );
1285 }
1286
1287 let (table_ids, node_actors) = self.collect_base_info();
1288
1289 let actors_to_create = Some(Command::reschedule_actors_to_create(
1291 reschedules,
1292 fragment_actors,
1293 &self.database_info,
1294 partial_graph_manager.control_stream_manager(),
1295 ));
1296
1297 self.database_info
1299 .post_apply_reschedules(reschedules.iter().map(|(fragment_id, reschedule)| {
1300 (
1301 *fragment_id,
1302 reschedule.removed_actors.iter().cloned().collect(),
1303 )
1304 }));
1305
1306 let mutation = Command::reschedule_to_mutation(
1308 reschedules,
1309 fragment_actors,
1310 partial_graph_manager.control_stream_manager(),
1311 &mut self.database_info,
1312 )?;
1313
1314 let reschedules = reschedule_plan
1315 .expect("reschedule intent should be resolved in global barrier worker")
1316 .reschedules;
1317 (
1318 mutation,
1319 table_ids,
1320 actors_to_create,
1321 node_actors,
1322 PostCollectCommand::Reschedule { reschedules },
1323 )
1324 }
1325
1326 Some(Command::ReplaceStreamJob(plan)) => {
1327 let ensembles = resolve_no_shuffle_ensembles(
1328 &plan.new_fragments,
1329 &plan.upstream_fragment_downstreams,
1330 )?;
1331 let mut render_result = render_actors(
1332 &plan.new_fragments,
1333 &self.database_info,
1334 "", &plan.new_fragments.inner.ctx,
1336 &plan.streaming_job_model,
1337 partial_graph_manager
1338 .control_stream_manager()
1339 .env
1340 .actor_id_generator(),
1341 worker_nodes,
1342 &ensembles,
1343 &plan.database_resource_group,
1344 )?;
1345
1346 if let Some(sinks) = &plan.auto_refresh_schema_sinks {
1349 let actor_id_counter = partial_graph_manager
1350 .control_stream_manager()
1351 .env
1352 .actor_id_generator();
1353 for sink_ctx in sinks {
1354 let original_fragment_id = sink_ctx.original_fragment.fragment_id;
1355 let original_frag_info = self.database_info.fragment(original_fragment_id);
1356 let actor_template = EnsembleActorTemplate::from_existing_inflight_fragment(
1357 original_frag_info,
1358 );
1359 let new_aligner = ComponentFragmentAligner::new_persistent(
1360 &actor_template,
1361 actor_id_counter,
1362 );
1363 let distribution_type: DistributionType =
1364 sink_ctx.new_fragment.distribution_type.into();
1365 let actor_assignments =
1366 new_aligner.align_component_actor(distribution_type);
1367 let new_fragment_id = sink_ctx.new_fragment.fragment_id;
1368 let mut actors = Vec::with_capacity(actor_assignments.len());
1369 for (&actor_id, (worker_id, vnode_bitmap)) in &actor_assignments {
1370 render_result.actor_location.insert(actor_id, *worker_id);
1371 actors.push(StreamActor {
1372 actor_id,
1373 fragment_id: new_fragment_id,
1374 vnode_bitmap: vnode_bitmap.clone(),
1375 mview_definition: String::new(),
1376 expr_context: Some(sink_ctx.ctx.to_expr_context()),
1377 config_override: sink_ctx.ctx.config_override.clone(),
1378 });
1379 }
1380 render_result.stream_actors.insert(new_fragment_id, actors);
1381 }
1382 }
1383
1384 let (mut edges, actor_new_no_shuffle) = self.database_info.build_edge(
1386 None,
1387 Some(&plan),
1388 None,
1389 partial_graph_manager.control_stream_manager(),
1390 &render_result.stream_actors,
1391 &render_result.actor_location,
1392 )?;
1393
1394 let fragment_actor_ids: HashMap<FragmentId, Vec<ActorId>> = render_result
1396 .stream_actors
1397 .iter()
1398 .map(|(fragment_id, actors)| {
1399 (
1400 *fragment_id,
1401 actors.iter().map(|a| a.actor_id).collect::<Vec<_>>(),
1402 )
1403 })
1404 .collect();
1405 let resolved_split_assignment = match &plan.split_plan {
1406 ReplaceJobSplitPlan::Discovered(discovered) => {
1407 SourceManager::resolve_fragment_to_actor_splits(
1408 &plan.new_fragments,
1409 discovered,
1410 &fragment_actor_ids,
1411 )?
1412 }
1413 ReplaceJobSplitPlan::AlignFromPrevious => {
1414 SourceManager::resolve_replace_source_splits(
1415 &plan.new_fragments,
1416 &plan.replace_upstream,
1417 &actor_new_no_shuffle,
1418 |_fragment_id, actor_id| {
1419 self.database_info.fragment_infos().find_map(|fragment| {
1420 fragment
1421 .actors
1422 .get(&actor_id)
1423 .map(|info| info.splits.clone())
1424 })
1425 },
1426 )?
1427 }
1428 };
1429
1430 self.database_info.pre_apply_new_fragments(
1432 plan.new_fragments
1433 .new_fragment_info(
1434 &render_result.stream_actors,
1435 &render_result.actor_location,
1436 &resolved_split_assignment,
1437 )
1438 .map(|(fragment_id, new_fragment)| {
1439 (fragment_id, plan.streaming_job.id(), new_fragment)
1440 }),
1441 );
1442 for (fragment_id, replace_map) in &plan.replace_upstream {
1443 self.database_info
1444 .pre_apply_replace_node_upstream(*fragment_id, replace_map);
1445 }
1446 if let Some(sinks) = &plan.auto_refresh_schema_sinks {
1447 self.database_info
1448 .pre_apply_new_fragments(sinks.iter().map(|sink| {
1449 (
1450 sink.new_fragment.fragment_id,
1451 sink.original_sink.id.as_job_id(),
1452 sink.new_fragment_info(
1453 &render_result.stream_actors,
1454 &render_result.actor_location,
1455 ),
1456 )
1457 }));
1458 }
1459
1460 let (table_ids, node_actors) = self.collect_base_info();
1461
1462 let actors_to_create = Some(Command::replace_stream_job_actors_to_create(
1464 &plan,
1465 &mut edges,
1466 &self.database_info,
1467 &render_result.stream_actors,
1468 &render_result.actor_location,
1469 ));
1470
1471 let mutation = Some(Command::replace_stream_job_to_mutation(
1474 &plan,
1475 edges,
1476 &mut self.database_info,
1477 &resolved_split_assignment,
1478 )?);
1479
1480 {
1482 let mut fragment_ids_to_remove: Vec<_> = plan
1483 .old_fragments
1484 .fragments
1485 .values()
1486 .map(|f| f.fragment_id)
1487 .collect();
1488 if let Some(sinks) = &plan.auto_refresh_schema_sinks {
1489 fragment_ids_to_remove
1490 .extend(sinks.iter().map(|sink| sink.original_fragment.fragment_id));
1491 }
1492 self.database_info
1493 .post_apply_remove_fragments(fragment_ids_to_remove);
1494 }
1495
1496 (
1497 mutation,
1498 table_ids,
1499 actors_to_create,
1500 node_actors,
1501 PostCollectCommand::ReplaceStreamJob {
1502 plan,
1503 resolved_split_assignment,
1504 },
1505 )
1506 }
1507
1508 Some(Command::SourceChangeSplit(split_state)) => {
1509 self.database_info.pre_apply_split_assignments(
1511 split_state
1512 .split_assignment
1513 .iter()
1514 .map(|(&fragment_id, splits)| (fragment_id, splits.clone())),
1515 );
1516
1517 let mutation = Some(Command::source_change_split_to_mutation(
1518 &split_state.split_assignment,
1519 ));
1520 let (table_ids, node_actors) = self.collect_base_info();
1521 (
1522 mutation,
1523 table_ids,
1524 None,
1525 node_actors,
1526 PostCollectCommand::SourceChangeSplit {
1527 split_assignment: split_state.split_assignment,
1528 },
1529 )
1530 }
1531
1532 Some(Command::CreateSubscription {
1533 subscription_id,
1534 upstream_mv_table_id,
1535 retention_second,
1536 }) => {
1537 self.database_info.register_subscriber(
1538 upstream_mv_table_id.as_job_id(),
1539 subscription_id.as_subscriber_id(),
1540 SubscriberType::Subscription(retention_second),
1541 );
1542 let mutation = Some(Command::create_subscription_to_mutation(
1543 upstream_mv_table_id,
1544 subscription_id,
1545 ));
1546 let (table_ids, node_actors) = self.collect_base_info();
1547 (
1548 mutation,
1549 table_ids,
1550 None,
1551 node_actors,
1552 PostCollectCommand::CreateSubscription { subscription_id },
1553 )
1554 }
1555
1556 Some(Command::DropSubscription {
1557 subscription_id,
1558 upstream_mv_table_id,
1559 }) => {
1560 if self
1561 .database_info
1562 .unregister_subscriber(
1563 upstream_mv_table_id.as_job_id(),
1564 subscription_id.as_subscriber_id(),
1565 )
1566 .is_none()
1567 {
1568 warn!(%subscription_id, %upstream_mv_table_id, "no subscription to drop");
1569 }
1570 let mutation = Some(Command::drop_subscription_to_mutation(
1571 upstream_mv_table_id,
1572 subscription_id,
1573 ));
1574 let (table_ids, node_actors) = self.collect_base_info();
1575 (
1576 mutation,
1577 table_ids,
1578 None,
1579 node_actors,
1580 PostCollectCommand::Command("DropSubscription".to_owned()),
1581 )
1582 }
1583
1584 Some(Command::AlterSubscriptionRetention {
1585 subscription_id,
1586 upstream_mv_table_id,
1587 retention_second,
1588 }) => {
1589 self.database_info.update_subscription_retention(
1590 upstream_mv_table_id.as_job_id(),
1591 subscription_id.as_subscriber_id(),
1592 retention_second,
1593 );
1594 self.apply_simple_command(None, "AlterSubscriptionRetention")
1595 }
1596
1597 Some(Command::ConnectorPropsChange(config)) => {
1598 let mutation = Some(Command::connector_props_change_to_mutation(&config));
1599 let (table_ids, node_actors) = self.collect_base_info();
1600 (
1601 mutation,
1602 table_ids,
1603 None,
1604 node_actors,
1605 PostCollectCommand::ConnectorPropsChange(config),
1606 )
1607 }
1608
1609 Some(Command::Refresh {
1610 table_id,
1611 associated_source_id,
1612 staging_table_id,
1613 trigger_time,
1614 }) => {
1615 let mutation = Some(Command::refresh_to_mutation(table_id, associated_source_id));
1616 let actors = RefreshCycleActors::from_fragments(
1617 self.database_info.job_fragment_infos(table_id.as_job_id()),
1618 );
1619 let (table_ids, node_actors) = self.collect_base_info();
1620 (
1621 mutation,
1622 table_ids,
1623 None,
1624 node_actors,
1625 PostCollectCommand::RefreshStarted {
1626 table_id,
1627 database_id: self.database_info.database_id,
1628 associated_source_id,
1629 staging_table_id,
1630 trigger_time,
1631 actors,
1632 },
1633 )
1634 }
1635
1636 Some(Command::ListFinish {
1637 table_id: _,
1638 associated_source_id,
1639 }) => {
1640 let mutation = Some(Command::list_finish_to_mutation(associated_source_id));
1641 self.apply_simple_command(mutation, "ListFinish")
1642 }
1643
1644 Some(Command::LoadFinish {
1645 table_id: _,
1646 associated_source_id,
1647 }) => {
1648 let mutation = Some(Command::load_finish_to_mutation(associated_source_id));
1649 self.apply_simple_command(mutation, "LoadFinish")
1650 }
1651
1652 Some(Command::FinishRefresh {
1653 table_id,
1654 staging_table_id,
1655 trigger_time,
1656 }) => {
1657 let (table_ids, node_actors) = self.collect_base_info();
1658 (
1659 None,
1660 table_ids,
1661 None,
1662 node_actors,
1663 PostCollectCommand::FinishRefresh {
1664 table_id,
1665 staging_table_id,
1666 trigger_time,
1667 },
1668 )
1669 }
1670
1671 Some(Command::ResetSource { source_id }) => {
1672 let mutation = Some(Command::reset_source_to_mutation(source_id));
1673 self.apply_simple_command(mutation, "ResetSource")
1674 }
1675
1676 Some(Command::ResumeBackfill { target }) => {
1677 let mutation = Command::resume_backfill_to_mutation(&target, &self.database_info)?;
1678 let (table_ids, node_actors) = self.collect_base_info();
1679 (
1680 mutation,
1681 table_ids,
1682 None,
1683 node_actors,
1684 PostCollectCommand::ResumeBackfill { target },
1685 )
1686 }
1687
1688 Some(Command::InjectSourceOffsets {
1689 source_id,
1690 split_offsets,
1691 }) => {
1692 let mutation = Some(Command::inject_source_offsets_to_mutation(
1693 source_id,
1694 &split_offsets,
1695 ));
1696 self.apply_simple_command(mutation, "InjectSourceOffsets")
1697 }
1698 };
1699
1700 let mut independent_job_throttle_mutations = HashMap::new();
1701 if let Some(config) = throttle_config.as_mut() {
1702 for (&job_id, job) in &mut self.independent_checkpoint_job_controls {
1703 let Some(job) = job.running_mut() else {
1704 continue;
1705 };
1706 let mutation = job.pre_apply_throttle(config);
1707 if let Some(mutation) = mutation {
1708 independent_job_throttle_mutations
1709 .try_insert(job_id, mutation)
1710 .expect("one throttle mutation per independent job");
1711 }
1712 }
1713 }
1714
1715 let mut finished_snapshot_backfill_jobs = HashSet::new();
1716 let mut started_consuming_upstream_jobs = HashSet::new();
1717 let mut mutation = match mutation {
1718 Some(mutation) => Some(mutation),
1719 None => {
1720 let mut finished_snapshot_backfill_job_info = HashMap::new();
1721 let mut update_mutation: Option<PbUpdateMutation> = None;
1722 if barrier_info.kind.is_checkpoint() {
1723 for (&job_id, job) in &mut self.independent_checkpoint_job_controls {
1724 let Some(IndependentCheckpointJob::CreatingStreamingJob(creating_job)) =
1725 job.running_mut()
1726 else {
1727 continue;
1728 };
1729 if independent_job_throttle_mutations.contains_key(&job_id) {
1730 continue;
1733 }
1734 if creating_job.is_consuming_upstream() {
1735 let info = creating_job
1736 .start_finishing(partial_graph_manager, &barrier_info)?;
1737 finished_snapshot_backfill_job_info
1738 .try_insert(job_id, info)
1739 .expect("non-duplicated");
1740 } else if creating_job.should_start_consume_upstream(partial_graph_manager)
1741 {
1742 let info = creating_job.start_consume_upstream(&barrier_info);
1743 let database_partial_graph_id =
1744 to_partial_graph_id(self.database_id, None);
1745 let job_partial_graph_id =
1746 to_partial_graph_id(self.database_id, Some(job_id));
1747 let mut edge_builder = FragmentEdgeBuilder::new()
1748 .add_existing_fragments(
1749 info.upstream_fragment_downstreams.keys().map(
1750 |upstream_fragment_id| {
1751 self.database_info.fragment(*upstream_fragment_id)
1752 },
1753 ),
1754 database_partial_graph_id,
1755 partial_graph_manager.control_stream_manager(),
1756 )
1757 .add_existing_fragments(
1758 info.fragment_infos.values(),
1759 job_partial_graph_id,
1760 partial_graph_manager.control_stream_manager(),
1761 )
1762 .finish_fragments();
1763 for (upstream_fragment_id, downstreams) in
1764 &info.upstream_fragment_downstreams
1765 {
1766 for downstream in downstreams {
1767 edge_builder =
1768 edge_builder.add_edge(*upstream_fragment_id, downstream)?;
1769 }
1770 }
1771 let (edges, _) = edge_builder.build();
1772 edges.apply_to_update_mutation(update_mutation.get_or_insert_default());
1773 started_consuming_upstream_jobs.insert(job_id);
1774 }
1775 }
1776 }
1777
1778 if !finished_snapshot_backfill_job_info.is_empty() {
1779 let mutation = update_mutation.get_or_insert_default();
1780 for (job_id, info) in finished_snapshot_backfill_job_info {
1781 let actors_to_create = actors_to_create.get_or_insert_default();
1782 finished_snapshot_backfill_jobs.insert(job_id);
1783 mutation.subscriptions_to_drop.extend(
1784 info.snapshot_backfill_upstream_tables.iter().map(
1785 |upstream_table_id| PbSubscriptionUpstreamInfo {
1786 subscriber_id: job_id.as_subscriber_id(),
1787 upstream_mv_table_id: *upstream_table_id,
1788 },
1789 ),
1790 );
1791 for upstream_mv_table_id in &info.snapshot_backfill_upstream_tables {
1792 assert_matches!(
1793 self.database_info.unregister_subscriber(
1794 upstream_mv_table_id.as_job_id(),
1795 job_id.as_subscriber_id()
1796 ),
1797 Some(SubscriberType::SnapshotBackfill)
1798 );
1799 }
1800
1801 table_ids_to_commit.extend(
1802 info.fragment_infos
1803 .values()
1804 .flat_map(|fragment| fragment.state_table_ids.iter())
1805 .copied(),
1806 );
1807
1808 let actor_len = info
1809 .fragment_infos
1810 .values()
1811 .map(|fragment| fragment.actors.len() as u64)
1812 .sum();
1813 let id_gen = GlobalActorIdGen::new(
1814 partial_graph_manager
1815 .control_stream_manager()
1816 .env
1817 .actor_id_generator(),
1818 actor_len,
1819 );
1820 let mut next_local_actor_id = 0;
1821 let actor_mapping: HashMap<_, _> = info
1823 .fragment_infos
1824 .values()
1825 .flat_map(|fragment| fragment.actors.keys())
1826 .map(|old_actor_id| {
1827 let new_actor_id = id_gen.to_global_id(next_local_actor_id);
1828 next_local_actor_id += 1;
1829 (*old_actor_id, new_actor_id.as_global_id())
1830 })
1831 .collect();
1832 let actor_mapping = &actor_mapping;
1833 let database_partial_graph_id = to_partial_graph_id(self.database_id, None);
1836 let mut edge_builder = FragmentEdgeBuilder::new()
1837 .add_existing_fragments(
1838 info.upstream_fragment_downstreams.keys().map(
1839 |upstream_fragment_id| {
1840 self.database_info.fragment(*upstream_fragment_id)
1841 },
1842 ),
1843 database_partial_graph_id,
1844 partial_graph_manager.control_stream_manager(),
1845 )
1846 .add_existing_fragments(
1847 info.fragment_infos.values(),
1848 to_partial_graph_id(self.database_id, Some(job_id)),
1849 partial_graph_manager.control_stream_manager(),
1850 );
1851 let new_stream_actors: HashMap<_, _> = info
1852 .stream_actors
1853 .into_iter()
1854 .map(|(old_actor_id, mut actor)| {
1855 let new_actor_id = actor_mapping[&old_actor_id];
1856 actor.actor_id = new_actor_id;
1857 (new_actor_id, actor)
1858 })
1859 .collect();
1860 let new_fragment_info: HashMap<_, _> = info
1861 .fragment_infos
1862 .into_iter()
1863 .map(|(fragment_id, mut fragment)| {
1864 let actors = take(&mut fragment.actors);
1865 fragment.actors = actors
1866 .into_iter()
1867 .map(|(old_actor_id, actor)| {
1868 let new_actor_id = actor_mapping[&old_actor_id];
1869 (new_actor_id, actor)
1870 })
1871 .collect();
1872 (fragment_id, fragment)
1873 })
1874 .collect();
1875 mutation.actor_splits.extend(
1876 new_fragment_info
1877 .values()
1878 .flat_map(|fragment| &fragment.actors)
1879 .map(|(actor_id, actor)| {
1880 (
1881 *actor_id,
1882 ConnectorSplits {
1883 splits: actor
1884 .splits
1885 .iter()
1886 .map(ConnectorSplit::from)
1887 .collect(),
1888 },
1889 )
1890 }),
1891 );
1892 edge_builder = edge_builder.replace_existing_fragment_actors(
1894 new_fragment_info.values(),
1895 database_partial_graph_id,
1896 partial_graph_manager.control_stream_manager(),
1897 );
1898 let (mut edges, _) = edge_builder
1899 .finish_fragments()
1900 .add_relations(&info.upstream_fragment_downstreams)?
1901 .add_relations(&info.downstreams)?
1902 .build();
1903 let new_actors_to_create = edges.collect_actors_to_create(
1904 new_fragment_info.values().map(|fragment| {
1905 (
1906 fragment.fragment_id,
1907 &fragment.nodes,
1908 fragment.actors.iter().map(|(actor_id, actor)| {
1909 (&new_stream_actors[actor_id], actor.worker_id)
1910 }),
1911 [], )
1913 }),
1914 );
1915 edges.apply_to_update_mutation(mutation);
1916 for (worker_id, worker_actors) in new_actors_to_create {
1917 node_actors.entry(worker_id).or_default().extend(
1918 worker_actors.values().flat_map(|(_, actors, _)| {
1919 actors.iter().map(|(actor, _, _)| actor.actor_id)
1920 }),
1921 );
1922 actors_to_create
1923 .entry(worker_id)
1924 .or_default()
1925 .extend(worker_actors);
1926 }
1927 self.database_info.add_existing(InflightStreamingJobInfo {
1928 job_id,
1929 fragment_infos: new_fragment_info,
1930 subscribers: Default::default(), status: CreateStreamingJobStatus::Created,
1932 cdc_table_backfill_tracker: None, });
1934 }
1935 }
1936
1937 if let Some(mutation) = update_mutation {
1938 Some(PbMutation::Update(mutation))
1939 } else {
1940 let fragment_ids = self.database_info.take_pending_backfill_nodes();
1941 if fragment_ids.is_empty() {
1942 None
1943 } else {
1944 Some(PbMutation::StartFragmentBackfill(
1945 PbStartFragmentBackfillMutation { fragment_ids },
1946 ))
1947 }
1948 }
1949 }
1950 };
1951
1952 if matches!(
1953 mutation,
1954 None | Some(PbMutation::Update(_)) | Some(PbMutation::DropSubscriptions(_))
1955 ) && !self
1956 .pending_independent_job_subscriptions_to_drop
1957 .is_empty()
1958 {
1959 let subscriptions_to_drop = self.take_pending_independent_job_subscriptions_to_drop();
1960 if !subscriptions_to_drop.is_empty() {
1961 match &mut mutation {
1962 None => {
1963 mutation =
1964 Some(PbMutation::DropSubscriptions(PbDropSubscriptionsMutation {
1965 info: subscriptions_to_drop,
1966 }));
1967 }
1968 Some(PbMutation::Update(update)) => {
1969 update.subscriptions_to_drop.extend(subscriptions_to_drop);
1970 }
1971 Some(PbMutation::DropSubscriptions(drop_subscriptions)) => {
1972 drop_subscriptions.info.extend(subscriptions_to_drop);
1973 }
1974 Some(_) => unreachable!("checked compatible mutation above"),
1975 }
1976 }
1977 }
1978
1979 for (job_id, job) in &mut self.independent_checkpoint_job_controls {
1981 let Some(job) = job.running_mut() else {
1982 continue;
1983 };
1984 if finished_snapshot_backfill_jobs.contains(job_id)
1985 && matches!(job, IndependentCheckpointJob::CreatingStreamingJob(_))
1986 {
1987 debug_assert!(!independent_job_throttle_mutations.contains_key(job_id));
1988 } else {
1989 let job_mutation = if started_consuming_upstream_jobs.contains(job_id) {
1990 debug_assert!(!independent_job_throttle_mutations.contains_key(job_id));
1991 Some((
1992 mutation
1993 .clone()
1994 .expect("transition should have an update mutation"),
1995 None,
1996 ))
1997 } else {
1998 independent_job_throttle_mutations
1999 .remove(job_id)
2000 .map(|mutation| (mutation, notifier.as_mut()))
2001 };
2002 job.on_new_upstream_barrier(partial_graph_manager, &barrier_info, job_mutation)?;
2003 }
2004 }
2005 debug_assert!(independent_job_throttle_mutations.is_empty());
2006
2007 let database_notifier = if notify_database_graph {
2008 notifier.as_mut()
2009 } else {
2010 None
2011 };
2012 partial_graph_manager.inject_barrier(
2013 to_partial_graph_id(self.database_id, None),
2014 mutation,
2015 &node_actors,
2016 InflightFragmentInfo::existing_table_ids(self.database_info.fragment_infos()),
2017 InflightFragmentInfo::workers(self.database_info.fragment_infos()),
2018 actors_to_create,
2019 PartialGraphBarrierInfo::new(
2020 post_collect_command,
2021 barrier_info,
2022 database_notifier,
2023 table_ids_to_commit,
2024 ),
2025 )?;
2026
2027 if let Some(notifier) = notifier.take() {
2030 notifier.started();
2031 }
2032
2033 if !started_consuming_upstream_jobs.is_empty() {
2034 periodic_barriers.force_checkpoint_in_next_barrier(self.database_id);
2035 }
2036
2037 Ok(ApplyCommandInfo {
2038 jobs_to_wait: finished_snapshot_backfill_jobs,
2039 })
2040 }
2041}