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