Skip to main content

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}