1mod barrier_control;
16mod status;
17
18use std::cmp::max;
19use std::collections::{HashMap, HashSet, VecDeque, hash_map};
20use std::mem::take;
21use std::ops::Bound::{Excluded, Unbounded};
22use std::time::Duration;
23
24use anyhow::anyhow;
25use risingwave_common::catalog::TableId;
26use risingwave_common::id::JobId;
27use risingwave_common::metrics::LabelGuardedIntGauge;
28use risingwave_common::util::epoch::Epoch;
29use risingwave_meta_model::WorkerId;
30use risingwave_pb::ddl_service::PbBackfillType;
31use risingwave_pb::hummock::HummockVersionStats;
32use risingwave_pb::id::{ActorId, FragmentId, PartialGraphId};
33use risingwave_pb::stream_plan::StopMutation;
34use risingwave_pb::stream_plan::barrier_mutation::Mutation;
35use risingwave_pb::stream_service::BarrierCompleteResponse;
36use status::CreatingStreamingJobStatus;
37use tracing::{debug, info};
38
39use self::barrier_control::CreatingStreamingJobBarrierStats;
40use super::super::state::RenderResult;
41use super::{
42 IndependentCheckpointJob, IndependentCheckpointJobControl, IndependentCheckpointJobStatus,
43 IndependentJobControl, IndependentJobInfo, InitialPartialGraphRequest, SnapshotPhaseControl,
44 add_initial_partial_graph, build_initial_add_mutation,
45 snapshot_backfill_max_pending_barrier_num,
46};
47use crate::MetaResult;
48use crate::barrier::command::{
49 PostCollectCommand, TableLogEpochs, ThrottleConfigMap, UpstreamTableLogEpochs,
50};
51use crate::barrier::context::CreateIndependentStreamingJobCommandInfo;
52use crate::barrier::edge_builder::FragmentEdgeBuildResult;
53use crate::barrier::info::BarrierInfo;
54use crate::barrier::partial_graph::{
55 CollectedBarrier, PartialGraphBarrierInfo, PartialGraphManager, PartialGraphRecoverer,
56};
57use crate::barrier::progress::{TrackingJob, collect_done_fragments};
58use crate::barrier::{
59 BackfillProgress, BarrierKind, Command, FragmentBackfillProgress, TracedEpoch,
60};
61use crate::controller::fragment::InflightFragmentInfo;
62use crate::model::{FragmentDownstreamRelation, StreamActor, StreamJobActorsToCreate};
63use crate::notification::NotifierStarter;
64use crate::rpc::metrics::GLOBAL_META_METRICS;
65use crate::stream::source_manager::SplitAssignment;
66use crate::stream::{ExtendedFragmentBackfillOrder, build_actor_connector_splits};
67
68type ResolvedUpstreamLogEpochs<'a> = (u64, &'a [(Vec<u64>, u64)]);
69
70#[derive(Debug)]
71pub(crate) struct CreatingJobInfo {
72 pub fragment_infos: HashMap<FragmentId, InflightFragmentInfo>,
73 pub upstream_fragment_downstreams: FragmentDownstreamRelation,
74 pub downstreams: FragmentDownstreamRelation,
75 pub snapshot_backfill_upstream_tables: HashSet<TableId>,
76 pub stream_actors: HashMap<ActorId, StreamActor>,
77}
78
79#[derive(Debug)]
80pub(crate) struct CreatingStreamingJobControl {
81 control: IndependentJobControl,
82
83 node_actors: HashMap<WorkerId, HashSet<ActorId>>,
84
85 status: CreatingStreamingJobStatus,
86 max_lagged_barrier_num: usize,
87 max_pending_barrier_num: usize,
88
89 upstream_lag: LabelGuardedIntGauge,
90}
91
92impl CreatingStreamingJobControl {
93 #[expect(clippy::too_many_arguments)]
94 pub(crate) fn new<'a>(
95 entry: hash_map::VacantEntry<'a, JobId, IndependentCheckpointJobControl>,
96 create_info: CreateIndependentStreamingJobCommandInfo,
97 notifier: Option<&mut NotifierStarter>,
98 snapshot_backfill_upstream_tables: HashSet<TableId>,
99 snapshot_epoch: u64,
100 since_timestamp_upstream_log_epochs: Option<(&TableLogEpochs, PartialGraphId, u64)>,
101 version_stat: &HummockVersionStats,
102 term_id: &str,
103 partial_graph_manager: &mut PartialGraphManager,
104 edges: &mut FragmentEdgeBuildResult,
105 split_assignment: &SplitAssignment,
106 actors: &RenderResult,
107 ) -> MetaResult<&'a mut Self> {
108 let info = create_info.info.clone();
109 let job_id = info.stream_job_fragments.stream_job_id();
110 let database_id = info.streaming_job.database_id();
111 let is_since_timestamp = since_timestamp_upstream_log_epochs.is_some();
112 debug!(
113 %job_id,
114 definition = info.definition,
115 "new creating job"
116 );
117 let fragment_infos = info
118 .stream_job_fragments
119 .new_fragment_info(
120 &actors.stream_actors,
121 &actors.actor_location,
122 split_assignment,
123 )
124 .collect();
125 let actors_to_create = Command::create_streaming_job_actors_to_create(
126 &info,
127 edges,
128 &actors.stream_actors,
129 &actors.actor_location,
130 );
131
132 let (snapshot, initial_barrier_info, log_store_barriers_to_inject) = if let Some((
133 upstream_log_epochs,
134 upstream_partial_graph_id,
135 new_upstream_barrier_prev_epoch,
136 )) =
137 since_timestamp_upstream_log_epochs
138 {
139 let (initial_barrier, barriers_to_inject) =
140 Self::resolve_since_timestamp_upstream_log_epochs(
141 upstream_log_epochs,
142 partial_graph_manager.pending_barrier_infos(upstream_partial_graph_id),
143 snapshot_epoch,
144 new_upstream_barrier_prev_epoch,
145 )?;
146 (None, initial_barrier, Some(barriers_to_inject))
147 } else {
148 let (snapshot, initial_barrier) = SnapshotPhaseControl::for_new_job(
149 snapshot_epoch,
150 &fragment_infos,
151 &info,
152 version_stat,
153 );
154 (Some(snapshot), initial_barrier, None)
155 };
156
157 let actor_splits = split_assignment
158 .values()
159 .flat_map(build_actor_connector_splits)
160 .collect();
161
162 assert!(
163 info.cdc_table_snapshot_splits.is_none(),
164 "should not have cdc backfill for snapshot backfill job"
165 );
166
167 let initial_mutation = build_initial_add_mutation(
168 &fragment_infos,
169 &info.fragment_backfill_ordering,
170 actor_splits,
171 );
172
173 let node_actors = InflightFragmentInfo::actor_ids_to_collect(fragment_infos.values());
174 let independent_job_info = IndependentJobInfo::from_fragment_infos(
175 database_id,
176 job_id,
177 snapshot_epoch,
178 snapshot_backfill_upstream_tables,
179 fragment_infos.values(),
180 );
181 let partial_graph_id = independent_job_info.partial_graph_id;
182 let max_lagged_barrier_num = partial_graph_manager
183 .control_stream_manager()
184 .env
185 .opts
186 .snapshot_backfill_finish_max_lagged_barriers;
187 let opts = &partial_graph_manager.control_stream_manager().env.opts;
188 let max_pending_barrier_num = snapshot_backfill_max_pending_barrier_num(
189 opts.in_flight_barrier_nums,
190 opts.snapshot_backfill_barrier_amplification_factor,
191 );
192
193 let mut job = Self {
194 control: IndependentJobControl::new(independent_job_info),
195 status: CreatingStreamingJobStatus::PlaceHolder, max_lagged_barrier_num,
197 max_pending_barrier_num,
198 upstream_lag: GLOBAL_META_METRICS
199 .snapshot_backfill_lag
200 .with_guarded_label_values(&[&format!("{}", job_id)]),
201 node_actors,
202 };
203
204 let mut graph_adder = partial_graph_manager.add_partial_graph(
205 partial_graph_id,
206 term_id,
207 CreatingStreamingJobBarrierStats::new(job_id, snapshot_epoch),
208 );
209 if let Err(e) = add_initial_partial_graph(
210 &mut graph_adder,
211 partial_graph_id,
212 InitialPartialGraphRequest {
213 node_actors: &job.node_actors,
214 state_table_ids: &job.control.info.state_table_ids,
215 barrier_info: initial_barrier_info,
216 actors_to_create,
217 mutation: initial_mutation,
218 notifier,
219 create_info,
220 },
221 ) {
222 graph_adder.failed();
223 entry.insert(IndependentCheckpointJobControl::resetting(
224 job.control.info.snapshot_backfill_upstream_tables,
225 ));
226 return Err(e);
227 }
228 graph_adder.added();
229 let job_info = CreatingJobInfo {
230 fragment_infos,
231 upstream_fragment_downstreams: info.upstream_fragment_downstreams.clone(),
232 downstreams: info.stream_job_fragments.downstreams,
233 snapshot_backfill_upstream_tables: job
234 .control
235 .info
236 .snapshot_backfill_upstream_tables
237 .clone(),
238 stream_actors: actors
239 .stream_actors
240 .values()
241 .flatten()
242 .map(|actor| (actor.actor_id, actor.clone()))
243 .collect(),
244 };
245 if let Some(log_store_barriers_to_inject) = log_store_barriers_to_inject {
246 job.status = CreatingStreamingJobStatus::ConsumingLogStore {
247 tracking_job: TrackingJob::recovered(job_id, &job_info.fragment_infos),
248 info: job_info,
249 pending_barriers: log_store_barriers_to_inject.into(),
250 };
251 } else {
252 job.status = CreatingStreamingJobStatus::ConsumingSnapshot {
253 snapshot: snapshot.expect("snapshot phase should be initialized"),
254 pending_upstream_barriers: vec![],
255 info: job_info,
256 };
257 }
258 let job_control = entry.insert(if is_since_timestamp {
259 IndependentCheckpointJobControl::creating_streaming_job(
262 job_id,
263 partial_graph_id,
264 IndependentCheckpointJobStatus::Ready,
265 job,
266 )
267 } else {
268 IndependentCheckpointJobControl::creating_streaming_job(
269 job_id,
270 partial_graph_id,
271 IndependentCheckpointJobStatus::Initial { snapshot_epoch },
272 job,
273 )
274 });
275 let Some(IndependentCheckpointJob::CreatingStreamingJob(job)) = job_control.running_mut()
276 else {
277 unreachable!()
278 };
279 Ok(job)
280 }
281
282 pub(super) fn gen_fragment_backfill_progress(&self) -> Vec<FragmentBackfillProgress> {
283 match &self.status {
284 CreatingStreamingJobStatus::ConsumingSnapshot { snapshot, info, .. } => snapshot
285 .create_mview_tracker
286 .collect_fragment_progress(&info.fragment_infos, true),
287 CreatingStreamingJobStatus::ConsumingLogStore { info, .. }
288 | CreatingStreamingJobStatus::ConsumingUpstream { info, .. } => {
289 collect_done_fragments(self.control.info.job_id, &info.fragment_infos)
290 }
291 CreatingStreamingJobStatus::Finishing(_, _)
292 | CreatingStreamingJobStatus::PlaceHolder => vec![],
293 }
294 }
295
296 pub(super) fn resolve_upstream_log_epochs<'a>(
303 snapshot_backfill_upstream_tables: &HashSet<TableId>,
304 upstream_table_log_epochs: &'a UpstreamTableLogEpochs,
305 upstream_lag_start_epoch: u64,
306 upstream_barrier_info: &BarrierInfo,
307 ) -> MetaResult<ResolvedUpstreamLogEpochs<'a>> {
308 let table_id = snapshot_backfill_upstream_tables
309 .iter()
310 .next()
311 .expect("snapshot backfill job should have upstream");
312 if let Some(epochs) = upstream_table_log_epochs
313 .get(table_id)
314 .filter(|epochs| !epochs.is_empty())
315 {
316 let first_checkpoint_group_after_start =
321 epochs.partition_point(|(_, checkpoint_epoch)| {
322 *checkpoint_epoch <= upstream_lag_start_epoch
323 });
324 if first_checkpoint_group_after_start == 0 {
325 return Err(anyhow!(
326 "no upstream checkpoint epoch at or before recovery epoch {} for table {}",
327 upstream_lag_start_epoch,
328 table_id
329 )
330 .into());
331 }
332 let checkpoint_epoch = epochs[first_checkpoint_group_after_start - 1].1;
333 Ok((
334 checkpoint_epoch,
335 &epochs[first_checkpoint_group_after_start..],
336 ))
337 } else {
338 let checkpoint_epoch = upstream_barrier_info.prev_epoch();
341 if checkpoint_epoch > upstream_lag_start_epoch {
342 return Err(anyhow!(
343 "upstream barrier starts at {} after recovery epoch {} for table {}",
344 checkpoint_epoch,
345 upstream_lag_start_epoch,
346 table_id
347 )
348 .into());
349 }
350 Ok((checkpoint_epoch, &[]))
351 }
352 }
353
354 pub(super) fn resolve_checkpoint_aligned_upstream_log_barriers(
355 snapshot_backfill_upstream_tables: &HashSet<TableId>,
356 upstream_table_log_epochs: &UpstreamTableLogEpochs,
357 upstream_lag_start_epoch: u64,
358 upstream_barrier_info: &BarrierInfo,
359 ) -> MetaResult<Vec<BarrierInfo>> {
360 let (checkpoint_epoch, upstream_log_epochs) = Self::resolve_upstream_log_epochs(
361 snapshot_backfill_upstream_tables,
362 upstream_table_log_epochs,
363 upstream_lag_start_epoch,
364 upstream_barrier_info,
365 )?;
366 if checkpoint_epoch != upstream_lag_start_epoch {
367 return Err(anyhow!(
368 "upstream lag start epoch {} is not an upstream checkpoint; preceding checkpoint is {}",
369 upstream_lag_start_epoch,
370 checkpoint_epoch
371 )
372 .into());
373 }
374 Self::compose_upstream_log_barriers(
375 upstream_lag_start_epoch,
376 upstream_log_epochs,
377 upstream_barrier_info,
378 )
379 }
380
381 pub(super) fn compose_upstream_log_barriers(
382 upstream_lag_start_epoch: u64,
383 upstream_log_epochs: &[(Vec<u64>, u64)],
384 upstream_barrier_info: &BarrierInfo,
385 ) -> MetaResult<Vec<BarrierInfo>> {
386 let mut ret = vec![];
387 let mut prev_epoch = upstream_lag_start_epoch;
388 let mut pending_non_checkpoint_barriers = vec![];
389 for (non_checkpoint_epochs, checkpoint_epoch) in upstream_log_epochs {
390 for (i, epoch) in non_checkpoint_epochs
391 .iter()
392 .chain([checkpoint_epoch])
393 .enumerate()
394 {
395 if *epoch <= prev_epoch {
396 return Err(anyhow!(
397 "upstream log epoch {} is not after recovery epoch {}",
398 epoch,
399 prev_epoch
400 )
401 .into());
402 }
403 pending_non_checkpoint_barriers.push(prev_epoch);
404 ret.push(BarrierInfo {
405 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
406 curr_epoch: TracedEpoch::new(Epoch(*epoch)),
407 kind: if i == 0 {
408 BarrierKind::Checkpoint(take(&mut pending_non_checkpoint_barriers))
409 } else {
410 BarrierKind::Barrier
411 },
412 });
413 prev_epoch = *epoch;
414 }
415 }
416 if upstream_barrier_info.curr_epoch() <= prev_epoch {
417 return Err(anyhow!(
418 "upstream barrier current epoch {} is not after resolved epoch {}",
419 upstream_barrier_info.curr_epoch(),
420 prev_epoch
421 )
422 .into());
423 }
424 ret.push(BarrierInfo {
425 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
426 curr_epoch: TracedEpoch::new(Epoch(upstream_barrier_info.curr_epoch())),
427 kind: BarrierKind::Checkpoint(pending_non_checkpoint_barriers),
428 });
429 Ok(ret)
430 }
431
432 fn resolve_since_timestamp_upstream_log_epochs(
461 upstream_log_epochs: &TableLogEpochs,
462 pending_upstream_barriers: impl Iterator<Item = &BarrierInfo>,
463 snapshot_epoch: u64,
464 new_upstream_barrier_prev_epoch: u64,
465 ) -> MetaResult<(BarrierInfo, Vec<BarrierInfo>)> {
466 let mut initial_barrier = None;
467 let mut barriers = vec![];
468 fn emit_barrier(
469 initial_barrier: &mut Option<BarrierInfo>,
470 barriers: &mut Vec<BarrierInfo>,
471 barrier: BarrierInfo,
472 ) {
473 if initial_barrier.is_none() {
474 *initial_barrier = Some(barrier);
475 } else {
476 barriers.push(barrier);
477 }
478 }
479
480 let mut prev_epoch = snapshot_epoch;
481 let mut pending_non_checkpoint_barriers = vec![];
482 for (non_checkpoint_epochs, checkpoint_epoch) in upstream_log_epochs {
483 for (i, epoch) in non_checkpoint_epochs
484 .iter()
485 .chain([checkpoint_epoch])
486 .enumerate()
487 {
488 assert!(
489 *epoch > prev_epoch,
490 "changelog epochs should be strictly increasing"
491 );
492 pending_non_checkpoint_barriers.push(prev_epoch);
493 emit_barrier(
494 &mut initial_barrier,
495 &mut barriers,
496 BarrierInfo {
497 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
498 curr_epoch: TracedEpoch::new(Epoch(*epoch)),
499 kind: if i == 0 {
500 BarrierKind::Checkpoint(take(&mut pending_non_checkpoint_barriers))
501 } else {
502 BarrierKind::Barrier
503 },
504 },
505 );
506 prev_epoch = *epoch;
507 }
508 }
509
510 let mut pending_upstream_barriers = pending_upstream_barriers.peekable();
511 pending_non_checkpoint_barriers.push(prev_epoch);
512 if pending_upstream_barriers.peek().is_none() {
513 assert!(
514 new_upstream_barrier_prev_epoch > prev_epoch,
515 "new upstream barrier prev epoch should be newer than the latest changelog epoch"
516 );
517 emit_barrier(
518 &mut initial_barrier,
519 &mut barriers,
520 BarrierInfo {
521 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
522 curr_epoch: TracedEpoch::new(Epoch(new_upstream_barrier_prev_epoch)),
523 kind: BarrierKind::Checkpoint(pending_non_checkpoint_barriers),
524 },
525 );
526 } else {
527 let first_pending_barrier = pending_upstream_barriers
528 .peek()
529 .expect("first pending upstream barrier should exist after peek");
530 assert!(
531 first_pending_barrier.prev_epoch() > prev_epoch,
532 "first pending upstream barrier should be newer than the latest resolved changelog epoch"
533 );
534 emit_barrier(
535 &mut initial_barrier,
536 &mut barriers,
537 BarrierInfo {
538 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
539 curr_epoch: TracedEpoch::new(Epoch(first_pending_barrier.prev_epoch())),
540 kind: BarrierKind::Checkpoint(take(&mut pending_non_checkpoint_barriers)),
541 },
542 );
543 prev_epoch = first_pending_barrier.prev_epoch();
544 for pending_barrier in pending_upstream_barriers {
545 assert_eq!(
546 pending_barrier.prev_epoch(),
547 prev_epoch,
548 "pending upstream barriers should continue from resolved changelog epochs"
549 );
550 pending_non_checkpoint_barriers.push(prev_epoch);
551 emit_barrier(
552 &mut initial_barrier,
553 &mut barriers,
554 BarrierInfo {
555 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
556 curr_epoch: TracedEpoch::new(Epoch(pending_barrier.curr_epoch())),
557 kind: if pending_barrier.kind.is_checkpoint() {
558 BarrierKind::Checkpoint(take(&mut pending_non_checkpoint_barriers))
559 } else {
560 BarrierKind::Barrier
561 },
562 },
563 );
564 prev_epoch = pending_barrier.curr_epoch();
565 }
566 assert_eq!(
567 new_upstream_barrier_prev_epoch, prev_epoch,
568 "new upstream barrier prev epoch should match the latest pending log-store epoch"
569 );
570 }
571 let Some(initial_barrier) = initial_barrier else {
572 return Err(anyhow::anyhow!(
573 "missing lagging barriers for direct log-store start from snapshot epoch {}",
574 snapshot_epoch
575 )
576 .into());
577 };
578 assert!(initial_barrier.kind.is_checkpoint());
579 Ok((initial_barrier, barriers))
580 }
581
582 fn recover_consuming_snapshot(
583 job_id: JobId,
584 upstream_table_log_epochs: &UpstreamTableLogEpochs,
585 snapshot_epoch: u64,
586 committed_epoch: u64,
587 upstream_barrier_info: &BarrierInfo,
588 info: CreatingJobInfo,
589 backfill_order: &ExtendedFragmentBackfillOrder,
590 version_stat: &HummockVersionStats,
591 ) -> MetaResult<(CreatingStreamingJobStatus, BarrierInfo)> {
592 let (snapshot, barrier_info) = SnapshotPhaseControl::for_recovery(
593 job_id,
594 snapshot_epoch,
595 committed_epoch,
596 &info.fragment_infos,
597 backfill_order,
598 version_stat,
599 );
600 Ok((
601 CreatingStreamingJobStatus::ConsumingSnapshot {
602 snapshot,
603 pending_upstream_barriers: Self::resolve_checkpoint_aligned_upstream_log_barriers(
604 &info.snapshot_backfill_upstream_tables,
605 upstream_table_log_epochs,
606 snapshot_epoch,
607 upstream_barrier_info,
608 )?,
609 info,
610 },
611 barrier_info,
612 ))
613 }
614
615 fn recover_consuming_log_store(
616 job_id: JobId,
617 upstream_table_log_epochs: &UpstreamTableLogEpochs,
618 committed_epoch: u64,
619 upstream_barrier_info: &BarrierInfo,
620 info: CreatingJobInfo,
621 ) -> MetaResult<(CreatingStreamingJobStatus, BarrierInfo)> {
622 let mut pending_barriers: VecDeque<_> =
623 Self::resolve_checkpoint_aligned_upstream_log_barriers(
624 &info.snapshot_backfill_upstream_tables,
625 upstream_table_log_epochs,
626 committed_epoch,
627 upstream_barrier_info,
628 )?
629 .into();
630 let mut first_barrier = pending_barriers
631 .pop_front()
632 .expect("resolved upstream log epochs should not be empty");
633 assert!(first_barrier.kind.is_checkpoint());
634 first_barrier.kind = BarrierKind::Initial;
635
636 Ok((
637 CreatingStreamingJobStatus::ConsumingLogStore {
638 tracking_job: TrackingJob::recovered(job_id, &info.fragment_infos),
639 pending_barriers,
640 info,
641 },
642 first_barrier,
643 ))
644 }
645
646 #[expect(clippy::too_many_arguments)]
647 pub(crate) fn recover(
648 control: IndependentJobControl,
649 upstream_table_log_epochs: &UpstreamTableLogEpochs,
650 upstream_barrier_info: &BarrierInfo,
651 fragment_infos: HashMap<FragmentId, InflightFragmentInfo>,
652 backfill_order: ExtendedFragmentBackfillOrder,
653 fragment_relations: &FragmentDownstreamRelation,
654 version_stat: &HummockVersionStats,
655 new_actors: StreamJobActorsToCreate,
656 initial_mutation: Mutation,
657 term_id: &str,
658 partial_graph_recoverer: &mut PartialGraphRecoverer<'_>,
659 ) -> MetaResult<Self> {
660 let job_id = control.info.job_id;
661 let partial_graph_id = control.info.partial_graph_id;
662 let snapshot_epoch = control.info.snapshot_epoch;
663 let committed_epoch = control
664 .max_committed_epoch
665 .expect("recovered independent job should have committed");
666 info!(
667 %job_id,
668 "recovered creating snapshot backfill job"
669 );
670
671 let node_actors = InflightFragmentInfo::actor_ids_to_collect(fragment_infos.values());
672
673 let mut upstream_fragment_downstreams: FragmentDownstreamRelation = Default::default();
674 for (upstream_fragment_id, downstreams) in fragment_relations {
675 if fragment_infos.contains_key(upstream_fragment_id) {
676 continue;
677 }
678 for downstream in downstreams {
679 if fragment_infos.contains_key(&downstream.downstream_fragment_id) {
680 upstream_fragment_downstreams
681 .entry(*upstream_fragment_id)
682 .or_default()
683 .push(downstream.clone());
684 }
685 }
686 }
687 let downstreams = fragment_infos
688 .keys()
689 .filter_map(|fragment_id| {
690 fragment_relations
691 .get(fragment_id)
692 .map(|relation| (*fragment_id, relation.clone()))
693 })
694 .collect();
695 let info = CreatingJobInfo {
696 fragment_infos,
697 upstream_fragment_downstreams,
698 downstreams,
699 snapshot_backfill_upstream_tables: control
700 .info
701 .snapshot_backfill_upstream_tables
702 .clone(),
703 stream_actors: new_actors
704 .values()
705 .flat_map(|fragments| {
706 fragments.values().flat_map(|(_, actors, _)| {
707 actors
708 .iter()
709 .map(|(actor, _, _)| (actor.actor_id, actor.clone()))
710 })
711 })
712 .collect(),
713 };
714
715 let (status, first_barrier_info) = if committed_epoch < snapshot_epoch {
716 Self::recover_consuming_snapshot(
717 job_id,
718 upstream_table_log_epochs,
719 snapshot_epoch,
720 committed_epoch,
721 upstream_barrier_info,
722 info,
723 &backfill_order,
724 version_stat,
725 )?
726 } else {
727 Self::recover_consuming_log_store(
728 job_id,
729 upstream_table_log_epochs,
730 committed_epoch,
731 upstream_barrier_info,
732 info,
733 )?
734 };
735
736 let max_lagged_barrier_num = partial_graph_recoverer
737 .control_stream_manager()
738 .env
739 .opts
740 .snapshot_backfill_finish_max_lagged_barriers;
741 let opts = &partial_graph_recoverer.control_stream_manager().env.opts;
742 let max_pending_barrier_num = snapshot_backfill_max_pending_barrier_num(
743 opts.in_flight_barrier_nums,
744 opts.snapshot_backfill_barrier_amplification_factor,
745 );
746
747 partial_graph_recoverer.recover_graph(
748 partial_graph_id,
749 term_id,
750 initial_mutation,
751 &first_barrier_info,
752 &node_actors,
753 control.info.state_table_ids.iter().copied(),
754 new_actors,
755 CreatingStreamingJobBarrierStats::new(job_id, snapshot_epoch),
756 )?;
757
758 Ok(Self {
759 control,
760 node_actors,
761 status,
762 max_lagged_barrier_num,
763 max_pending_barrier_num,
764 upstream_lag: GLOBAL_META_METRICS
765 .snapshot_backfill_lag
766 .with_guarded_label_values(&[&format!("{}", job_id)]),
767 })
768 }
769
770 pub(crate) fn gen_backfill_progress(&self) -> BackfillProgress {
771 let progress = match &self.status {
772 CreatingStreamingJobStatus::ConsumingSnapshot { snapshot, .. } => {
773 if snapshot.create_mview_tracker.is_finished() {
774 "Snapshot finished".to_owned()
775 } else {
776 let progress = snapshot.create_mview_tracker.gen_backfill_progress();
777 format!("Snapshot [{}]", progress)
778 }
779 }
780 CreatingStreamingJobStatus::ConsumingLogStore {
781 pending_barriers, ..
782 } => format!("LogStore [pending barriers: {}]", pending_barriers.len()),
783 CreatingStreamingJobStatus::ConsumingUpstream { .. } => "Upstream".to_owned(),
784 CreatingStreamingJobStatus::Finishing(finish_epoch, ..) => {
785 if let Some(committed_epoch) = self.control.max_committed_epoch {
786 let lag = Duration::from_millis(
787 Epoch(*finish_epoch)
788 .physical_time()
789 .saturating_sub(Epoch(committed_epoch).physical_time()),
790 );
791 format!("Finishing [epoch lag: {lag:?}]")
792 } else {
793 "Finishing [waiting for first checkpoint]".to_owned()
795 }
796 }
797 CreatingStreamingJobStatus::PlaceHolder => {
798 unreachable!()
799 }
800 };
801 BackfillProgress {
802 progress,
803 backfill_type: PbBackfillType::SnapshotBackfill,
804 }
805 }
806
807 pub(super) fn pinned_upstream_tables(&self) -> &HashSet<TableId> {
808 &self.control.info.snapshot_backfill_upstream_tables
809 }
810
811 fn inject_barrier(
812 partial_graph_id: PartialGraphId,
813 partial_graph_manager: &mut PartialGraphManager,
814 node_actors: &HashMap<WorkerId, HashSet<ActorId>>,
815 state_table_ids: &HashSet<TableId>,
816 is_finishing: bool,
817 barrier_info: BarrierInfo,
818 new_actors: Option<StreamJobActorsToCreate>,
819 mutation: Option<Mutation>,
820 notifier: Option<&mut NotifierStarter>,
821 first_create_info: Option<CreateIndependentStreamingJobCommandInfo>,
822 ) -> MetaResult<()> {
823 let (table_ids_to_sync, nodes_to_sync_table) = if !is_finishing {
824 (Some(state_table_ids), Some(node_actors.keys().copied()))
825 } else {
826 (None, None)
827 };
828 partial_graph_manager.inject_barrier(
829 partial_graph_id,
830 mutation,
831 node_actors,
832 table_ids_to_sync.into_iter().flatten().copied(),
833 nodes_to_sync_table.into_iter().flatten(),
834 new_actors,
835 PartialGraphBarrierInfo::new(
836 first_create_info.map_or_else(
837 PostCollectCommand::barrier,
838 CreateIndependentStreamingJobCommandInfo::into_post_collect,
839 ),
840 barrier_info,
841 notifier,
842 state_table_ids.clone(),
843 ),
844 )?;
845 Ok(())
846 }
847
848 pub(crate) fn start_consume_upstream(
849 &mut self,
850 barrier_info: &BarrierInfo,
851 ) -> &CreatingJobInfo {
852 info!(
853 job_id = %self.control.info.job_id,
854 prev_epoch = barrier_info.prev_epoch(),
855 "start consuming upstream"
856 );
857 self.status.start_consume_upstream(barrier_info)
858 }
859
860 pub(crate) fn start_finishing(
861 &mut self,
862 partial_graph_manager: &mut PartialGraphManager,
863 barrier_info: &BarrierInfo,
864 ) -> MetaResult<CreatingJobInfo> {
865 info!(
866 job_id = %self.control.info.job_id,
867 prev_epoch = barrier_info.prev_epoch(),
868 "start merging into database graph"
869 );
870 let info = self.status.start_finishing(barrier_info);
871 Self::inject_barrier(
872 self.control.info.partial_graph_id,
873 partial_graph_manager,
874 &self.node_actors,
875 &self.control.info.state_table_ids,
876 true,
877 barrier_info.clone(),
878 None,
879 Some(Mutation::Stop(StopMutation {
880 actors: info
882 .fragment_infos
883 .values()
884 .flat_map(|info| info.actors.keys().copied())
885 .collect(),
886 dropped_sink_fragments: vec![], })),
888 None, None,
890 )?;
891 Ok(info)
892 }
893
894 pub(crate) fn on_new_upstream_barrier(
895 &mut self,
896 partial_graph_manager: &mut PartialGraphManager,
897 barrier_info: &BarrierInfo,
898 mutation: Option<(Mutation, Option<&mut NotifierStarter>)>,
899 ) -> MetaResult<()> {
900 let progress_epoch = if let Some(max_committed_epoch) = self.control.max_committed_epoch {
901 max(max_committed_epoch, self.control.info.snapshot_epoch)
902 } else {
903 self.control.info.snapshot_epoch
904 };
905 self.upstream_lag.set(
906 barrier_info
907 .prev_epoch
908 .value()
909 .0
910 .saturating_sub(progress_epoch) as _,
911 );
912 let (mut mutation, mut notifier) = match mutation {
913 Some((mutation, notifier)) => (Some(mutation), notifier),
914 None => (None, None),
915 };
916 for (barrier_to_inject, mutation) in self.status.on_new_upstream_epoch(
917 partial_graph_manager,
918 self.control.info.partial_graph_id,
919 self.max_pending_barrier_num,
920 barrier_info,
921 mutation.take(),
922 ) {
923 Self::inject_barrier(
924 self.control.info.partial_graph_id,
925 partial_graph_manager,
926 &self.node_actors,
927 &self.control.info.state_table_ids,
928 false,
929 barrier_to_inject,
930 None,
931 mutation,
932 notifier.take(),
933 None,
934 )?;
935 }
936 Ok(())
937 }
938
939 pub(crate) fn pre_apply_throttle(
940 &mut self,
941 config: &mut ThrottleConfigMap,
942 ) -> Option<Mutation> {
943 self.status.pre_apply_throttle(config)
944 }
945
946 pub(crate) fn collect(&mut self, collected_barrier: CollectedBarrier<'_>) -> bool {
948 let pending_barrier_num = collected_barrier.pending_barrier_num;
949 self.status.update_progress(
950 collected_barrier
951 .resps
952 .values()
953 .flat_map(|resp| &resp.create_mview_progress),
954 );
955 self.is_ready_to_consume_upstream() && pending_barrier_num <= self.max_lagged_barrier_num
956 }
957
958 fn is_ready_to_consume_upstream(&self) -> bool {
959 if let CreatingStreamingJobStatus::ConsumingLogStore {
960 pending_barriers, ..
961 } = &self.status
962 {
963 pending_barriers.is_empty()
964 } else {
965 false
966 }
967 }
968
969 pub(crate) fn is_consuming_upstream(&self) -> bool {
970 matches!(
971 &self.status,
972 CreatingStreamingJobStatus::ConsumingUpstream { .. }
973 )
974 }
975
976 pub(crate) fn should_start_consume_upstream(
977 &self,
978 partial_graph_manager: &PartialGraphManager,
979 ) -> bool {
980 if !self.is_ready_to_consume_upstream() {
981 return false;
982 }
983
984 partial_graph_manager.pending_barrier_num(self.control.info.partial_graph_id)
987 <= self.max_lagged_barrier_num
988 }
989}
990
991impl CreatingStreamingJobControl {
992 pub(crate) fn start_completing(
993 &mut self,
994 partial_graph_manager: &mut PartialGraphManager,
995 min_upstream_inflight_epoch: Option<u64>,
996 ) -> Option<(
997 u64,
998 HashMap<WorkerId, BarrierCompleteResponse>,
999 PartialGraphBarrierInfo,
1000 bool,
1001 )> {
1002 let (finished_at_epoch, epoch_end_bound) = match &self.status {
1003 CreatingStreamingJobStatus::Finishing(finish_at_epoch, _) => {
1004 let epoch_end_bound = min_upstream_inflight_epoch
1005 .map(|upstream_epoch| {
1006 if upstream_epoch < *finish_at_epoch {
1007 Excluded(upstream_epoch)
1008 } else {
1009 Unbounded
1010 }
1011 })
1012 .unwrap_or(Unbounded);
1013 (Some(*finish_at_epoch), epoch_end_bound)
1014 }
1015 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1016 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1017 | CreatingStreamingJobStatus::ConsumingUpstream { .. } => (
1018 None,
1019 min_upstream_inflight_epoch
1020 .map(Excluded)
1021 .unwrap_or(Unbounded),
1022 ),
1023 CreatingStreamingJobStatus::PlaceHolder => {
1024 unreachable!()
1025 }
1026 };
1027 partial_graph_manager
1028 .start_completing(
1029 self.control.info.partial_graph_id,
1030 epoch_end_bound,
1031 |non_checkpoint_epoch, _, _| {
1032 if let Some(finish_at_epoch) = finished_at_epoch {
1033 assert!(non_checkpoint_epoch.prev < finish_at_epoch);
1034 }
1035 },
1036 )
1037 .map(|(epoch, resps, info)| {
1038 let is_finish_epoch = if let Some(finish_at_epoch) = finished_at_epoch {
1039 assert!(!info.post_collect_command.should_checkpoint());
1040 if epoch == finish_at_epoch {
1041 self.ack_completed(partial_graph_manager, epoch);
1043 true
1044 } else {
1045 false
1046 }
1047 } else {
1048 false
1049 };
1050 (epoch, resps, info, is_finish_epoch)
1051 })
1052 }
1053
1054 pub(super) fn ack_completed(
1055 &mut self,
1056 partial_graph_manager: &mut PartialGraphManager,
1057 completed_epoch: u64,
1058 ) {
1059 match &self.status {
1060 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1061 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1062 | CreatingStreamingJobStatus::ConsumingUpstream { .. }
1063 | CreatingStreamingJobStatus::Finishing(_, _) => {
1064 partial_graph_manager
1065 .ack_completed(self.control.info.partial_graph_id, completed_epoch);
1066 self.control.ack_completed(completed_epoch);
1067 }
1068 CreatingStreamingJobStatus::PlaceHolder => {
1069 unreachable!()
1070 }
1071 }
1072 }
1073
1074 pub(crate) fn fragment_infos(&self) -> Option<&HashMap<FragmentId, InflightFragmentInfo>> {
1075 self.status.fragment_infos()
1076 }
1077
1078 pub fn into_tracking_job(self) -> TrackingJob {
1079 match self.status {
1080 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1081 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1082 | CreatingStreamingJobStatus::ConsumingUpstream { .. }
1083 | CreatingStreamingJobStatus::PlaceHolder => {
1084 unreachable!("expect finish")
1085 }
1086 CreatingStreamingJobStatus::Finishing(_, tracking_job) => tracking_job,
1087 }
1088 }
1089
1090 pub(super) fn can_drop_independently(&self) -> bool {
1095 match &self.status {
1096 CreatingStreamingJobStatus::ConsumingSnapshot { .. }
1097 | CreatingStreamingJobStatus::ConsumingLogStore { .. }
1098 | CreatingStreamingJobStatus::ConsumingUpstream { .. } => true,
1099 CreatingStreamingJobStatus::Finishing(_, _) => false,
1100 CreatingStreamingJobStatus::PlaceHolder => {
1101 unreachable!()
1102 }
1103 }
1104 }
1105
1106 pub(super) fn can_drop(&self) -> bool {
1107 self.status.can_drop()
1108 }
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113 use super::*;
1114
1115 #[test]
1116 fn test_finishing_progress_before_first_checkpoint() {
1117 let job_id = JobId::new(1);
1118 let mut job = CreatingStreamingJobControl {
1119 control: IndependentJobControl::new(IndependentJobInfo {
1120 job_id,
1121 partial_graph_id: 1.into(),
1122 snapshot_backfill_upstream_tables: HashSet::new(),
1123 snapshot_epoch: Epoch::from_physical_time(1000).0,
1124 state_table_ids: HashSet::new(),
1125 }),
1126 node_actors: HashMap::new(),
1127 status: CreatingStreamingJobStatus::Finishing(
1128 Epoch::from_physical_time(2000).0,
1129 TrackingJob::recovered(job_id, &HashMap::new()),
1130 ),
1131 max_lagged_barrier_num: 10,
1132 max_pending_barrier_num: 10,
1133 upstream_lag: GLOBAL_META_METRICS
1134 .snapshot_backfill_lag
1135 .with_guarded_label_values(&[&job_id.to_string()]),
1136 };
1137 assert_eq!(
1138 job.gen_backfill_progress().progress,
1139 "Finishing [waiting for first checkpoint]"
1140 );
1141 for (committed_time, expected_lag) in [(1500, "500ms"), (2000, "0ns"), (2500, "0ns")] {
1142 job.control
1143 .ack_completed(Epoch::from_physical_time(committed_time).0);
1144 assert_eq!(
1145 job.gen_backfill_progress().progress,
1146 format!("Finishing [epoch lag: {expected_lag}]")
1147 );
1148 }
1149 }
1150
1151 fn checkpoint_barrier(prev_epoch: u64, curr_epoch: u64) -> BarrierInfo {
1152 BarrierInfo {
1153 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
1154 curr_epoch: TracedEpoch::new(Epoch(curr_epoch)),
1155 kind: BarrierKind::Checkpoint(vec![]),
1156 }
1157 }
1158
1159 #[test]
1160 fn test_resolve_and_compose_upstream_log_epochs_from_lagging_start() {
1161 let table_id = TableId::new(1);
1162 let upstream_tables = HashSet::from([table_id]);
1163 let upstream_log_epochs = HashMap::from([(table_id, vec![(vec![], 100), (vec![], 102)])]);
1164 let upstream_barrier = checkpoint_barrier(102, 104);
1165
1166 let (checkpoint_epoch, upstream_log_epochs) =
1167 CreatingStreamingJobControl::resolve_upstream_log_epochs(
1168 &upstream_tables,
1169 &upstream_log_epochs,
1170 101,
1171 &upstream_barrier,
1172 )
1173 .unwrap();
1174
1175 assert_eq!(checkpoint_epoch, 100);
1176 assert_eq!(upstream_log_epochs, &[(vec![], 102)]);
1177
1178 let barriers = CreatingStreamingJobControl::compose_upstream_log_barriers(
1179 101,
1180 upstream_log_epochs,
1181 &upstream_barrier,
1182 )
1183 .unwrap();
1184
1185 assert_eq!(
1186 barriers
1187 .iter()
1188 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1189 .collect::<Vec<_>>(),
1190 vec![(101, 102), (102, 104)]
1191 );
1192 assert!(barriers.iter().all(|barrier| barrier.kind.is_checkpoint()));
1193 }
1194
1195 #[test]
1196 fn test_resolve_and_compose_upstream_log_epochs_from_lagging_start_without_log_entry() {
1197 let table_id = TableId::new(1);
1198 let upstream_tables = HashSet::from([table_id]);
1199 let upstream_log_epochs = HashMap::new();
1200 let upstream_barrier = checkpoint_barrier(100, 102);
1201
1202 let (checkpoint_epoch, upstream_log_epochs) =
1203 CreatingStreamingJobControl::resolve_upstream_log_epochs(
1204 &upstream_tables,
1205 &upstream_log_epochs,
1206 101,
1207 &upstream_barrier,
1208 )
1209 .unwrap();
1210
1211 assert_eq!(checkpoint_epoch, 100);
1212 assert!(upstream_log_epochs.is_empty());
1213
1214 let barriers = CreatingStreamingJobControl::compose_upstream_log_barriers(
1215 101,
1216 upstream_log_epochs,
1217 &upstream_barrier,
1218 )
1219 .unwrap();
1220
1221 assert_eq!(barriers.len(), 1);
1222 assert_eq!(barriers[0].prev_epoch(), 101);
1223 assert_eq!(barriers[0].curr_epoch(), 102);
1224 assert!(barriers[0].kind.is_checkpoint());
1225 }
1226
1227 #[test]
1228 fn test_resolve_upstream_log_epochs_from_exact_checkpoint() {
1229 let table_id = TableId::new(1);
1230 let upstream_tables = HashSet::from([table_id]);
1231 let upstream_log_epochs = HashMap::from([(table_id, vec![(vec![], 100), (vec![], 102)])]);
1232 let upstream_barrier = checkpoint_barrier(102, 104);
1233
1234 let (checkpoint_epoch, upstream_log_epochs) =
1235 CreatingStreamingJobControl::resolve_upstream_log_epochs(
1236 &upstream_tables,
1237 &upstream_log_epochs,
1238 100,
1239 &upstream_barrier,
1240 )
1241 .unwrap();
1242
1243 assert_eq!(checkpoint_epoch, 100);
1244 assert_eq!(upstream_log_epochs, &[(vec![], 102)]);
1245 }
1246
1247 #[test]
1248 fn test_resolve_upstream_log_epochs_rejects_start_before_first_checkpoint() {
1249 let table_id = TableId::new(1);
1250 let upstream_tables = HashSet::from([table_id]);
1251 let upstream_log_epochs = HashMap::from([(table_id, vec![(vec![], 100), (vec![], 102)])]);
1252 let upstream_barrier = checkpoint_barrier(102, 104);
1253
1254 let error = CreatingStreamingJobControl::resolve_upstream_log_epochs(
1255 &upstream_tables,
1256 &upstream_log_epochs,
1257 99,
1258 &upstream_barrier,
1259 )
1260 .unwrap_err();
1261
1262 assert!(error.to_string().contains("at or before recovery epoch 99"));
1263 }
1264
1265 #[test]
1266 fn test_snapshot_backfill_max_pending_barrier_num() {
1267 assert_eq!(snapshot_backfill_max_pending_barrier_num(10, 0), 10);
1268 assert_eq!(snapshot_backfill_max_pending_barrier_num(10, 1), 10);
1269 assert_eq!(snapshot_backfill_max_pending_barrier_num(10, 10), 100);
1270 assert_eq!(
1271 snapshot_backfill_max_pending_barrier_num(usize::MAX, 10),
1272 usize::MAX
1273 );
1274 }
1275
1276 #[test]
1277 fn test_resolve_since_timestamp_upstream_log_epochs() {
1278 let upstream_log_epochs = vec![(vec![45, 50], 55)];
1279
1280 let (initial_barrier, barriers) =
1281 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1282 &upstream_log_epochs,
1283 [].iter(),
1284 40,
1285 60,
1286 )
1287 .unwrap();
1288
1289 assert_eq!(
1290 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1291 (40, 45)
1292 );
1293 assert!(initial_barrier.kind.is_checkpoint());
1294 assert_eq!(
1295 barriers
1296 .iter()
1297 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1298 .collect::<Vec<_>>(),
1299 vec![(45, 50), (50, 55), (55, 60)]
1300 );
1301 assert_eq!(
1302 barriers
1303 .iter()
1304 .map(|barrier| match &barrier.kind {
1305 BarrierKind::Checkpoint(epochs) => Some(epochs.clone()),
1306 _ => None,
1307 })
1308 .collect::<Vec<_>>(),
1309 vec![None, None, Some(vec![45, 50, 55])]
1310 );
1311 }
1312
1313 #[test]
1314 fn test_resolve_since_timestamp_upstream_log_epochs_with_pending_barriers() {
1315 let upstream_log_epochs = vec![(vec![45, 50], 55)];
1316 let pending_upstream_barriers = [
1317 BarrierInfo {
1318 prev_epoch: TracedEpoch::new(Epoch(60)),
1319 curr_epoch: TracedEpoch::new(Epoch(65)),
1320 kind: BarrierKind::Barrier,
1321 },
1322 BarrierInfo {
1323 prev_epoch: TracedEpoch::new(Epoch(65)),
1324 curr_epoch: TracedEpoch::new(Epoch(70)),
1325 kind: BarrierKind::Checkpoint(vec![60, 65]),
1326 },
1327 ];
1328
1329 let (initial_barrier, barriers) =
1330 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1331 &upstream_log_epochs,
1332 pending_upstream_barriers.iter(),
1333 40,
1334 70,
1335 )
1336 .unwrap();
1337
1338 assert_eq!(
1339 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1340 (40, 45)
1341 );
1342 assert!(initial_barrier.kind.is_checkpoint());
1343 assert_eq!(
1344 barriers
1345 .iter()
1346 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1347 .collect::<Vec<_>>(),
1348 vec![(45, 50), (50, 55), (55, 60), (60, 65), (65, 70)]
1349 );
1350 assert_eq!(
1351 barriers
1352 .iter()
1353 .map(|barrier| match &barrier.kind {
1354 BarrierKind::Checkpoint(epochs) => Some(epochs.clone()),
1355 _ => None,
1356 })
1357 .collect::<Vec<_>>(),
1358 vec![None, None, Some(vec![45, 50, 55]), None, Some(vec![60, 65])]
1359 );
1360 }
1361
1362 #[test]
1363 fn test_resolve_since_timestamp_upstream_log_epochs_with_gap_before_pending_barriers() {
1364 let upstream_log_epochs = vec![(vec![61, 62, 63, 64], 65)];
1365 let pending_upstream_barriers = [
1366 BarrierInfo {
1367 prev_epoch: TracedEpoch::new(Epoch(66)),
1368 curr_epoch: TracedEpoch::new(Epoch(67)),
1369 kind: BarrierKind::Barrier,
1370 },
1371 BarrierInfo {
1372 prev_epoch: TracedEpoch::new(Epoch(67)),
1373 curr_epoch: TracedEpoch::new(Epoch(68)),
1374 kind: BarrierKind::Barrier,
1375 },
1376 BarrierInfo {
1377 prev_epoch: TracedEpoch::new(Epoch(68)),
1378 curr_epoch: TracedEpoch::new(Epoch(69)),
1379 kind: BarrierKind::Barrier,
1380 },
1381 BarrierInfo {
1382 prev_epoch: TracedEpoch::new(Epoch(69)),
1383 curr_epoch: TracedEpoch::new(Epoch(70)),
1384 kind: BarrierKind::Barrier,
1385 },
1386 ];
1387
1388 let (initial_barrier, barriers) =
1389 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1390 &upstream_log_epochs,
1391 pending_upstream_barriers.iter(),
1392 60,
1393 70,
1394 )
1395 .unwrap();
1396
1397 assert_eq!(
1398 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1399 (60, 61)
1400 );
1401 assert!(initial_barrier.kind.is_checkpoint());
1402 assert_eq!(
1403 barriers
1404 .iter()
1405 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1406 .collect::<Vec<_>>(),
1407 vec![
1408 (61, 62),
1409 (62, 63),
1410 (63, 64),
1411 (64, 65),
1412 (65, 66),
1413 (66, 67),
1414 (67, 68),
1415 (68, 69),
1416 (69, 70)
1417 ]
1418 );
1419 assert_eq!(
1420 barriers
1421 .iter()
1422 .map(|barrier| match &barrier.kind {
1423 BarrierKind::Checkpoint(epochs) => Some(epochs.clone()),
1424 _ => None,
1425 })
1426 .collect::<Vec<_>>(),
1427 vec![
1428 None,
1429 None,
1430 None,
1431 None,
1432 Some(vec![61, 62, 63, 64, 65]),
1433 None,
1434 None,
1435 None,
1436 None
1437 ]
1438 );
1439 }
1440
1441 #[test]
1442 fn test_resolve_since_timestamp_upstream_log_epochs_without_pending_barriers() {
1443 let upstream_log_epochs = vec![(vec![61, 62, 63, 64], 65)];
1444
1445 let (initial_barrier, barriers) =
1446 CreatingStreamingJobControl::resolve_since_timestamp_upstream_log_epochs(
1447 &upstream_log_epochs,
1448 [].iter(),
1449 60,
1450 66,
1451 )
1452 .unwrap();
1453
1454 assert_eq!(
1455 (initial_barrier.prev_epoch(), initial_barrier.curr_epoch()),
1456 (60, 61)
1457 );
1458 assert!(initial_barrier.kind.is_checkpoint());
1459 assert_eq!(
1460 barriers
1461 .iter()
1462 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
1463 .collect::<Vec<_>>(),
1464 vec![(61, 62), (62, 63), (63, 64), (64, 65), (65, 66)]
1465 );
1466 assert_eq!(
1467 barriers
1468 .iter()
1469 .map(|barrier| match &barrier.kind {
1470 BarrierKind::Checkpoint(epochs) => Some(epochs.clone()),
1471 _ => None,
1472 })
1473 .collect::<Vec<_>>(),
1474 vec![None, None, None, None, Some(vec![61, 62, 63, 64, 65])]
1475 );
1476 }
1477}