risingwave_stream/executor/backfill/arrangement_backfill.rs
1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use either::Either;
18use futures::stream::{select_all, select_with_strategy};
19use futures::{TryStreamExt, stream};
20use itertools::Itertools;
21use risingwave_common::array::{DataChunk, Op};
22use risingwave_common::bail;
23use risingwave_common::hash::{VirtualNode, VnodeBitmapExt};
24use risingwave_common::util::chunk_coalesce::DataChunkBuilder;
25use risingwave_common_rate_limit::{MonitoredRateLimiter, RateLimit, RateLimiter};
26use risingwave_pb::common::ThrottleType;
27use risingwave_storage::row_serde::value_serde::ValueRowSerde;
28use risingwave_storage::store::PrefetchOptions;
29
30use crate::common::table::state_table::ReplicatedStateTable;
31#[cfg(debug_assertions)]
32use crate::executor::backfill::utils::METADATA_STATE_LEN;
33use crate::executor::backfill::utils::{
34 BackfillProgressPerVnode, BackfillState, compute_bounds, create_builder,
35 get_progress_per_vnode, mapping_chunk, mapping_message, mark_chunk_ref_by_vnode,
36 persist_state_per_vnode, update_pos_by_vnode,
37};
38use crate::executor::prelude::*;
39use crate::task::{CreateMviewProgressReporter, FragmentId};
40
41type Builders = HashMap<VirtualNode, DataChunkBuilder>;
42
43/// Similar to [`super::no_shuffle_backfill::BackfillExecutor`].
44/// Main differences:
45/// - [`ArrangementBackfillExecutor`] can reside on a different CN, so it can be scaled
46/// independently.
47/// - To synchronize upstream shared buffer, it is initialized with a [`ReplicatedStateTable`].
48pub struct ArrangementBackfillExecutor<S: StateStore, SD: ValueRowSerde> {
49 /// Upstream table
50 upstream_table: ReplicatedStateTable<S, SD>,
51
52 /// Upstream with the same schema with the upstream table.
53 upstream: Executor,
54
55 /// Internal state table for persisting state of backfill state.
56 state_table: StateTable<S>,
57
58 /// The column indices need to be forwarded to the downstream from the upstream and table scan.
59 output_indices: Vec<usize>,
60
61 progress: CreateMviewProgressReporter,
62
63 actor_id: ActorId,
64
65 metrics: Arc<StreamingMetrics>,
66
67 chunk_size: usize,
68
69 rate_limiter: MonitoredRateLimiter,
70
71 /// Fragment id of the fragment this backfill node belongs to.
72 fragment_id: FragmentId,
73}
74
75impl<S, SD> ArrangementBackfillExecutor<S, SD>
76where
77 S: StateStore,
78 SD: ValueRowSerde,
79{
80 #[allow(clippy::too_many_arguments)]
81 #[allow(dead_code)]
82 pub fn new(
83 upstream_table: ReplicatedStateTable<S, SD>,
84 upstream: Executor,
85 state_table: StateTable<S>,
86 output_indices: Vec<usize>,
87 progress: CreateMviewProgressReporter,
88 metrics: Arc<StreamingMetrics>,
89 chunk_size: usize,
90 rate_limit: RateLimit,
91 fragment_id: FragmentId,
92 ) -> Self {
93 let rate_limiter = RateLimiter::new(rate_limit).monitored(upstream_table.table_id());
94 Self {
95 upstream_table,
96 upstream,
97 state_table,
98 output_indices,
99 actor_id: progress.actor_id(),
100 progress,
101 metrics,
102 chunk_size,
103 rate_limiter,
104 fragment_id,
105 }
106 }
107
108 #[try_stream(ok = Message, error = StreamExecutorError)]
109 async fn execute_inner(mut self) {
110 tracing::debug!("backfill executor started");
111 // The primary key columns, in the output columns of the upstream_table scan.
112 // Table scan scans a subset of the columns of the upstream table.
113 let pk_in_output_indices = self.upstream_table.pk_in_output_indices().unwrap();
114 #[cfg(debug_assertions)]
115 let state_len = self.upstream_table.pk_indices().len() + METADATA_STATE_LEN;
116 let pk_order = self.upstream_table.pk_serde().get_order_types().to_vec();
117 let upstream_table_id = self.upstream_table.table_id();
118 let mut upstream_table = self.upstream_table;
119 let vnodes = upstream_table.vnodes().clone();
120
121 // These builders will build data chunks.
122 // We must supply them with the full datatypes which correspond to
123 // pk + output_indices.
124 let snapshot_data_types = self
125 .upstream
126 .schema()
127 .fields()
128 .iter()
129 .map(|field| field.data_type.clone())
130 .collect_vec();
131 let mut builders: Builders = upstream_table
132 .vnodes()
133 .iter_vnodes()
134 .map(|vnode| {
135 let builder = create_builder(
136 self.rate_limiter.rate_limit(),
137 self.chunk_size,
138 snapshot_data_types.clone(),
139 );
140 (vnode, builder)
141 })
142 .collect();
143
144 let mut upstream = self.upstream.execute();
145
146 // Poll the upstream to get the first barrier.
147 let first_barrier = expect_first_barrier(&mut upstream).await?;
148 let mut global_pause = first_barrier.is_pause_on_startup();
149 let mut backfill_paused = first_barrier.is_backfill_pause_on_startup(self.fragment_id);
150 let first_epoch = first_barrier.epoch;
151 let is_newly_added = first_barrier.is_newly_added(self.actor_id);
152 // The first barrier message should be propagated.
153 yield Message::Barrier(first_barrier);
154
155 self.state_table.init_epoch(first_epoch).await?;
156
157 let progress_per_vnode = get_progress_per_vnode(&self.state_table).await?;
158
159 let is_completely_finished = progress_per_vnode.iter().all(|(_, p)| {
160 matches!(
161 p.current_state(),
162 &BackfillProgressPerVnode::Completed { .. }
163 )
164 });
165 if is_completely_finished {
166 assert!(!is_newly_added);
167 }
168
169 upstream_table.init_epoch(first_epoch).await?;
170
171 let mut backfill_state: BackfillState = progress_per_vnode.into();
172
173 let to_backfill = !is_completely_finished;
174
175 // If no need backfill, but state was still "unfinished" we need to finish it.
176 // So we just update the state + progress to meta at the next barrier to finish progress,
177 // and forward other messages.
178 //
179 // Reason for persisting on second barrier rather than first:
180 // We can't update meta with progress as finished until state_table
181 // has been updated.
182 // We also can't update state_table in first epoch, since state_table
183 // expects to have been initialized in previous epoch.
184
185 // The epoch used to snapshot read upstream mv.
186 let mut snapshot_read_epoch;
187
188 // Keep track of rows from the snapshot.
189 let mut total_snapshot_processed_rows: u64 = backfill_state.get_snapshot_row_count();
190
191 // Arrangement Backfill Algorithm:
192 //
193 // backfill_stream
194 // / \
195 // upstream snapshot
196 //
197 // We construct a backfill stream with upstream as its left input and mv snapshot read
198 // stream as its right input. When a chunk comes from upstream, we will buffer it.
199 //
200 // When a barrier comes from upstream:
201 // Immediately break out of backfill loop.
202 // - For each row of the upstream chunk buffer, compute vnode.
203 // - Get the `current_pos` corresponding to the vnode. Forward it to downstream if its pk
204 // <= `current_pos`, otherwise ignore it.
205 // - Flush all buffered upstream_chunks to replicated state table.
206 // - Update the `snapshot_read_epoch`.
207 // - Reconstruct the whole backfill stream with upstream and new mv snapshot read stream
208 // with the `snapshot_read_epoch`.
209 //
210 // When a chunk comes from snapshot, we forward it to the downstream and raise
211 // `current_pos`.
212 //
213 // When we reach the end of the snapshot read stream, it means backfill has been
214 // finished.
215 //
216 // Once the backfill loop ends, we forward the upstream directly to the downstream.
217 if to_backfill {
218 let mut upstream_chunk_buffer: Vec<StreamChunk> = vec![];
219 let mut pending_barrier: Option<Barrier> = None;
220
221 let metrics = self
222 .metrics
223 .new_backfill_metrics(upstream_table_id, self.actor_id);
224
225 'backfill_loop: loop {
226 let mut cur_barrier_snapshot_processed_rows: u64 = 0;
227 let mut cur_barrier_upstream_processed_rows: u64 = 0;
228 let mut snapshot_read_complete = false;
229 let mut has_snapshot_read = false;
230
231 // NOTE(kwannoel): Scope it so that immutable reference to `upstream_table` can be
232 // dropped. Then we can write to `upstream_table` on barrier in the
233 // next block.
234 {
235 let left_upstream = upstream.by_ref().map(Either::Left);
236
237 // Check if stream paused
238 let paused = global_pause
239 || backfill_paused
240 || matches!(self.rate_limiter.rate_limit(), RateLimit::Pause);
241 // Create the snapshot stream
242 let right_snapshot = pin!(
243 Self::make_snapshot_stream(
244 &upstream_table,
245 backfill_state.clone(), // FIXME: Use mutable reference instead.
246 paused,
247 &self.rate_limiter,
248 )
249 .map(Either::Right)
250 );
251
252 // Prefer to select upstream, so we can stop snapshot stream as soon as the
253 // barrier comes.
254 let mut backfill_stream =
255 select_with_strategy(left_upstream, right_snapshot, |_: &mut ()| {
256 stream::PollNext::Left
257 });
258
259 #[for_await]
260 for either in &mut backfill_stream {
261 match either {
262 // Upstream
263 Either::Left(msg) => {
264 match msg? {
265 Message::Barrier(barrier) => {
266 // We have to process the barrier outside of the loop.
267 // This is because our state_table reference is still live
268 // here, we have to break the loop to drop it,
269 // so we can do replication of upstream state_table.
270 pending_barrier = Some(barrier);
271
272 // Break the for loop and start a new snapshot read stream.
273 break;
274 }
275 Message::Chunk(chunk) => {
276 // Buffer the upstream chunk.
277 upstream_chunk_buffer.push(chunk.compact_vis());
278 }
279 Message::Watermark(_) => {
280 // Ignore watermark during backfill.
281 }
282 }
283 }
284 // Snapshot read
285 Either::Right(msg) => {
286 has_snapshot_read = true;
287 match msg? {
288 None => {
289 // Consume remaining rows in the builder.
290 for (vnode, builder) in &mut builders {
291 if let Some(data_chunk) = builder.consume_all() {
292 let chunk = Self::handle_snapshot_chunk(
293 data_chunk,
294 *vnode,
295 &pk_in_output_indices,
296 &mut backfill_state,
297 &mut cur_barrier_snapshot_processed_rows,
298 &mut total_snapshot_processed_rows,
299 &self.output_indices,
300 )?;
301 tracing::trace!(
302 source = "snapshot",
303 state = "finish_backfill_stream",
304 action = "drain_snapshot_buffers",
305 ?vnode,
306 "{:#?}",
307 chunk,
308 );
309 yield Message::Chunk(chunk);
310 }
311 }
312
313 // End of the snapshot read stream.
314 // We should not mark the chunk anymore,
315 // otherwise, we will ignore some rows
316 // in the buffer. Here we choose to never mark the chunk.
317 // Consume with the renaming stream buffer chunk without
318 // mark.
319 for chunk in upstream_chunk_buffer.drain(..) {
320 let chunk_cardinality = chunk.cardinality() as u64;
321 cur_barrier_upstream_processed_rows +=
322 chunk_cardinality;
323 let chunk = mapping_chunk(chunk, &self.output_indices);
324 tracing::trace!(
325 source = "upstream",
326 state = "finish_backfill_stream",
327 action = "drain_upstream_buffer",
328 "{:#?}",
329 chunk,
330 );
331 yield Message::Chunk(chunk);
332 }
333 metrics
334 .backfill_snapshot_read_row_count
335 .inc_by(cur_barrier_snapshot_processed_rows);
336 metrics
337 .backfill_upstream_output_row_count
338 .inc_by(cur_barrier_upstream_processed_rows);
339 break 'backfill_loop;
340 }
341 Some((vnode, row)) => {
342 let builder = builders.get_mut(&vnode).unwrap();
343 if let Some(chunk) = builder.append_one_row(row) {
344 let chunk = Self::handle_snapshot_chunk(
345 chunk,
346 vnode,
347 &pk_in_output_indices,
348 &mut backfill_state,
349 &mut cur_barrier_snapshot_processed_rows,
350 &mut total_snapshot_processed_rows,
351 &self.output_indices,
352 )?;
353 tracing::trace!(
354 source = "snapshot",
355 state = "process_backfill_stream",
356 action = "drain_full_snapshot_buffer",
357 "{:#?}",
358 chunk,
359 );
360 yield Message::Chunk(chunk);
361 }
362 }
363 }
364 }
365 }
366 }
367
368 // Before processing barrier, if did not snapshot read,
369 // do a snapshot read first.
370 // This is so we don't lose the tombstone iteration progress.
371 // Or if s3 read latency is high, we don't fail to read from s3.
372 //
373 // If paused, we can't read any snapshot records, skip this.
374 //
375 // If rate limit is set, respect the rate limit, check if we can read,
376 // If we can't, skip it. If no rate limit set, we can read.
377 let rate_limit_ready = self.rate_limiter.check(1).is_ok();
378 if !has_snapshot_read && !paused && rate_limit_ready {
379 debug_assert!(builders.values().all(|b| b.is_empty()));
380 let (_, snapshot) = backfill_stream.into_inner();
381 #[for_await]
382 for msg in snapshot {
383 let Either::Right(msg) = msg else {
384 bail!("BUG: snapshot_read contains upstream messages");
385 };
386 match msg? {
387 None => {
388 // End of the snapshot read stream.
389 // We let the barrier handling logic take care of upstream updates:
390 // it must forward all buffered upstream chunks without marking.
391 // But we still want to exit backfill loop, so we mark snapshot read complete.
392 snapshot_read_complete = true;
393 break;
394 }
395 Some((vnode, row)) => {
396 let builder = builders.get_mut(&vnode).unwrap();
397 if let Some(chunk) = builder.append_one_row(row) {
398 let chunk = Self::handle_snapshot_chunk(
399 chunk,
400 vnode,
401 &pk_in_output_indices,
402 &mut backfill_state,
403 &mut cur_barrier_snapshot_processed_rows,
404 &mut total_snapshot_processed_rows,
405 &self.output_indices,
406 )?;
407 tracing::trace!(
408 source = "snapshot",
409 state = "process_backfill_stream",
410 action = "snapshot_read_at_least_one",
411 "{:#?}",
412 chunk,
413 );
414 yield Message::Chunk(chunk);
415 }
416
417 break;
418 }
419 }
420 }
421 }
422 }
423
424 // Process barrier
425 // When we break out of inner backfill_stream loop, it means we have a barrier.
426 // If there are no updates and there are no snapshots left,
427 // we already finished backfill and should have exited the outer backfill loop.
428 let barrier = match pending_barrier.take() {
429 Some(barrier) => barrier,
430 None => bail!("BUG: current_backfill loop exited without a barrier"),
431 };
432
433 // Process barrier:
434 // - consume snapshot rows left in builder.
435 // - consume upstream buffer chunk
436 // - handle mutations
437 // - switch snapshot
438
439 // consume snapshot rows left in builder.
440 // NOTE(kwannoel): `zip_eq_debug` does not work here,
441 // we encounter "higher-ranked lifetime error".
442 for (vnode, chunk) in builders.iter_mut().map(|(vnode, b)| {
443 let chunk = b.consume_all().map(|chunk| {
444 let ops = vec![Op::Insert; chunk.capacity()];
445 StreamChunk::from_parts(ops, chunk)
446 });
447 (vnode, chunk)
448 }) {
449 if let Some(chunk) = chunk {
450 let chunk_cardinality = chunk.cardinality() as u64;
451 // Raise the current position.
452 // As snapshot read streams are ordered by pk, so we can
453 // just use the last row to update `current_pos`.
454 update_pos_by_vnode(
455 *vnode,
456 &chunk,
457 &pk_in_output_indices,
458 &mut backfill_state,
459 chunk_cardinality,
460 )?;
461
462 cur_barrier_snapshot_processed_rows += chunk_cardinality;
463 total_snapshot_processed_rows += chunk_cardinality;
464 let chunk = mapping_chunk(chunk, &self.output_indices);
465 tracing::trace!(
466 source = "snapshot",
467 state = "process_barrier",
468 action = "consume_remaining_snapshot",
469 "{:#?}",
470 chunk,
471 );
472 yield Message::Chunk(chunk);
473 }
474 }
475
476 // consume upstream buffer chunk
477 for chunk in upstream_chunk_buffer.drain(..) {
478 cur_barrier_upstream_processed_rows += chunk.cardinality() as u64;
479 if snapshot_read_complete {
480 // The snapshot read above reached the end of the snapshot, so every
481 // snapshot row has already been emitted and the buffered upstream
482 // chunks are entirely newer than the snapshot. We exit the backfill
483 // loop after this barrier and will never read the replicated table
484 // again, so rows filtered out by `current_pos` here would be lost.
485 // Forward the buffered chunks without marking, the same as when the
486 // snapshot stream ends inside the backfill stream above.
487 let chunk = mapping_chunk(chunk.clone(), &self.output_indices);
488 tracing::trace!(
489 source = "upstream",
490 state = "process_barrier",
491 action = "drain_upstream_buffer_after_snapshot_read_complete",
492 "{:#?}",
493 chunk,
494 );
495 yield Message::Chunk(chunk);
496 } else if backfill_state.has_progress() {
497 let chunk = mapping_chunk(
498 mark_chunk_ref_by_vnode(
499 &chunk,
500 &backfill_state,
501 &pk_in_output_indices,
502 &upstream_table,
503 &pk_order,
504 )?,
505 &self.output_indices,
506 );
507 tracing::trace!(
508 source = "upstream",
509 state = "process_barrier",
510 action = "consume_remaining_upstream",
511 "{:#?}",
512 chunk,
513 );
514 yield Message::Chunk(chunk);
515 }
516
517 // Replicate
518 upstream_table.write_chunk(chunk);
519 }
520
521 upstream_table
522 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
523 .await?;
524
525 metrics
526 .backfill_snapshot_read_row_count
527 .inc_by(cur_barrier_snapshot_processed_rows);
528 metrics
529 .backfill_upstream_output_row_count
530 .inc_by(cur_barrier_upstream_processed_rows);
531
532 // Update snapshot read epoch.
533 snapshot_read_epoch = barrier.epoch.prev;
534
535 // TODO(kwannoel): Not sure if this holds for arrangement backfill.
536 // May need to revisit it.
537 // Need to check it after scale-in / scale-out.
538 self.progress.update(
539 barrier.epoch,
540 snapshot_read_epoch,
541 total_snapshot_processed_rows,
542 );
543
544 // If the snapshot read is complete, the buffered upstream chunks have been forwarded
545 // without marking above, including rows beyond `current_pos` or in vnodes that
546 // have not started. Persist all vnodes as finished on this barrier, so that the
547 // persisted progress is consistent with the forwarded rows. Otherwise, recovering
548 // from this barrier would scan these rows from the snapshot and emit them again.
549 if snapshot_read_complete {
550 for vnode in upstream_table.vnodes().iter_vnodes() {
551 backfill_state.finish_progress(vnode, upstream_table.pk_indices().len());
552 }
553 }
554
555 // Persist state on barrier
556 persist_state_per_vnode(
557 barrier.epoch,
558 &mut self.state_table,
559 &mut backfill_state,
560 #[cfg(debug_assertions)]
561 state_len,
562 vnodes.iter_vnodes(),
563 )
564 .await?;
565
566 tracing::trace!(
567 barrier = ?barrier,
568 "barrier persisted"
569 );
570
571 // handle mutations
572 if let Some(mutation) = barrier.mutation.as_deref() {
573 use crate::executor::Mutation;
574 match mutation {
575 Mutation::Pause => {
576 global_pause = true;
577 }
578 Mutation::Resume => {
579 global_pause = false;
580 }
581 Mutation::StartFragmentBackfill { fragment_ids } if backfill_paused => {
582 if fragment_ids.contains(&self.fragment_id) {
583 backfill_paused = false;
584 }
585 }
586 Mutation::Throttle(fragment_to_apply) => {
587 if let Some(entry) = fragment_to_apply.get(&self.fragment_id)
588 && entry.throttle_type() == ThrottleType::Backfill
589 {
590 let new_rate_limit = entry.rate_limit.into();
591 let old_rate_limit = self.rate_limiter.update(new_rate_limit);
592 if old_rate_limit != new_rate_limit {
593 tracing::info!(
594 old_rate_limit = ?old_rate_limit,
595 new_rate_limit = ?new_rate_limit,
596 %upstream_table_id,
597 actor_id = %self.actor_id,
598 "backfill rate limit changed",
599 );
600 builders = upstream_table
601 .vnodes()
602 .iter_vnodes()
603 .map(|vnode| {
604 let builder = create_builder(
605 new_rate_limit,
606 self.chunk_size,
607 snapshot_data_types.clone(),
608 );
609 (vnode, builder)
610 })
611 .collect();
612 }
613 }
614 }
615 _ => {}
616 }
617 }
618
619 yield Message::Barrier(barrier);
620
621 // We will switch snapshot at the start of the next iteration of the backfill loop.
622 // Unless snapshot read is already completed.
623 if snapshot_read_complete {
624 break 'backfill_loop;
625 }
626 }
627 }
628
629 tracing::debug!("snapshot read finished, wait to commit state on next barrier");
630
631 // Update our progress as finished in state table.
632
633 // Wait for first barrier to come after backfill is finished.
634 // So we can update our progress + persist the status.
635 while let Some(Ok(msg)) = upstream.next().await {
636 if let Some(msg) = mapping_message(msg, &self.output_indices) {
637 // If not finished then we need to update state, otherwise no need.
638 if let Message::Barrier(barrier) = &msg {
639 if is_completely_finished {
640 // If already finished, no need to persist any state. But we need to advance the epoch anyway
641 self.state_table
642 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
643 .await?;
644 } else {
645 // If snapshot was empty, we do not need to backfill,
646 // but we still need to persist the finished state.
647 // We currently persist it on the second barrier here rather than first.
648 // This is because we can't update state table in first epoch,
649 // since it expects to have been initialized in previous epoch
650 // (there's no epoch before the first epoch).
651 for vnode in upstream_table.vnodes().iter_vnodes() {
652 backfill_state
653 .finish_progress(vnode, upstream_table.pk_indices().len());
654 }
655
656 persist_state_per_vnode(
657 barrier.epoch,
658 &mut self.state_table,
659 &mut backfill_state,
660 #[cfg(debug_assertions)]
661 state_len,
662 vnodes.iter_vnodes(),
663 )
664 .await?;
665 }
666
667 self.progress
668 .finish(barrier.epoch, total_snapshot_processed_rows);
669 yield msg;
670 break;
671 }
672 // Allow other messages to pass through.
673 // We won't yield twice here, since if there's a barrier,
674 // we will always break out of the loop.
675 yield msg;
676 }
677 }
678
679 tracing::debug!("backfill finished");
680
681 // After progress finished + state persisted,
682 // we can forward messages directly to the downstream,
683 // as backfill is finished.
684 #[for_await]
685 for msg in upstream {
686 if let Some(msg) = mapping_message(msg?, &self.output_indices) {
687 if let Message::Barrier(barrier) = &msg {
688 // If already finished, no need persist any state, but we need to advance the epoch of the state table anyway.
689 self.state_table
690 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
691 .await?;
692 }
693 yield msg;
694 }
695 }
696 }
697
698 #[try_stream(ok = Option<(VirtualNode, OwnedRow)>, error = StreamExecutorError)]
699 async fn make_snapshot_stream<'a>(
700 upstream_table: &'a ReplicatedStateTable<S, SD>,
701 backfill_state: BackfillState,
702 paused: bool,
703 rate_limiter: &'a MonitoredRateLimiter,
704 ) {
705 if paused {
706 #[for_await]
707 for _ in tokio_stream::pending() {
708 bail!("BUG: paused stream should not yield");
709 }
710 } else {
711 // Checked the rate limit is not zero.
712 #[for_await]
713 for r in Self::snapshot_read_per_vnode(upstream_table, backfill_state) {
714 let r = r?;
715 rate_limiter.wait(1).await;
716 yield r;
717 }
718 }
719 }
720
721 fn handle_snapshot_chunk(
722 chunk: DataChunk,
723 vnode: VirtualNode,
724 pk_in_output_indices: &[usize],
725 backfill_state: &mut BackfillState,
726 cur_barrier_snapshot_processed_rows: &mut u64,
727 total_snapshot_processed_rows: &mut u64,
728 output_indices: &[usize],
729 ) -> StreamExecutorResult<StreamChunk> {
730 let chunk = StreamChunk::from_parts(vec![Op::Insert; chunk.capacity()], chunk);
731 // Raise the current position.
732 // As snapshot read streams are ordered by pk, so we can
733 // just use the last row to update `current_pos`.
734 let snapshot_row_count_delta = chunk.cardinality() as u64;
735 update_pos_by_vnode(
736 vnode,
737 &chunk,
738 pk_in_output_indices,
739 backfill_state,
740 snapshot_row_count_delta,
741 )?;
742
743 let chunk_cardinality = chunk.cardinality() as u64;
744 *cur_barrier_snapshot_processed_rows += chunk_cardinality;
745 *total_snapshot_processed_rows += chunk_cardinality;
746 Ok(mapping_chunk(chunk, output_indices))
747 }
748
749 /// Read snapshot per vnode.
750 /// These streams should be sorted in storage layer.
751 /// 1. Get row iterator / vnode.
752 /// 2. Merge it with `select_all`.
753 /// 3. Change it into a chunk iterator with `iter_chunks`.
754 /// This means it should fetch a row from each iterator to form a chunk.
755 ///
756 /// We interleave at chunk per vnode level rather than rows.
757 /// This is so that we can compute `current_pos` once per chunk, since they correspond to 1
758 /// vnode.
759 ///
760 /// The stream contains pairs of `(VirtualNode, StreamChunk)`.
761 /// The `VirtualNode` is the vnode that the chunk belongs to.
762 /// The `StreamChunk` is the chunk that contains the rows from the vnode.
763 /// If it's `None`, it means the vnode has no more rows for this snapshot read.
764 ///
765 /// The `snapshot_read_epoch` is supplied as a parameter for `state_table`.
766 /// It is required to ensure we read a fully-checkpointed snapshot the **first time**.
767 ///
768 /// The rows from upstream snapshot read will be buffered inside the `builder`.
769 /// If snapshot is dropped before its rows are consumed,
770 /// remaining data in `builder` must be flushed manually.
771 /// Otherwise when we scan a new snapshot, it is possible the rows in the `builder` would be
772 /// present, Then when we flush we contain duplicate rows.
773 #[try_stream(ok = Option<(VirtualNode, OwnedRow)>, error = StreamExecutorError)]
774 async fn snapshot_read_per_vnode(
775 upstream_table: &ReplicatedStateTable<S, SD>,
776 backfill_state: BackfillState,
777 ) {
778 let mut iterators = vec![];
779 for vnode in upstream_table.vnodes().iter_vnodes() {
780 let backfill_progress = backfill_state.get_progress(&vnode)?;
781 let current_pos = match backfill_progress {
782 BackfillProgressPerVnode::NotStarted => None,
783 BackfillProgressPerVnode::Completed { .. } => {
784 continue;
785 }
786 BackfillProgressPerVnode::InProgress { current_pos, .. } => {
787 Some(current_pos.clone())
788 }
789 };
790
791 let range_bounds = compute_bounds(upstream_table.pk_indices(), current_pos.clone());
792 if range_bounds.is_none() {
793 continue;
794 }
795 let range_bounds = range_bounds.unwrap();
796
797 tracing::trace!(
798 vnode = ?vnode,
799 current_pos = ?current_pos,
800 range_bounds = ?range_bounds,
801 "iter_with_vnode"
802 );
803 let vnode_row_iter = upstream_table
804 .iter_with_vnode(
805 vnode,
806 &range_bounds,
807 PrefetchOptions::prefetch_for_small_range_scan(),
808 )
809 .await?;
810
811 let vnode_row_iter = vnode_row_iter.map_ok(move |row| (vnode, row));
812
813 let vnode_row_iter = Box::pin(vnode_row_iter);
814
815 iterators.push(vnode_row_iter);
816 }
817
818 // TODO(kwannoel): We can provide an option between snapshot read in parallel vs serial.
819 let vnode_row_iter = select_all(iterators);
820
821 #[for_await]
822 for vnode_and_row in vnode_row_iter {
823 yield Some(vnode_and_row?);
824 }
825 yield None;
826 return Ok(());
827 }
828}
829
830impl<S, SD> Execute for ArrangementBackfillExecutor<S, SD>
831where
832 S: StateStore,
833 SD: ValueRowSerde,
834{
835 fn execute(self: Box<Self>) -> BoxedMessageStream {
836 self.execute_inner().boxed()
837 }
838}
839
840#[cfg(test)]
841mod tests {
842 use risingwave_common::array::stream_chunk::StreamChunkTestExt;
843 use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema, TableId};
844 use risingwave_common::row::{OwnedRow, Row};
845 use risingwave_common::types::DataType;
846 use risingwave_common::util::epoch::{EpochPair, test_epoch};
847 use risingwave_common::util::sort_util::OrderType;
848 use risingwave_common::util::value_encoding::BasicSerde;
849 use risingwave_pb::catalog::PbTable;
850 use risingwave_storage::memory::MemoryStateStore;
851
852 use super::*;
853 use crate::common::table::test_utils::{gen_pbtable, gen_pbtable_with_value_indices};
854 use crate::executor::test_utils::MockSource;
855 use crate::task::barrier_test_utils::LocalBarrierTestEnv;
856
857 const UPSTREAM_TABLE_ID: TableId = TableId::new(1);
858 const BACKFILL_STATE_TABLE_ID: TableId = TableId::new(2);
859
860 /// Upstream mv: `(k bigint primary key, v bigint)`.
861 fn upstream_table_catalog() -> PbTable {
862 gen_pbtable(
863 UPSTREAM_TABLE_ID,
864 vec![
865 ColumnDesc::unnamed(ColumnId::from(0), DataType::Int64),
866 ColumnDesc::unnamed(ColumnId::from(1), DataType::Int64),
867 ],
868 vec![OrderType::ascending()],
869 vec![0],
870 1,
871 )
872 }
873
874 /// Backfill state table: `| vnode | k | backfill_finished | row_count |`, keyed by vnode.
875 fn backfill_state_table_catalog() -> PbTable {
876 gen_pbtable_with_value_indices(
877 BACKFILL_STATE_TABLE_ID,
878 vec![
879 ColumnDesc::unnamed(ColumnId::from(0), VirtualNode::RW_TYPE),
880 ColumnDesc::unnamed(ColumnId::from(1), DataType::Int64),
881 ColumnDesc::unnamed(ColumnId::from(2), DataType::Boolean),
882 ColumnDesc::unnamed(ColumnId::from(3), DataType::Int64),
883 ],
884 vec![OrderType::ascending()],
885 vec![0],
886 1,
887 vec![1, 2, 3],
888 )
889 }
890
891 fn row(k: i64, v: i64) -> OwnedRow {
892 OwnedRow::new(vec![Some(k.into()), Some(v.into())])
893 }
894
895 fn upstream(messages: Vec<Message>) -> Executor {
896 MockSource::with_messages(messages)
897 .stop_on_finish(false)
898 .into_executor(
899 Schema::new(vec![
900 Field::unnamed(DataType::Int64),
901 Field::unnamed(DataType::Int64),
902 ]),
903 vec![0],
904 )
905 }
906
907 /// Commit `rows` to the upstream mv in the epoch `epoch`, as the upstream materialize
908 /// executor does.
909 async fn commit_upstream_rows(store: &MemoryStateStore, epoch: u64, rows: Vec<OwnedRow>) {
910 let mut upstream_mv = StateTable::from_table_catalog_inconsistent_op(
911 &upstream_table_catalog(),
912 store.clone(),
913 None,
914 )
915 .await;
916 let mut epoch = EpochPair::new_test_epoch(epoch);
917 upstream_mv.init_epoch(epoch).await.unwrap();
918 for row in rows {
919 upstream_mv.insert(row);
920 }
921 epoch.inc_for_test();
922 upstream_mv.commit_for_test(epoch).await.unwrap();
923 }
924
925 /// Prepare the committed upstream mv `{(1, 10)}` and the backfill progress
926 /// `InProgress { current_pos: 1, snapshot_row_count: 1 }` for the singleton vnode in epoch
927 /// `test_epoch(1)`, as if row 1 had been backfilled before a recovery.
928 async fn prepare_in_progress_backfill(store: &MemoryStateStore) {
929 commit_upstream_rows(store, test_epoch(1), vec![row(1, 10)]).await;
930
931 let mut state_table =
932 StateTable::from_table_catalog(&backfill_state_table_catalog(), store.clone(), None)
933 .await;
934 let mut epoch = EpochPair::new_test_epoch(test_epoch(1));
935 state_table.init_epoch(epoch).await.unwrap();
936 state_table.insert(OwnedRow::new(vec![
937 Some(VirtualNode::ZERO.to_scalar().into()),
938 Some(1_i64.into()),
939 Some(false.into()),
940 Some(1_i64.into()),
941 ]));
942 epoch.inc_for_test();
943 state_table.commit_for_test(epoch).await.unwrap();
944 }
945
946 async fn build_executor(
947 store: MemoryStateStore,
948 upstream: Executor,
949 test_env: &LocalBarrierTestEnv,
950 ) -> BoxedMessageStream {
951 let upstream_table = ReplicatedStateTable::<_, BasicSerde>::new_replicated(
952 &upstream_table_catalog(),
953 store.clone(),
954 None,
955 vec![ColumnId::from(0), ColumnId::from(1)],
956 )
957 .await;
958 let state_table =
959 StateTable::from_table_catalog(&backfill_state_table_catalog(), store, None).await;
960 let progress =
961 CreateMviewProgressReporter::for_test(test_env.local_barrier_manager.clone());
962 ArrangementBackfillExecutor::new(
963 upstream_table,
964 upstream,
965 state_table,
966 vec![0, 1],
967 progress,
968 Arc::new(StreamingMetrics::unused()),
969 1024,
970 RateLimit::Disabled,
971 0.into(),
972 )
973 .boxed()
974 .execute()
975 }
976
977 /// Pull messages until `num_barriers` barriers are received. Returns the visible rows
978 /// forwarded to downstream and the `curr` epochs of the received barriers.
979 async fn collect_until_barriers(
980 stream: &mut BoxedMessageStream,
981 num_barriers: usize,
982 ) -> (Vec<OwnedRow>, Vec<u64>) {
983 let mut rows = vec![];
984 let mut barrier_epochs = vec![];
985 while barrier_epochs.len() < num_barriers {
986 match stream.next().await.unwrap().unwrap() {
987 Message::Barrier(barrier) => barrier_epochs.push(barrier.epoch.curr),
988 Message::Chunk(chunk) => {
989 for (op, row) in chunk.rows() {
990 assert_eq!(op, Op::Insert);
991 rows.push(row.into_owned_row());
992 }
993 }
994 Message::Watermark(_) => {}
995 }
996 }
997 (rows, barrier_epochs)
998 }
999
1000 /// The upstream mv is still empty when backfill starts, and the upstream keeps producing
1001 /// data for the whole first epoch, so the snapshot is never polled by the backfill stream.
1002 /// The snapshot read before processing the barrier then hits the end of the (empty)
1003 /// snapshot. The buffered upstream rows must still be forwarded, since backfill finishes
1004 /// right after this barrier.
1005 #[tokio::test]
1006 async fn test_snapshot_read_complete_on_barrier_forwards_upstream_when_not_started() {
1007 let store = MemoryStateStore::new();
1008 let test_env = LocalBarrierTestEnv::for_test().await;
1009
1010 let upstream = upstream(vec![
1011 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1012 Message::Chunk(StreamChunk::from_pretty(
1013 " I I
1014 + 1 10
1015 + 2 20",
1016 )),
1017 Message::Chunk(StreamChunk::from_pretty(
1018 " I I
1019 + 3 30",
1020 )),
1021 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1022 Message::Chunk(StreamChunk::from_pretty(
1023 " I I
1024 + 4 40",
1025 )),
1026 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1027 ]);
1028
1029 let mut backfill = build_executor(store, upstream, &test_env).await;
1030 let (rows, barrier_epochs) = collect_until_barriers(&mut backfill, 3).await;
1031 assert_eq!(
1032 barrier_epochs,
1033 vec![test_epoch(1), test_epoch(2), test_epoch(3)]
1034 );
1035 assert_eq!(rows, vec![row(1, 10), row(2, 20), row(3, 30), row(4, 40)]);
1036 }
1037
1038 /// Same as above, but the vnode has already made progress. Buffered upstream rows beyond
1039 /// `current_pos` must still be forwarded when the snapshot read before processing the
1040 /// barrier finds no more rows.
1041 #[tokio::test]
1042 async fn test_snapshot_read_complete_on_barrier_forwards_upstream_when_in_progress() {
1043 let store = MemoryStateStore::new();
1044 let test_env = LocalBarrierTestEnv::for_test().await;
1045 prepare_in_progress_backfill(&store).await;
1046
1047 let upstream = upstream(vec![
1048 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1049 Message::Chunk(StreamChunk::from_pretty(
1050 " I I
1051 + 2 20
1052 + 3 30",
1053 )),
1054 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1055 Message::Chunk(StreamChunk::from_pretty(
1056 " I I
1057 + 4 40",
1058 )),
1059 Message::Barrier(Barrier::new_test_barrier(test_epoch(4))),
1060 ]);
1061
1062 let mut backfill = build_executor(store, upstream, &test_env).await;
1063 let (rows, barrier_epochs) = collect_until_barriers(&mut backfill, 3).await;
1064 assert_eq!(
1065 barrier_epochs,
1066 vec![test_epoch(2), test_epoch(3), test_epoch(4)]
1067 );
1068 assert_eq!(rows, vec![row(2, 20), row(3, 30), row(4, 40)]);
1069 }
1070
1071 /// Recover right after the barrier on which the snapshot read completes and the buffered
1072 /// upstream rows are forwarded, before the next barrier. The upstream mv has committed the
1073 /// forwarded rows in that checkpoint, so backfill must not emit them again from the
1074 /// snapshot after recovery.
1075 #[tokio::test]
1076 async fn test_recover_after_snapshot_read_complete_on_barrier_when_not_started() {
1077 let store = MemoryStateStore::new();
1078 let test_env = LocalBarrierTestEnv::for_test().await;
1079
1080 let upstream_before_recovery = upstream(vec![
1081 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1082 Message::Chunk(StreamChunk::from_pretty(
1083 " I I
1084 + 1 10
1085 + 2 20
1086 + 3 30",
1087 )),
1088 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1089 ]);
1090 let mut backfill = build_executor(store.clone(), upstream_before_recovery, &test_env).await;
1091 let (rows, barrier_epochs) = collect_until_barriers(&mut backfill, 2).await;
1092 assert_eq!(barrier_epochs, vec![test_epoch(1), test_epoch(2)]);
1093 assert_eq!(rows, vec![row(1, 10), row(2, 20), row(3, 30)]);
1094 drop(backfill);
1095
1096 // The upstream mv commits the rows forwarded above in the same checkpoint.
1097 commit_upstream_rows(
1098 &store,
1099 test_epoch(1),
1100 vec![row(1, 10), row(2, 20), row(3, 30)],
1101 )
1102 .await;
1103
1104 // Recover from the checkpoint committed by the barrier of `test_epoch(2)`.
1105 let upstream_after_recovery = upstream(vec![
1106 Message::Barrier(Barrier::with_prev_epoch_for_test(
1107 test_epoch(3),
1108 test_epoch(1),
1109 )),
1110 Message::Chunk(StreamChunk::from_pretty(
1111 " I I
1112 + 4 40",
1113 )),
1114 Message::Barrier(Barrier::new_test_barrier(test_epoch(4))),
1115 ]);
1116 let mut backfill = build_executor(store, upstream_after_recovery, &test_env).await;
1117 let (rows, barrier_epochs) = collect_until_barriers(&mut backfill, 2).await;
1118 assert_eq!(barrier_epochs, vec![test_epoch(3), test_epoch(4)]);
1119 assert_eq!(rows, vec![row(4, 40)]);
1120 }
1121
1122 /// Same as above, but the vnode has already made progress before the snapshot read
1123 /// completes.
1124 #[tokio::test]
1125 async fn test_recover_after_snapshot_read_complete_on_barrier_when_in_progress() {
1126 let store = MemoryStateStore::new();
1127 let test_env = LocalBarrierTestEnv::for_test().await;
1128 prepare_in_progress_backfill(&store).await;
1129
1130 let upstream_before_recovery = upstream(vec![
1131 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1132 Message::Chunk(StreamChunk::from_pretty(
1133 " I I
1134 + 2 20
1135 + 3 30",
1136 )),
1137 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1138 ]);
1139 let mut backfill = build_executor(store.clone(), upstream_before_recovery, &test_env).await;
1140 let (rows, barrier_epochs) = collect_until_barriers(&mut backfill, 2).await;
1141 assert_eq!(barrier_epochs, vec![test_epoch(2), test_epoch(3)]);
1142 assert_eq!(rows, vec![row(2, 20), row(3, 30)]);
1143 drop(backfill);
1144
1145 // The upstream mv commits the rows forwarded above in the same checkpoint.
1146 commit_upstream_rows(&store, test_epoch(2), vec![row(2, 20), row(3, 30)]).await;
1147
1148 // Recover from the checkpoint committed by the barrier of `test_epoch(3)`.
1149 let upstream_after_recovery = upstream(vec![
1150 Message::Barrier(Barrier::with_prev_epoch_for_test(
1151 test_epoch(4),
1152 test_epoch(2),
1153 )),
1154 Message::Chunk(StreamChunk::from_pretty(
1155 " I I
1156 + 4 40",
1157 )),
1158 Message::Barrier(Barrier::new_test_barrier(test_epoch(5))),
1159 ]);
1160 let mut backfill = build_executor(store, upstream_after_recovery, &test_env).await;
1161 let (rows, barrier_epochs) = collect_until_barriers(&mut backfill, 2).await;
1162 assert_eq!(barrier_epochs, vec![test_epoch(4), test_epoch(5)]);
1163 assert_eq!(rows, vec![row(4, 40)]);
1164 }
1165}