1mod barrier_control;
16mod status;
17
18use std::cmp::max;
19use std::collections::{HashMap, HashSet, hash_map};
20use std::mem::take;
21use std::ops::Bound::{Excluded, Unbounded};
22use std::time::Duration;
23
24use risingwave_common::catalog::{DatabaseId, TableId};
25use risingwave_common::id::JobId;
26use risingwave_common::metrics::LabelGuardedIntGauge;
27use risingwave_common::util::epoch::Epoch;
28use risingwave_meta_model::WorkerId;
29use risingwave_pb::ddl_service::PbBackfillType;
30use risingwave_pb::hummock::HummockVersionStats;
31use risingwave_pb::id::{ActorId, FragmentId, PartialGraphId};
32use risingwave_pb::stream_plan::barrier::PbBarrierKind;
33use risingwave_pb::stream_plan::barrier_mutation::Mutation;
34use risingwave_pb::stream_plan::{AddMutation, StopMutation};
35use risingwave_pb::stream_service::BarrierCompleteResponse;
36use status::CreatingStreamingJobStatus;
37use tracing::{debug, info};
38
39use super::super::state::RenderResult;
40use super::IndependentCheckpointJobControl;
41use crate::MetaResult;
42use crate::barrier::backfill_order_control::get_nodes_with_backfill_dependencies;
43use crate::barrier::checkpoint::independent_job::creating_job::barrier_control::CreatingStreamingJobBarrierStats;
44use crate::barrier::checkpoint::independent_job::creating_job::status::CreateMviewLogStoreProgressTracker;
45use crate::barrier::command::{PostCollectCommand, TableLogEpochs, UpstreamTableLogEpochs};
46use crate::barrier::context::CreateSnapshotBackfillJobCommandInfo;
47use crate::barrier::edge_builder::FragmentEdgeBuildResult;
48use crate::barrier::info::{BarrierInfo, InflightStreamingJobInfo};
49use crate::barrier::notifier::Notifier;
50use crate::barrier::partial_graph::{
51 CollectedBarrier, PartialGraphBarrierInfo, PartialGraphManager, PartialGraphRecoverer,
52};
53use crate::barrier::progress::{CreateMviewProgressTracker, TrackingJob, collect_done_fragments};
54use crate::barrier::rpc::{build_locality_fragment_state_table_mapping, to_partial_graph_id};
55use crate::barrier::{
56 BackfillOrderState, BackfillProgress, BarrierKind, Command, FragmentBackfillProgress,
57 TracedEpoch,
58};
59use crate::controller::fragment::InflightFragmentInfo;
60use crate::model::{FragmentDownstreamRelation, StreamActor, StreamJobActorsToCreate};
61use crate::rpc::metrics::GLOBAL_META_METRICS;
62use crate::stream::source_manager::SplitAssignment;
63use crate::stream::{ExtendedFragmentBackfillOrder, build_actor_connector_splits};
64
65#[derive(Debug)]
66pub(crate) struct CreatingJobInfo {
67 pub fragment_infos: HashMap<FragmentId, InflightFragmentInfo>,
68 pub upstream_fragment_downstreams: FragmentDownstreamRelation,
69 pub downstreams: FragmentDownstreamRelation,
70 pub snapshot_backfill_upstream_tables: HashSet<TableId>,
71 pub stream_actors: HashMap<ActorId, StreamActor>,
72}
73
74#[derive(Debug)]
75pub(crate) struct CreatingStreamingJobControl {
76 job_id: JobId,
77 partial_graph_id: PartialGraphId,
78 snapshot_backfill_upstream_tables: HashSet<TableId>,
79 snapshot_epoch: u64,
80
81 node_actors: HashMap<WorkerId, HashSet<ActorId>>,
82 state_table_ids: HashSet<TableId>,
83
84 max_committed_epoch: Option<u64>,
85 status: CreatingStreamingJobStatus,
86 max_lagged_barrier_num: usize,
87
88 upstream_lag: LabelGuardedIntGauge,
89}
90
91impl CreatingStreamingJobControl {
92 #[expect(clippy::too_many_arguments)]
93 pub(crate) fn new<'a>(
94 entry: hash_map::VacantEntry<'a, JobId, IndependentCheckpointJobControl>,
95 create_info: CreateSnapshotBackfillJobCommandInfo,
96 notifiers: Vec<Notifier>,
97 snapshot_backfill_upstream_tables: HashSet<TableId>,
98 snapshot_epoch: u64,
99 since_timestamp_upstream_log_epochs: Option<(&TableLogEpochs, PartialGraphId, u64)>,
100 version_stat: &HummockVersionStats,
101 partial_graph_manager: &mut PartialGraphManager,
102 edges: &mut FragmentEdgeBuildResult,
103 split_assignment: &SplitAssignment,
104 actors: &RenderResult,
105 ) -> MetaResult<&'a mut Self> {
106 let info = create_info.info.clone();
107 let job_id = info.stream_job_fragments.stream_job_id();
108 let database_id = info.streaming_job.database_id();
109 debug!(
110 %job_id,
111 definition = info.definition,
112 "new creating job"
113 );
114 let fragment_infos = info
115 .stream_job_fragments
116 .new_fragment_info(
117 &actors.stream_actors,
118 &actors.actor_location,
119 split_assignment,
120 )
121 .collect();
122 let snapshot_backfill_actors: HashSet<ActorId> =
123 InflightStreamingJobInfo::snapshot_backfill_actor_ids(&fragment_infos).collect();
124 let backfill_nodes_to_pause =
125 get_nodes_with_backfill_dependencies(&info.fragment_backfill_ordering)
126 .into_iter()
127 .collect();
128 let backfill_order_state = BackfillOrderState::new(
129 &info.fragment_backfill_ordering,
130 &fragment_infos,
131 info.locality_fragment_state_table_mapping.clone(),
132 );
133 let create_mview_tracker = CreateMviewProgressTracker::recover(
134 job_id,
135 &fragment_infos,
136 backfill_order_state,
137 version_stat,
138 );
139
140 let actors_to_create = Command::create_streaming_job_actors_to_create(
141 &info,
142 edges,
143 &actors.stream_actors,
144 &actors.actor_location,
145 );
146
147 let mut prev_epoch_fake_physical_time = 0;
148 let mut pending_non_checkpoint_barriers = vec![];
149
150 let (initial_barrier_info, log_store_barriers_to_inject) = if let Some((
151 upstream_log_epochs,
152 upstream_partial_graph_id,
153 new_upstream_barrier_prev_epoch,
154 )) =
155 since_timestamp_upstream_log_epochs
156 {
157 let (initial_barrier, barriers_to_inject) =
158 Self::resolve_since_timestamp_upstream_log_epochs(
159 upstream_log_epochs,
160 partial_graph_manager.pending_barrier_infos(upstream_partial_graph_id),
161 snapshot_epoch,
162 new_upstream_barrier_prev_epoch,
163 )?;
164 (initial_barrier, Some(barriers_to_inject))
165 } else {
166 (
167 CreatingStreamingJobStatus::new_fake_barrier(
168 &mut prev_epoch_fake_physical_time,
169 &mut pending_non_checkpoint_barriers,
170 PbBarrierKind::Checkpoint,
171 ),
172 None,
173 )
174 };
175
176 let added_actors: Vec<ActorId> = actors
177 .stream_actors
178 .values()
179 .flatten()
180 .map(|actor| actor.actor_id)
181 .collect();
182 let actor_splits = split_assignment
183 .values()
184 .flat_map(build_actor_connector_splits)
185 .collect();
186
187 assert!(
188 info.cdc_table_snapshot_splits.is_none(),
189 "should not have cdc backfill for snapshot backfill job"
190 );
191
192 let initial_mutation = Mutation::Add(AddMutation {
193 actor_dispatchers: Default::default(),
195 added_actors,
196 actor_splits,
197 pause: false,
199 subscriptions_to_add: Default::default(),
200 backfill_nodes_to_pause,
201 actor_cdc_table_snapshot_splits: None,
202 new_upstream_sinks: Default::default(),
203 dropped_actors: Default::default(),
204 sink_log_store_flush: Default::default(),
205 });
206
207 let node_actors = InflightFragmentInfo::actor_ids_to_collect(fragment_infos.values());
208 let state_table_ids =
209 InflightFragmentInfo::existing_table_ids(fragment_infos.values()).collect();
210
211 let partial_graph_id = to_partial_graph_id(database_id, Some(job_id));
212 let max_lagged_barrier_num = partial_graph_manager
213 .control_stream_manager()
214 .env
215 .opts
216 .snapshot_backfill_finish_max_lagged_barriers;
217
218 let IndependentCheckpointJobControl::CreatingStreamingJob(job) = entry.insert(
219 IndependentCheckpointJobControl::CreatingStreamingJob(Self {
220 partial_graph_id,
221 job_id,
222 snapshot_backfill_upstream_tables,
223 max_committed_epoch: None,
224 snapshot_epoch,
225 status: CreatingStreamingJobStatus::PlaceHolder, max_lagged_barrier_num,
227 upstream_lag: GLOBAL_META_METRICS
228 .snapshot_backfill_lag
229 .with_guarded_label_values(&[&format!("{}", job_id)]),
230 node_actors,
231 state_table_ids,
232 }),
233 ) else {
234 unreachable!()
235 };
236
237 let mut graph_adder = partial_graph_manager.add_partial_graph(
238 partial_graph_id,
239 CreatingStreamingJobBarrierStats::new(job_id, snapshot_epoch),
240 );
241
242 if let Err(e) = Self::inject_barrier(
243 partial_graph_id,
244 graph_adder.manager(),
245 &job.node_actors,
246 &job.state_table_ids,
247 false,
248 initial_barrier_info,
249 Some(actors_to_create),
250 Some(initial_mutation),
251 notifiers,
252 Some(create_info),
253 ) {
254 graph_adder.failed();
255 job.status = CreatingStreamingJobStatus::Resetting(vec![]);
256 Err(e)
257 } else {
258 graph_adder.added();
259 let job_info = CreatingJobInfo {
260 fragment_infos,
261 upstream_fragment_downstreams: info.upstream_fragment_downstreams.clone(),
262 downstreams: info.stream_job_fragments.downstreams,
263 snapshot_backfill_upstream_tables: job.snapshot_backfill_upstream_tables.clone(),
264 stream_actors: actors
265 .stream_actors
266 .values()
267 .flatten()
268 .map(|actor| (actor.actor_id, actor.clone()))
269 .collect(),
270 };
271 if let Some(log_store_barriers_to_inject) = log_store_barriers_to_inject {
272 let upstream_lag = log_store_barriers_to_inject
273 .last()
274 .map(|info| info.prev_epoch().saturating_sub(snapshot_epoch))
275 .unwrap_or(0);
276 job.status = CreatingStreamingJobStatus::ConsumingLogStore {
277 tracking_job: TrackingJob::recovered(job_id, &job_info.fragment_infos),
278 info: job_info,
279 log_store_progress_tracker: CreateMviewLogStoreProgressTracker::new(
280 snapshot_backfill_actors.iter().cloned(),
281 upstream_lag,
282 ),
283 barriers_to_inject: Some(log_store_barriers_to_inject),
284 };
285 } else {
286 assert!(pending_non_checkpoint_barriers.is_empty());
287 job.status = CreatingStreamingJobStatus::ConsumingSnapshot {
288 prev_epoch_fake_physical_time,
289 pending_upstream_barriers: vec![],
290 version_stats: version_stat.clone(),
291 create_mview_tracker,
292 snapshot_backfill_actors,
293 snapshot_epoch,
294 info: job_info,
295 pending_non_checkpoint_barriers,
296 };
297 };
298 Ok(job)
299 }
300 }
301
302 pub(super) fn gen_fragment_backfill_progress(&self) -> Vec<FragmentBackfillProgress> {
303 match &self.status {
304 CreatingStreamingJobStatus::ConsumingSnapshot {
305 create_mview_tracker,
306 info,
307 ..
308 } => create_mview_tracker.collect_fragment_progress(&info.fragment_infos, true),
309 CreatingStreamingJobStatus::ConsumingLogStore { info, .. } => {
310 collect_done_fragments(self.job_id, &info.fragment_infos)
311 }
312 CreatingStreamingJobStatus::Finishing(_, _)
313 | CreatingStreamingJobStatus::Resetting(_)
314 | CreatingStreamingJobStatus::PlaceHolder => vec![],
315 }
316 }
317
318 fn resolve_upstream_log_epochs(
319 snapshot_backfill_upstream_tables: &HashSet<TableId>,
320 upstream_table_log_epochs: &UpstreamTableLogEpochs,
321 exclusive_start_log_epoch: u64,
322 upstream_barrier_info: &BarrierInfo,
323 ) -> MetaResult<Vec<BarrierInfo>> {
324 let table_id = snapshot_backfill_upstream_tables
325 .iter()
326 .next()
327 .expect("snapshot backfill job should have upstream");
328 let epochs_iter = if let Some(epochs) = upstream_table_log_epochs.get(table_id) {
329 let mut epochs_iter = epochs.iter();
330 loop {
331 let (_, checkpoint_epoch) =
332 epochs_iter.next().expect("not reach committed epoch yet");
333 if *checkpoint_epoch < exclusive_start_log_epoch {
334 continue;
335 }
336 assert_eq!(*checkpoint_epoch, exclusive_start_log_epoch);
337 break;
338 }
339 epochs_iter
340 } else {
341 assert_eq!(
343 upstream_barrier_info.prev_epoch(),
344 exclusive_start_log_epoch
345 );
346 static EMPTY_VEC: Vec<(Vec<u64>, u64)> = Vec::new();
347 EMPTY_VEC.iter()
348 };
349
350 let mut ret = vec![];
351 let mut prev_epoch = exclusive_start_log_epoch;
352 let mut pending_non_checkpoint_barriers = vec![];
353 for (non_checkpoint_epochs, checkpoint_epoch) in epochs_iter {
354 for (i, epoch) in non_checkpoint_epochs
355 .iter()
356 .chain([checkpoint_epoch])
357 .enumerate()
358 {
359 assert!(*epoch > prev_epoch);
360 pending_non_checkpoint_barriers.push(prev_epoch);
361 ret.push(BarrierInfo {
362 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
363 curr_epoch: TracedEpoch::new(Epoch(*epoch)),
364 kind: if i == 0 {
365 BarrierKind::Checkpoint(take(&mut pending_non_checkpoint_barriers))
366 } else {
367 BarrierKind::Barrier
368 },
369 });
370 prev_epoch = *epoch;
371 }
372 }
373 ret.push(BarrierInfo {
374 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
375 curr_epoch: TracedEpoch::new(Epoch(upstream_barrier_info.curr_epoch())),
376 kind: BarrierKind::Checkpoint(pending_non_checkpoint_barriers),
377 });
378 Ok(ret)
379 }
380
381 fn resolve_since_timestamp_upstream_log_epochs(
410 upstream_log_epochs: &TableLogEpochs,
411 pending_upstream_barriers: impl Iterator<Item = &BarrierInfo>,
412 snapshot_epoch: u64,
413 new_upstream_barrier_prev_epoch: u64,
414 ) -> MetaResult<(BarrierInfo, Vec<BarrierInfo>)> {
415 let mut initial_barrier = None;
416 let mut barriers = vec![];
417 fn emit_barrier(
418 initial_barrier: &mut Option<BarrierInfo>,
419 barriers: &mut Vec<BarrierInfo>,
420 barrier: BarrierInfo,
421 ) {
422 if initial_barrier.is_none() {
423 *initial_barrier = Some(barrier);
424 } else {
425 barriers.push(barrier);
426 }
427 }
428
429 let mut prev_epoch = snapshot_epoch;
430 let mut pending_non_checkpoint_barriers = vec![];
431 for (non_checkpoint_epochs, checkpoint_epoch) in upstream_log_epochs {
432 for (i, epoch) in non_checkpoint_epochs
433 .iter()
434 .chain([checkpoint_epoch])
435 .enumerate()
436 {
437 assert!(
438 *epoch > prev_epoch,
439 "changelog epochs should be strictly increasing"
440 );
441 pending_non_checkpoint_barriers.push(prev_epoch);
442 emit_barrier(
443 &mut initial_barrier,
444 &mut barriers,
445 BarrierInfo {
446 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
447 curr_epoch: TracedEpoch::new(Epoch(*epoch)),
448 kind: if i == 0 {
449 BarrierKind::Checkpoint(take(&mut pending_non_checkpoint_barriers))
450 } else {
451 BarrierKind::Barrier
452 },
453 },
454 );
455 prev_epoch = *epoch;
456 }
457 }
458
459 let mut pending_upstream_barriers = pending_upstream_barriers.peekable();
460 if pending_upstream_barriers.peek().is_none() {
461 assert!(
462 new_upstream_barrier_prev_epoch > prev_epoch,
463 "new upstream barrier prev epoch should be newer than the latest changelog epoch"
464 );
465 emit_barrier(
466 &mut initial_barrier,
467 &mut barriers,
468 BarrierInfo {
469 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
470 curr_epoch: TracedEpoch::new(Epoch(new_upstream_barrier_prev_epoch)),
471 kind: BarrierKind::Checkpoint(pending_non_checkpoint_barriers),
472 },
473 );
474 } else {
475 for (index, pending_barrier) in pending_upstream_barriers.enumerate() {
476 assert_eq!(
477 pending_barrier.prev_epoch(),
478 prev_epoch,
479 "pending upstream barriers should continue from resolved changelog epochs"
480 );
481 if index == 0 {
482 assert!(
483 pending_barrier.kind.is_checkpoint(),
484 "first pending upstream barrier should checkpoint from the latest resolved changelog epoch"
485 );
486 }
487 pending_non_checkpoint_barriers.push(prev_epoch);
488 emit_barrier(
489 &mut initial_barrier,
490 &mut barriers,
491 BarrierInfo {
492 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
493 curr_epoch: TracedEpoch::new(Epoch(pending_barrier.curr_epoch())),
494 kind: if pending_barrier.kind.is_checkpoint() {
495 BarrierKind::Checkpoint(take(&mut pending_non_checkpoint_barriers))
496 } else {
497 BarrierKind::Barrier
498 },
499 },
500 );
501 prev_epoch = pending_barrier.curr_epoch();
502 }
503 assert_eq!(
504 new_upstream_barrier_prev_epoch, prev_epoch,
505 "new upstream barrier prev epoch should match the latest pending log-store epoch"
506 );
507 }
508 let Some(initial_barrier) = initial_barrier else {
509 return Err(anyhow::anyhow!(
510 "missing lagging barriers for direct log-store start from snapshot epoch {}",
511 snapshot_epoch
512 )
513 .into());
514 };
515 assert!(initial_barrier.kind.is_checkpoint());
516 Ok((initial_barrier, barriers))
517 }
518
519 fn recover_consuming_snapshot(
520 job_id: JobId,
521 upstream_table_log_epochs: &UpstreamTableLogEpochs,
522 snapshot_epoch: u64,
523 committed_epoch: u64,
524 upstream_barrier_info: &BarrierInfo,
525 info: CreatingJobInfo,
526 backfill_order_state: BackfillOrderState,
527 version_stat: &HummockVersionStats,
528 ) -> MetaResult<(CreatingStreamingJobStatus, BarrierInfo)> {
529 let mut prev_epoch_fake_physical_time = Epoch(committed_epoch).physical_time();
530 let mut pending_non_checkpoint_barriers = vec![];
531 let create_mview_tracker = CreateMviewProgressTracker::recover(
532 job_id,
533 &info.fragment_infos,
534 backfill_order_state,
535 version_stat,
536 );
537 let barrier_info = CreatingStreamingJobStatus::new_fake_barrier(
538 &mut prev_epoch_fake_physical_time,
539 &mut pending_non_checkpoint_barriers,
540 PbBarrierKind::Initial,
541 );
542
543 Ok((
544 CreatingStreamingJobStatus::ConsumingSnapshot {
545 prev_epoch_fake_physical_time,
546 pending_upstream_barriers: Self::resolve_upstream_log_epochs(
547 &info.snapshot_backfill_upstream_tables,
548 upstream_table_log_epochs,
549 snapshot_epoch,
550 upstream_barrier_info,
551 )?,
552 version_stats: version_stat.clone(),
553 create_mview_tracker,
554 snapshot_backfill_actors: InflightStreamingJobInfo::snapshot_backfill_actor_ids(
555 &info.fragment_infos,
556 )
557 .collect(),
558 info,
559 snapshot_epoch,
560 pending_non_checkpoint_barriers,
561 },
562 barrier_info,
563 ))
564 }
565
566 fn recover_consuming_log_store(
567 job_id: JobId,
568 upstream_table_log_epochs: &UpstreamTableLogEpochs,
569 committed_epoch: u64,
570 upstream_barrier_info: &BarrierInfo,
571 info: CreatingJobInfo,
572 ) -> MetaResult<(CreatingStreamingJobStatus, BarrierInfo)> {
573 let mut barriers_to_inject = Self::resolve_upstream_log_epochs(
574 &info.snapshot_backfill_upstream_tables,
575 upstream_table_log_epochs,
576 committed_epoch,
577 upstream_barrier_info,
578 )?;
579 let mut first_barrier = barriers_to_inject.remove(0);
580 assert!(first_barrier.kind.is_checkpoint());
581 first_barrier.kind = BarrierKind::Initial;
582
583 Ok((
584 CreatingStreamingJobStatus::ConsumingLogStore {
585 tracking_job: TrackingJob::recovered(job_id, &info.fragment_infos),
586 log_store_progress_tracker: CreateMviewLogStoreProgressTracker::new(
587 InflightStreamingJobInfo::snapshot_backfill_actor_ids(&info.fragment_infos),
588 barriers_to_inject
589 .last()
590 .map(|info| info.prev_epoch() - committed_epoch)
591 .unwrap_or(0),
592 ),
593 barriers_to_inject: Some(barriers_to_inject),
594 info,
595 },
596 first_barrier,
597 ))
598 }
599
600 #[expect(clippy::too_many_arguments)]
601 pub(crate) fn recover(
602 database_id: DatabaseId,
603 job_id: JobId,
604 snapshot_backfill_upstream_tables: HashSet<TableId>,
605 upstream_table_log_epochs: &UpstreamTableLogEpochs,
606 snapshot_epoch: u64,
607 committed_epoch: u64,
608 upstream_barrier_info: &BarrierInfo,
609 fragment_infos: HashMap<FragmentId, InflightFragmentInfo>,
610 backfill_order: ExtendedFragmentBackfillOrder,
611 fragment_relations: &FragmentDownstreamRelation,
612 version_stat: &HummockVersionStats,
613 new_actors: StreamJobActorsToCreate,
614 initial_mutation: Mutation,
615 partial_graph_recoverer: &mut PartialGraphRecoverer<'_>,
616 ) -> MetaResult<Self> {
617 info!(
618 %job_id,
619 "recovered creating snapshot backfill job"
620 );
621
622 let node_actors = InflightFragmentInfo::actor_ids_to_collect(fragment_infos.values());
623 let state_table_ids: HashSet<_> =
624 InflightFragmentInfo::existing_table_ids(fragment_infos.values()).collect();
625
626 let mut upstream_fragment_downstreams: FragmentDownstreamRelation = Default::default();
627 for (upstream_fragment_id, downstreams) in fragment_relations {
628 if fragment_infos.contains_key(upstream_fragment_id) {
629 continue;
630 }
631 for downstream in downstreams {
632 if fragment_infos.contains_key(&downstream.downstream_fragment_id) {
633 upstream_fragment_downstreams
634 .entry(*upstream_fragment_id)
635 .or_default()
636 .push(downstream.clone());
637 }
638 }
639 }
640 let downstreams = fragment_infos
641 .keys()
642 .filter_map(|fragment_id| {
643 fragment_relations
644 .get(fragment_id)
645 .map(|relation| (*fragment_id, relation.clone()))
646 })
647 .collect();
648
649 let info = CreatingJobInfo {
650 fragment_infos,
651 upstream_fragment_downstreams,
652 downstreams,
653 snapshot_backfill_upstream_tables: snapshot_backfill_upstream_tables.clone(),
654 stream_actors: new_actors
655 .values()
656 .flat_map(|fragments| {
657 fragments.values().flat_map(|(_, actors, _)| {
658 actors
659 .iter()
660 .map(|(actor, _, _)| (actor.actor_id, actor.clone()))
661 })
662 })
663 .collect(),
664 };
665
666 let (status, first_barrier_info) = if committed_epoch < snapshot_epoch {
667 let locality_fragment_state_table_mapping =
668 build_locality_fragment_state_table_mapping(&info.fragment_infos);
669 let backfill_order_state = BackfillOrderState::recover_from_fragment_infos(
670 &backfill_order,
671 &info.fragment_infos,
672 locality_fragment_state_table_mapping,
673 );
674 Self::recover_consuming_snapshot(
675 job_id,
676 upstream_table_log_epochs,
677 snapshot_epoch,
678 committed_epoch,
679 upstream_barrier_info,
680 info,
681 backfill_order_state,
682 version_stat,
683 )?
684 } else {
685 Self::recover_consuming_log_store(
686 job_id,
687 upstream_table_log_epochs,
688 committed_epoch,
689 upstream_barrier_info,
690 info,
691 )?
692 };
693
694 let partial_graph_id = to_partial_graph_id(database_id, Some(job_id));
695 let max_lagged_barrier_num = partial_graph_recoverer
696 .control_stream_manager()
697 .env
698 .opts
699 .snapshot_backfill_finish_max_lagged_barriers;
700
701 partial_graph_recoverer.recover_graph(
702 partial_graph_id,
703 initial_mutation,
704 &first_barrier_info,
705 &node_actors,
706 state_table_ids.iter().copied(),
707 new_actors,
708 CreatingStreamingJobBarrierStats::new(job_id, snapshot_epoch),
709 )?;
710
711 Ok(Self {
712 job_id,
713 partial_graph_id,
714 snapshot_backfill_upstream_tables,
715 snapshot_epoch,
716 node_actors,
717 state_table_ids,
718 max_committed_epoch: Some(committed_epoch),
719 status,
720 max_lagged_barrier_num,
721 upstream_lag: GLOBAL_META_METRICS
722 .snapshot_backfill_lag
723 .with_guarded_label_values(&[&format!("{}", job_id)]),
724 })
725 }
726
727 pub(crate) fn gen_backfill_progress(&self) -> BackfillProgress {
728 let progress = match &self.status {
729 CreatingStreamingJobStatus::ConsumingSnapshot {
730 create_mview_tracker,
731 ..
732 } => {
733 if create_mview_tracker.is_finished() {
734 "Snapshot finished".to_owned()
735 } else {
736 let progress = create_mview_tracker.gen_backfill_progress();
737 format!("Snapshot [{}]", progress)
738 }
739 }
740 CreatingStreamingJobStatus::ConsumingLogStore {
741 log_store_progress_tracker,
742 ..
743 } => {
744 format!(
745 "LogStore [{}]",
746 log_store_progress_tracker.gen_backfill_progress()
747 )
748 }
749 CreatingStreamingJobStatus::Finishing(finish_epoch, ..) => {
750 let committed_epoch = self.max_committed_epoch.expect("should have committed");
751 let lag = Duration::from_millis(
752 Epoch(*finish_epoch).physical_time() - Epoch(committed_epoch).physical_time(),
753 );
754 format!("Finishing [epoch lag: {lag:?}]",)
755 }
756 CreatingStreamingJobStatus::Resetting(_) => "Resetting".to_owned(),
757 CreatingStreamingJobStatus::PlaceHolder => {
758 unreachable!()
759 }
760 };
761 BackfillProgress {
762 progress,
763 backfill_type: PbBackfillType::SnapshotBackfill,
764 }
765 }
766
767 pub(super) fn pinned_upstream_log_epoch(&self) -> (u64, HashSet<TableId>) {
768 (
769 max(self.max_committed_epoch.unwrap_or(0), self.snapshot_epoch),
770 self.snapshot_backfill_upstream_tables.clone(),
771 )
772 }
773
774 fn inject_barrier(
775 partial_graph_id: PartialGraphId,
776 partial_graph_manager: &mut PartialGraphManager,
777 node_actors: &HashMap<WorkerId, HashSet<ActorId>>,
778 state_table_ids: &HashSet<TableId>,
779 is_finishing: bool,
780 barrier_info: BarrierInfo,
781 new_actors: Option<StreamJobActorsToCreate>,
782 mutation: Option<Mutation>,
783 notifiers: Vec<Notifier>,
784 first_create_info: Option<CreateSnapshotBackfillJobCommandInfo>,
785 ) -> MetaResult<()> {
786 let (table_ids_to_sync, nodes_to_sync_table) = if !is_finishing {
787 (Some(state_table_ids), Some(node_actors.keys().copied()))
788 } else {
789 (None, None)
790 };
791 partial_graph_manager.inject_barrier(
792 partial_graph_id,
793 mutation,
794 node_actors,
795 table_ids_to_sync.into_iter().flatten().copied(),
796 nodes_to_sync_table.into_iter().flatten(),
797 new_actors,
798 PartialGraphBarrierInfo::new(
799 first_create_info.map_or_else(
800 PostCollectCommand::barrier,
801 CreateSnapshotBackfillJobCommandInfo::into_post_collect,
802 ),
803 barrier_info,
804 notifiers,
805 state_table_ids.clone(),
806 ),
807 )?;
808 Ok(())
809 }
810
811 pub(crate) fn start_consume_upstream(
812 &mut self,
813 partial_graph_manager: &mut PartialGraphManager,
814 barrier_info: &BarrierInfo,
815 ) -> MetaResult<CreatingJobInfo> {
816 info!(
817 job_id = %self.job_id,
818 prev_epoch = barrier_info.prev_epoch(),
819 "start consuming upstream"
820 );
821 let info = self.status.start_consume_upstream(barrier_info);
822 Self::inject_barrier(
823 self.partial_graph_id,
824 partial_graph_manager,
825 &self.node_actors,
826 &self.state_table_ids,
827 true,
828 barrier_info.clone(),
829 None,
830 Some(Mutation::Stop(StopMutation {
831 actors: info
833 .fragment_infos
834 .values()
835 .flat_map(|info| info.actors.keys().copied())
836 .collect(),
837 dropped_sink_fragments: vec![], })),
839 vec![], None,
841 )?;
842 Ok(info)
843 }
844
845 pub(crate) fn on_new_upstream_barrier(
846 &mut self,
847 partial_graph_manager: &mut PartialGraphManager,
848 barrier_info: &BarrierInfo,
849 mutation: Option<(Mutation, Vec<Notifier>)>,
850 ) -> MetaResult<()> {
851 let progress_epoch = if let Some(max_committed_epoch) = self.max_committed_epoch {
852 max(max_committed_epoch, self.snapshot_epoch)
853 } else {
854 self.snapshot_epoch
855 };
856 self.upstream_lag.set(
857 barrier_info
858 .prev_epoch
859 .value()
860 .0
861 .saturating_sub(progress_epoch) as _,
862 );
863 let (mut mutation, mut notifiers) = match mutation {
864 Some((mutation, notifiers)) => (Some(mutation), notifiers),
865 None => (None, vec![]),
866 };
867 {
868 for (barrier_to_inject, mutation) in self
869 .status
870 .on_new_upstream_epoch(barrier_info, mutation.take())
871 {
872 Self::inject_barrier(
873 self.partial_graph_id,
874 partial_graph_manager,
875 &self.node_actors,
876 &self.state_table_ids,
877 false,
878 barrier_to_inject,
879 None,
880 mutation,
881 take(&mut notifiers),
882 None,
883 )?;
884 }
885 assert!(mutation.is_none(), "must have consumed mutation");
886 assert!(notifiers.is_empty(), "must consumed notifiers");
887 }
888 Ok(())
889 }
890
891 pub(crate) fn collect(&mut self, collected_barrier: CollectedBarrier<'_>) -> bool {
893 let pending_barrier_num = collected_barrier.pending_barrier_num;
894 self.status.update_progress(
895 collected_barrier
896 .resps
897 .values()
898 .flat_map(|resp| &resp.create_mview_progress),
899 );
900 self.is_ready_to_merge() && pending_barrier_num <= self.max_lagged_barrier_num
901 }
902
903 fn is_ready_to_merge(&self) -> bool {
904 if let CreatingStreamingJobStatus::ConsumingLogStore {
905 log_store_progress_tracker,
906 barriers_to_inject,
907 ..
908 } = &self.status
909 && barriers_to_inject.is_none()
910 && log_store_progress_tracker.is_finished()
911 {
912 true
913 } else {
914 false
915 }
916 }
917
918 pub(crate) fn should_merge_to_upstream(
919 &self,
920 partial_graph_manager: &PartialGraphManager,
921 ) -> bool {
922 if !self.is_ready_to_merge() {
923 return false;
924 }
925
926 partial_graph_manager.pending_barrier_num(self.partial_graph_id)
929 <= self.max_lagged_barrier_num
930 }
931}
932
933impl CreatingStreamingJobControl {
934 pub(crate) fn start_completing(
935 &mut self,
936 partial_graph_manager: &mut PartialGraphManager,
937 min_upstream_inflight_epoch: Option<u64>,
938 upstream_committed_epoch: u64,
939 ) -> Option<(
940 u64,
941 HashMap<WorkerId, BarrierCompleteResponse>,
942 PartialGraphBarrierInfo,
943 bool,
944 )> {
945 if upstream_committed_epoch < self.snapshot_epoch {
947 return None;
948 }
949 let (finished_at_epoch, epoch_end_bound) = match &self.status {
950 CreatingStreamingJobStatus::Finishing(finish_at_epoch, _) => {
951 let epoch_end_bound = min_upstream_inflight_epoch
952 .map(|upstream_epoch| {
953 if upstream_epoch < *finish_at_epoch {
954 Excluded(upstream_epoch)
955 } else {
956 Unbounded
957 }
958 })
959 .unwrap_or(Unbounded);
960 (Some(*finish_at_epoch), epoch_end_bound)
961 }
962 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
963 | CreatingStreamingJobStatus::ConsumingLogStore { .. } => (
964 None,
965 min_upstream_inflight_epoch
966 .map(Excluded)
967 .unwrap_or(Unbounded),
968 ),
969 CreatingStreamingJobStatus::Resetting(..) => {
970 return None;
971 }
972 CreatingStreamingJobStatus::PlaceHolder => {
973 unreachable!()
974 }
975 };
976 partial_graph_manager
977 .start_completing(
978 self.partial_graph_id,
979 epoch_end_bound,
980 |non_checkpoint_epoch, _, _| {
981 if let Some(finish_at_epoch) = finished_at_epoch {
982 assert!(non_checkpoint_epoch.prev < finish_at_epoch);
983 }
984 },
985 )
986 .map(|(epoch, resps, info)| {
987 let is_finish_epoch = if let Some(finish_at_epoch) = finished_at_epoch {
988 assert!(!info.post_collect_command.should_checkpoint());
989 if epoch == finish_at_epoch {
990 self.ack_completed(partial_graph_manager, epoch);
992 true
993 } else {
994 false
995 }
996 } else {
997 false
998 };
999 (epoch, resps, info, is_finish_epoch)
1000 })
1001 }
1002
1003 pub(super) fn ack_completed(
1004 &mut self,
1005 partial_graph_manager: &mut PartialGraphManager,
1006 completed_epoch: u64,
1007 ) {
1008 match &self.status {
1009 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1010 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1011 | CreatingStreamingJobStatus::Finishing(_, _) => {
1012 partial_graph_manager.ack_completed(self.partial_graph_id, completed_epoch);
1013 if let Some(prev_max_committed_epoch) =
1014 self.max_committed_epoch.replace(completed_epoch)
1015 {
1016 assert!(completed_epoch > prev_max_committed_epoch);
1017 }
1018 }
1019 CreatingStreamingJobStatus::Resetting(_) => {
1020 }
1023 CreatingStreamingJobStatus::PlaceHolder => {
1024 unreachable!()
1025 }
1026 }
1027 }
1028
1029 pub(crate) fn fragment_infos(&self) -> Option<&HashMap<FragmentId, InflightFragmentInfo>> {
1030 self.status.fragment_infos()
1031 }
1032
1033 pub fn into_tracking_job(self) -> TrackingJob {
1034 match self.status {
1035 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1036 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1037 | CreatingStreamingJobStatus::Resetting(..)
1038 | CreatingStreamingJobStatus::PlaceHolder => {
1039 unreachable!("expect finish")
1040 }
1041 CreatingStreamingJobStatus::Finishing(_, tracking_job) => tracking_job,
1042 }
1043 }
1044
1045 pub(super) fn on_partial_graph_reset(mut self) {
1046 match &mut self.status {
1047 CreatingStreamingJobStatus::Resetting(notifiers) => {
1048 for notifier in notifiers.drain(..) {
1049 notifier.notify_collected();
1050 }
1051 }
1052 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1053 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1054 | CreatingStreamingJobStatus::Finishing(_, _) => {
1055 panic!(
1056 "should be resetting when receiving reset partial graph resp, but at {:?}",
1057 self.status
1058 )
1059 }
1060 CreatingStreamingJobStatus::PlaceHolder => {
1061 unreachable!()
1062 }
1063 }
1064 }
1065
1066 pub(super) fn drop(
1070 &mut self,
1071 notifiers: &mut Vec<Notifier>,
1072 partial_graph_manager: &mut PartialGraphManager,
1073 ) -> bool {
1074 match &mut self.status {
1075 CreatingStreamingJobStatus::Resetting(existing_notifiers) => {
1076 for notifier in &mut *notifiers {
1077 notifier.notify_started();
1078 }
1079 existing_notifiers.append(notifiers);
1080 true
1081 }
1082 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1083 | CreatingStreamingJobStatus::ConsumingLogStore { .. } => {
1084 for notifier in &mut *notifiers {
1085 notifier.notify_started();
1086 }
1087 partial_graph_manager.reset_partial_graphs([self.partial_graph_id]);
1088 self.status = CreatingStreamingJobStatus::Resetting(take(notifiers));
1089 true
1090 }
1091 CreatingStreamingJobStatus::Finishing(_, _) => false,
1092 CreatingStreamingJobStatus::PlaceHolder => {
1093 unreachable!()
1094 }
1095 }
1096 }
1097
1098 pub(crate) fn reset(self) -> bool {
1099 match self.status {
1100 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1101 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1102 | CreatingStreamingJobStatus::Finishing(_, _) => false,
1103 CreatingStreamingJobStatus::Resetting(notifiers) => {
1104 for notifier in notifiers {
1105 notifier.notify_collected();
1106 }
1107 true
1108 }
1109 CreatingStreamingJobStatus::PlaceHolder => {
1110 unreachable!()
1111 }
1112 }
1113 }
1114}
1115
1116#[cfg(test)]
1117mod tests {
1118 use super::*;
1119
1120 #[test]
1121 fn test_resolve_since_timestamp_upstream_log_epochs() {
1122 let upstream_log_epochs = vec![(vec![45, 50], 55)];
1123
1124 let (initial_barrier, barriers) =
1125 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1126 &upstream_log_epochs,
1127 [].iter(),
1128 40,
1129 60,
1130 )
1131 .unwrap();
1132
1133 assert_eq!(
1134 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1135 (40, 45)
1136 );
1137 assert!(initial_barrier.kind.is_checkpoint());
1138 assert_eq!(
1139 barriers
1140 .iter()
1141 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1142 .collect::<Vec<_>>(),
1143 vec![(45, 50), (50, 55), (55, 60)]
1144 );
1145 assert_eq!(
1146 barriers
1147 .iter()
1148 .map(|barrier| barrier.kind.is_checkpoint())
1149 .collect::<Vec<_>>(),
1150 vec![false, false, true]
1151 );
1152 }
1153
1154 #[test]
1155 fn test_resolve_since_timestamp_upstream_log_epochs_with_pending_barriers() {
1156 let upstream_log_epochs = vec![(vec![45, 50], 55)];
1157 let pending_upstream_barriers = [
1158 BarrierInfo {
1159 prev_epoch: TracedEpoch::new(Epoch(55)),
1160 curr_epoch: TracedEpoch::new(Epoch(60)),
1161 kind: BarrierKind::Checkpoint(vec![55]),
1162 },
1163 BarrierInfo {
1164 prev_epoch: TracedEpoch::new(Epoch(60)),
1165 curr_epoch: TracedEpoch::new(Epoch(70)),
1166 kind: BarrierKind::Barrier,
1167 },
1168 ];
1169
1170 let (initial_barrier, barriers) =
1171 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1172 &upstream_log_epochs,
1173 pending_upstream_barriers.iter(),
1174 40,
1175 70,
1176 )
1177 .unwrap();
1178
1179 assert_eq!(
1180 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1181 (40, 45)
1182 );
1183 assert!(initial_barrier.kind.is_checkpoint());
1184 assert_eq!(
1185 barriers
1186 .iter()
1187 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1188 .collect::<Vec<_>>(),
1189 vec![(45, 50), (50, 55), (55, 60), (60, 70)]
1190 );
1191 assert_eq!(
1192 barriers
1193 .iter()
1194 .map(|barrier| barrier.kind.is_checkpoint())
1195 .collect::<Vec<_>>(),
1196 vec![false, false, true, false]
1197 );
1198 }
1199
1200 #[test]
1201 fn test_resolve_since_timestamp_upstream_log_epochs_with_gap_before_pending_barriers() {
1202 let upstream_log_epochs = vec![(vec![61, 62, 63, 64], 65)];
1203 let pending_upstream_barriers = [
1204 BarrierInfo {
1205 prev_epoch: TracedEpoch::new(Epoch(65)),
1206 curr_epoch: TracedEpoch::new(Epoch(66)),
1207 kind: BarrierKind::Checkpoint(vec![65]),
1208 },
1209 BarrierInfo {
1210 prev_epoch: TracedEpoch::new(Epoch(66)),
1211 curr_epoch: TracedEpoch::new(Epoch(67)),
1212 kind: BarrierKind::Barrier,
1213 },
1214 BarrierInfo {
1215 prev_epoch: TracedEpoch::new(Epoch(67)),
1216 curr_epoch: TracedEpoch::new(Epoch(68)),
1217 kind: BarrierKind::Barrier,
1218 },
1219 BarrierInfo {
1220 prev_epoch: TracedEpoch::new(Epoch(68)),
1221 curr_epoch: TracedEpoch::new(Epoch(69)),
1222 kind: BarrierKind::Barrier,
1223 },
1224 BarrierInfo {
1225 prev_epoch: TracedEpoch::new(Epoch(69)),
1226 curr_epoch: TracedEpoch::new(Epoch(70)),
1227 kind: BarrierKind::Barrier,
1228 },
1229 ];
1230
1231 let (initial_barrier, barriers) =
1232 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1233 &upstream_log_epochs,
1234 pending_upstream_barriers.iter(),
1235 60,
1236 70,
1237 )
1238 .unwrap();
1239
1240 assert_eq!(
1241 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1242 (60, 61)
1243 );
1244 assert!(initial_barrier.kind.is_checkpoint());
1245 assert_eq!(
1246 barriers
1247 .iter()
1248 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1249 .collect::<Vec<_>>(),
1250 vec![
1251 (61, 62),
1252 (62, 63),
1253 (63, 64),
1254 (64, 65),
1255 (65, 66),
1256 (66, 67),
1257 (67, 68),
1258 (68, 69),
1259 (69, 70)
1260 ]
1261 );
1262 assert_eq!(
1263 barriers
1264 .iter()
1265 .map(|barrier| barrier.kind.is_checkpoint())
1266 .collect::<Vec<_>>(),
1267 vec![false, false, false, false, true, false, false, false, false]
1268 );
1269 }
1270
1271 #[test]
1272 fn test_resolve_since_timestamp_upstream_log_epochs_without_pending_barriers() {
1273 let upstream_log_epochs = vec![(vec![61, 62, 63, 64], 65)];
1274
1275 let (initial_barrier, barriers) =
1276 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1277 &upstream_log_epochs,
1278 [].iter(),
1279 60,
1280 66,
1281 )
1282 .unwrap();
1283
1284 assert_eq!(
1285 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1286 (60, 61)
1287 );
1288 assert!(initial_barrier.kind.is_checkpoint());
1289 assert_eq!(
1290 barriers
1291 .iter()
1292 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1293 .collect::<Vec<_>>(),
1294 vec![(61, 62), (62, 63), (63, 64), (64, 65), (65, 66)]
1295 );
1296 assert_eq!(
1297 barriers
1298 .iter()
1299 .map(|barrier| barrier.kind.is_checkpoint())
1300 .collect::<Vec<_>>(),
1301 vec![false, false, false, false, true]
1302 );
1303 }
1304}