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