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