1use std::collections::hash_map::Entry;
16use std::collections::{HashMap, HashSet, VecDeque};
17use std::mem::{replace, take};
18use std::time::Duration;
19
20use itertools::Itertools;
21use risingwave_common::hash::ActorId;
22use risingwave_common::util::epoch::Epoch;
23use risingwave_pb::hummock::HummockVersionStats;
24use risingwave_pb::id::{FragmentId, PartialGraphId};
25use risingwave_pb::stream_plan::StartFragmentBackfillMutation;
26use risingwave_pb::stream_plan::barrier::PbBarrierKind;
27use risingwave_pb::stream_plan::barrier_mutation::Mutation;
28use risingwave_pb::stream_service::barrier_complete_response::{
29 CreateMviewProgress, PbCreateMviewProgress,
30};
31use tracing::warn;
32
33use crate::barrier::checkpoint::independent_job::creating_job::CreatingJobInfo;
34use crate::barrier::command::{ThrottleConfigMap, extract_throttle_config};
35use crate::barrier::partial_graph::PartialGraphManager;
36use crate::barrier::progress::{CreateMviewProgressTracker, TrackingJob};
37use crate::barrier::{BarrierInfo, BarrierKind, TracedEpoch};
38use crate::controller::fragment::InflightFragmentInfo;
39
40#[derive(Debug)]
41pub(super) struct CreateMviewLogStoreProgressTracker {
42 ongoing_actors: HashMap<ActorId, u64>,
44 finished_actors: HashSet<ActorId>,
45}
46
47impl CreateMviewLogStoreProgressTracker {
48 pub(super) fn new(actors: impl Iterator<Item = ActorId>, pending_barrier_lag: u64) -> Self {
49 Self {
50 ongoing_actors: HashMap::from_iter(actors.map(|actor| (actor, pending_barrier_lag))),
51 finished_actors: HashSet::new(),
52 }
53 }
54
55 pub(super) fn gen_backfill_progress(&self) -> String {
56 let sum = self.ongoing_actors.values().sum::<u64>() as f64;
57 let count = if self.ongoing_actors.is_empty() {
58 1
59 } else {
60 self.ongoing_actors.len()
61 } as f64;
62 let avg = sum / count;
63 let avg_lag_time = Duration::from_millis(Epoch(avg as _).physical_time());
64 format!(
65 "actor: {}/{}, avg lag {:?}",
66 self.finished_actors.len(),
67 self.ongoing_actors.len() + self.finished_actors.len(),
68 avg_lag_time
69 )
70 }
71
72 fn update(&mut self, progress: impl IntoIterator<Item = &PbCreateMviewProgress>) {
73 for progress in progress {
74 match self.ongoing_actors.entry(progress.backfill_actor_id) {
75 Entry::Occupied(mut entry) => {
76 if progress.done {
77 entry.remove_entry();
78 assert!(
79 self.finished_actors.insert(progress.backfill_actor_id),
80 "non-duplicate"
81 );
82 } else {
83 *entry.get_mut() = progress.pending_epoch_lag as _;
84 }
85 }
86 Entry::Vacant(_) => {
87 if cfg!(debug_assertions) {
88 panic!(
89 "reporting progress on non-inflight actor: {:?} {:?}",
90 progress, self
91 );
92 } else {
93 warn!(?progress, progress_tracker = ?self, "reporting progress on non-inflight actor");
94 }
95 }
96 }
97 }
98 }
99
100 pub(super) fn is_finished(&self) -> bool {
101 self.ongoing_actors.is_empty()
102 }
103}
104
105#[derive(Debug)]
106pub(super) enum CreatingStreamingJobStatus {
107 ConsumingSnapshot {
111 prev_epoch_fake_physical_time: u64,
112 pending_upstream_barriers: Vec<BarrierInfo>,
113 version_stats: HummockVersionStats,
114 create_mview_tracker: CreateMviewProgressTracker,
115 snapshot_backfill_actors: HashSet<ActorId>,
116 snapshot_epoch: u64,
117 info: CreatingJobInfo,
118 pending_non_checkpoint_barriers: Vec<u64>,
120 },
121 ConsumingLogStore {
125 tracking_job: TrackingJob,
126 info: CreatingJobInfo,
127 log_store_progress_tracker: CreateMviewLogStoreProgressTracker,
128 pending_barriers: VecDeque<BarrierInfo>,
129 },
130 Finishing(u64, TrackingJob),
134 PlaceHolder,
135}
136
137impl CreatingStreamingJobStatus {
138 pub(super) fn update_progress(
139 &mut self,
140 create_mview_progress: impl IntoIterator<Item = &CreateMviewProgress>,
141 ) {
142 match self {
143 &mut Self::ConsumingSnapshot {
144 ref mut create_mview_tracker,
145 ref version_stats,
146 ref mut prev_epoch_fake_physical_time,
147 ref mut pending_upstream_barriers,
148 ref mut pending_non_checkpoint_barriers,
149 ref snapshot_epoch,
150 ..
151 } => {
152 for progress in create_mview_progress {
153 create_mview_tracker.apply_progress(progress, version_stats);
154 }
155 if create_mview_tracker.is_finished() {
156 pending_non_checkpoint_barriers.push(*snapshot_epoch);
157
158 let prev_epoch = Epoch::from_physical_time(*prev_epoch_fake_physical_time);
159 let pending_barriers: VecDeque<_> = [BarrierInfo {
160 curr_epoch: TracedEpoch::new(Epoch(*snapshot_epoch)),
161 prev_epoch: TracedEpoch::new(prev_epoch),
162 kind: BarrierKind::Checkpoint(take(pending_non_checkpoint_barriers)),
163 }]
164 .into_iter()
165 .chain(pending_upstream_barriers.drain(..))
166 .collect();
167
168 let CreatingStreamingJobStatus::ConsumingSnapshot {
169 create_mview_tracker,
170 info,
171 snapshot_epoch,
172 snapshot_backfill_actors,
173 ..
174 } = replace(self, CreatingStreamingJobStatus::PlaceHolder)
175 else {
176 unreachable!()
177 };
178
179 let tracking_job = create_mview_tracker.into_tracking_job();
180
181 *self = CreatingStreamingJobStatus::ConsumingLogStore {
182 tracking_job,
183 info,
184 log_store_progress_tracker: CreateMviewLogStoreProgressTracker::new(
185 snapshot_backfill_actors.iter().cloned(),
186 pending_barriers
187 .back()
188 .map(|barrier_info| {
189 barrier_info.prev_epoch().saturating_sub(snapshot_epoch)
190 })
191 .unwrap_or(0),
192 ),
193 pending_barriers,
194 };
195 }
196 }
197 CreatingStreamingJobStatus::ConsumingLogStore {
198 log_store_progress_tracker,
199 ..
200 } => {
201 log_store_progress_tracker.update(create_mview_progress);
202 }
203 CreatingStreamingJobStatus::Finishing(..) => {}
204 CreatingStreamingJobStatus::PlaceHolder => {
205 unreachable!()
206 }
207 }
208 }
209
210 pub(super) fn start_consume_upstream(&mut self, barrier_info: &BarrierInfo) -> CreatingJobInfo {
211 match self {
212 CreatingStreamingJobStatus::ConsumingSnapshot { .. } => {
213 unreachable!(
214 "should not start consuming upstream for a job that are consuming snapshot"
215 )
216 }
217 CreatingStreamingJobStatus::ConsumingLogStore { .. } => {
218 let prev_epoch = barrier_info.prev_epoch();
219 {
220 assert!(barrier_info.kind.is_checkpoint());
221 let CreatingStreamingJobStatus::ConsumingLogStore {
222 info, tracking_job, ..
223 } = replace(self, CreatingStreamingJobStatus::PlaceHolder)
224 else {
225 unreachable!()
226 };
227 *self = CreatingStreamingJobStatus::Finishing(prev_epoch, tracking_job);
228 info
229 }
230 }
231 CreatingStreamingJobStatus::Finishing { .. } => {
232 unreachable!("should not start consuming upstream for a job again")
233 }
234 CreatingStreamingJobStatus::PlaceHolder => {
235 unreachable!()
236 }
237 }
238 }
239
240 pub(super) fn on_new_upstream_epoch(
241 &mut self,
242 partial_graph_manager: &PartialGraphManager,
243 partial_graph_id: PartialGraphId,
244 max_pending_barrier_num: usize,
245 barrier_info: &BarrierInfo,
246 mutation: Option<Mutation>, ) -> Vec<(BarrierInfo, Option<Mutation>)> {
248 let resolve_initial_barrier_num_to_inject = || {
249 max_pending_barrier_num
250 .saturating_sub(partial_graph_manager.pending_barrier_num(partial_graph_id))
251 };
252 match self {
253 CreatingStreamingJobStatus::ConsumingSnapshot {
254 pending_upstream_barriers,
255 prev_epoch_fake_physical_time,
256 pending_non_checkpoint_barriers,
257 create_mview_tracker,
258 ..
259 } => {
260 let mutation = mutation.or_else(|| {
261 let pending_backfill_nodes = create_mview_tracker
262 .take_pending_backfill_nodes()
263 .collect_vec();
264 if pending_backfill_nodes.is_empty() {
265 None
266 } else {
267 Some(Mutation::StartFragmentBackfill(
268 StartFragmentBackfillMutation {
269 fragment_ids: pending_backfill_nodes,
270 },
271 ))
272 }
273 });
274 let barrier_num_to_inject = resolve_initial_barrier_num_to_inject();
275 pending_upstream_barriers.push(barrier_info.clone());
276 if barrier_num_to_inject == 0 && mutation.is_none() {
279 return vec![];
280 }
281 vec![(
282 CreatingStreamingJobStatus::new_fake_barrier(
283 prev_epoch_fake_physical_time,
284 pending_non_checkpoint_barriers,
285 match barrier_info.kind {
286 BarrierKind::Barrier => PbBarrierKind::Barrier,
287 BarrierKind::Checkpoint(_) => PbBarrierKind::Checkpoint,
288 BarrierKind::Initial => {
289 unreachable!("upstream new epoch should not be initial")
290 }
291 },
292 ),
293 mutation,
294 )]
295 }
296 CreatingStreamingJobStatus::ConsumingLogStore {
297 pending_barriers, ..
298 } => {
299 drain_pending_barriers(
303 pending_barriers,
304 barrier_info.clone(),
305 resolve_initial_barrier_num_to_inject(),
306 )
307 .into_iter()
308 .map(|barrier_info| (barrier_info, None))
309 .collect()
310 }
311 CreatingStreamingJobStatus::Finishing { .. } => vec![],
312 CreatingStreamingJobStatus::PlaceHolder => {
313 unreachable!()
314 }
315 }
316 }
317
318 pub(super) fn new_fake_barrier(
319 prev_epoch_fake_physical_time: &mut u64,
320 pending_non_checkpoint_barriers: &mut Vec<u64>,
321 kind: PbBarrierKind,
322 ) -> BarrierInfo {
323 super::super::new_fake_barrier(
324 prev_epoch_fake_physical_time,
325 pending_non_checkpoint_barriers,
326 kind,
327 )
328 }
329
330 pub(super) fn fragment_infos(&self) -> Option<&HashMap<FragmentId, InflightFragmentInfo>> {
331 match self {
332 CreatingStreamingJobStatus::ConsumingSnapshot { info, .. }
333 | CreatingStreamingJobStatus::ConsumingLogStore { info, .. } => {
334 Some(&info.fragment_infos)
335 }
336 CreatingStreamingJobStatus::Finishing(..) => None,
337 CreatingStreamingJobStatus::PlaceHolder => {
338 unreachable!()
339 }
340 }
341 }
342
343 pub(super) fn pre_apply_throttle(
344 &mut self,
345 config: &mut ThrottleConfigMap,
346 ) -> Option<Mutation> {
347 let fragment_infos = match self {
348 CreatingStreamingJobStatus::ConsumingSnapshot { info, .. }
349 | CreatingStreamingJobStatus::ConsumingLogStore { info, .. } => {
350 &mut info.fragment_infos
351 }
352 CreatingStreamingJobStatus::Finishing(..) => return None,
353 CreatingStreamingJobStatus::PlaceHolder => {
354 unreachable!()
355 }
356 };
357
358 extract_throttle_config(config, |fragment_id, stream_node| {
359 if let Some(fragment_info) = fragment_infos.get_mut(&fragment_id) {
360 fragment_info.nodes = stream_node.clone();
361 true
362 } else {
363 false
364 }
365 })
366 }
367}
368
369fn drain_pending_barriers(
370 pending_barriers: &mut VecDeque<BarrierInfo>,
371 new_upstream_barrier: BarrierInfo,
372 barrier_num_to_inject: usize,
373) -> Vec<BarrierInfo> {
374 pending_barriers.push_back(new_upstream_barrier);
375 let barrier_count = pending_barriers.len().min(barrier_num_to_inject);
376 pending_barriers.drain(..barrier_count).collect()
377}
378
379#[cfg(test)]
380mod tests {
381 use risingwave_pb::stream_plan::PbStreamNode;
382
383 use super::*;
384
385 fn barrier(prev_epoch: u64, curr_epoch: u64) -> BarrierInfo {
386 BarrierInfo {
387 prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
388 curr_epoch: TracedEpoch::new(Epoch(curr_epoch)),
389 kind: BarrierKind::Barrier,
390 }
391 }
392
393 fn epochs(barriers: &[BarrierInfo]) -> Vec<(u64, u64)> {
394 barriers
395 .iter()
396 .map(|barrier| (barrier.prev_epoch(), barrier.curr_epoch()))
397 .collect()
398 }
399
400 #[test]
401 fn test_drain_pending_barriers_with_available_capacity() {
402 let mut pending_barriers = VecDeque::from([barrier(1, 2), barrier(2, 3), barrier(3, 4)]);
403
404 let injected = drain_pending_barriers(&mut pending_barriers, barrier(4, 5), 0);
405 assert!(injected.is_empty());
406 assert_eq!(
407 epochs(pending_barriers.make_contiguous()),
408 vec![(1, 2), (2, 3), (3, 4), (4, 5)]
409 );
410
411 let injected = drain_pending_barriers(&mut pending_barriers, barrier(5, 6), 2);
412 assert_eq!(epochs(&injected), vec![(1, 2), (2, 3)]);
413 assert_eq!(
414 epochs(pending_barriers.make_contiguous()),
415 vec![(3, 4), (4, 5), (5, 6)]
416 );
417
418 let injected = drain_pending_barriers(&mut pending_barriers, barrier(6, 7), 2);
419 assert_eq!(epochs(&injected), vec![(3, 4), (4, 5)]);
420 assert_eq!(
421 epochs(pending_barriers.make_contiguous()),
422 vec![(5, 6), (6, 7)]
423 );
424
425 let injected = drain_pending_barriers(&mut pending_barriers, barrier(7, 8), 2);
426 assert_eq!(epochs(&injected), vec![(5, 6), (6, 7)]);
427 assert_eq!(epochs(pending_barriers.make_contiguous()), vec![(7, 8)]);
428 }
429
430 #[test]
431 fn test_drain_pending_barriers_without_backlog() {
432 let mut pending_barriers = VecDeque::new();
433
434 let injected = drain_pending_barriers(&mut pending_barriers, barrier(1, 2), 100);
435
436 assert_eq!(epochs(&injected), vec![(1, 2)]);
437 assert!(pending_barriers.is_empty());
438 }
439
440 #[test]
441 fn test_pre_apply_throttle_before_merge() {
442 let job_id = risingwave_common::id::JobId::new(1);
443 let fragment_id = FragmentId::new(1);
444 let old_node = PbStreamNode {
445 identity: "old".to_owned(),
446 ..Default::default()
447 };
448 let new_node = PbStreamNode {
449 identity: "new".to_owned(),
450 ..Default::default()
451 };
452 let fragment_infos = HashMap::from([(
453 fragment_id,
454 InflightFragmentInfo {
455 fragment_id,
456 distribution_type: risingwave_meta_model::fragment::DistributionType::Single,
457 fragment_type_mask: Default::default(),
458 vnode_count: 1,
459 nodes: old_node,
460 actors: Default::default(),
461 state_table_ids: Default::default(),
462 },
463 )]);
464 let mut status = CreatingStreamingJobStatus::ConsumingLogStore {
465 tracking_job: TrackingJob::recovered(job_id, &fragment_infos),
466 info: CreatingJobInfo {
467 fragment_infos,
468 upstream_fragment_downstreams: Default::default(),
469 downstreams: Default::default(),
470 snapshot_backfill_upstream_tables: Default::default(),
471 stream_actors: Default::default(),
472 },
473 log_store_progress_tracker: CreateMviewLogStoreProgressTracker::new(
474 std::iter::empty(),
475 0,
476 ),
477 pending_barriers: Default::default(),
478 };
479 let mut config = HashMap::from([(
480 fragment_id,
481 (
482 risingwave_pb::stream_plan::throttle_mutation::ThrottleConfig {
483 rate_limit: Some(1_000),
484 throttle_type: Default::default(),
485 },
486 new_node.clone(),
487 ),
488 )]);
489
490 assert!(status.pre_apply_throttle(&mut config).is_some());
491 assert!(config.is_empty());
492
493 let info = status.start_consume_upstream(&BarrierInfo {
494 prev_epoch: TracedEpoch::new(Epoch(1)),
495 curr_epoch: TracedEpoch::new(Epoch(2)),
496 kind: BarrierKind::Checkpoint(vec![1]),
497 });
498 assert_eq!(info.fragment_infos[&fragment_id].nodes, new_node);
499 }
500}