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