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