Skip to main content

risingwave_stream/executor/backfill/
utils.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::borrow::Cow;
16use std::cmp::{Ordering, max, min};
17use std::collections::HashMap;
18use std::ops::Bound;
19
20use await_tree::InstrumentAwait;
21use futures::Stream;
22use futures::future::try_join_all;
23use futures_async_stream::try_stream;
24use risingwave_common::array::stream_record::Record;
25use risingwave_common::array::{DataChunk, Op, StreamChunk};
26use risingwave_common::bail;
27use risingwave_common::bitmap::BitmapBuilder;
28use risingwave_common::hash::{VirtualNode, VnodeBitmapExt};
29use risingwave_common::row::{OwnedRow, Row, RowExt};
30use risingwave_common::types::{DataType, Datum, DatumRef, ScalarRefImpl};
31use risingwave_common::util::chunk_coalesce::DataChunkBuilder;
32use risingwave_common::util::epoch::EpochPair;
33use risingwave_common::util::iter_util::ZipEqDebug;
34use risingwave_common::util::sort_util::{OrderType, cmp_datum, cmp_datum_iter};
35use risingwave_common::util::value_encoding::BasicSerde;
36use risingwave_common_rate_limit::RateLimit;
37use risingwave_connector::error::ConnectorError;
38use risingwave_connector::source::cdc::external::{CdcOffset, CdcOffsetParseFunc};
39use risingwave_storage::StateStore;
40use risingwave_storage::row_serde::value_serde::ValueRowSerde;
41use risingwave_storage::table::collect_data_chunk_with_builder;
42
43use crate::common::table::state_table::{ReplicatedStateTable, StateTableInner};
44use crate::executor::{Message, StreamExecutorError, StreamExecutorResult, Watermark};
45
46/// `vnode`, `is_finished`, `row_count`, all occupy 1 column each.
47pub const METADATA_STATE_LEN: usize = 3;
48
49#[derive(Clone, Debug)]
50pub struct BackfillState {
51    /// Used to track backfill progress.
52    // TODO: Instead of using hashmap, perhaps we can just use static array.
53    inner: HashMap<VirtualNode, BackfillStatePerVnode>,
54}
55
56impl BackfillState {
57    /// Whether any vnode has started backfilling, i.e. whether any upstream row may be forwarded.
58    /// After a reschedule, an actor may hold finished vnodes and vnodes that have not started, with
59    /// none in progress.
60    pub(crate) fn has_progress(&self) -> bool {
61        self.inner
62            .values()
63            .any(|p| !matches!(p.current_state(), &BackfillProgressPerVnode::NotStarted))
64    }
65
66    pub(crate) fn get_current_state(
67        &mut self,
68        vnode: &VirtualNode,
69    ) -> &mut BackfillProgressPerVnode {
70        &mut self.inner.get_mut(vnode).unwrap().current_state
71    }
72
73    // Expects the vnode to always have progress, otherwise it will return an error.
74    pub(crate) fn get_progress(
75        &self,
76        vnode: &VirtualNode,
77    ) -> StreamExecutorResult<&BackfillProgressPerVnode> {
78        match self.inner.get(vnode) {
79            Some(p) => Ok(p.current_state()),
80            None => bail!(
81                "Backfill progress for vnode {:#?} not found, backfill_state not initialized properly",
82                vnode,
83            ),
84        }
85    }
86
87    pub(crate) fn update_progress(
88        &mut self,
89        vnode: VirtualNode,
90        new_pos: OwnedRow,
91        snapshot_row_count_delta: u64,
92    ) -> StreamExecutorResult<()> {
93        let state = self.get_current_state(&vnode);
94        match state {
95            BackfillProgressPerVnode::NotStarted => {
96                *state = BackfillProgressPerVnode::InProgress {
97                    current_pos: new_pos,
98                    snapshot_row_count: snapshot_row_count_delta,
99                };
100            }
101            BackfillProgressPerVnode::InProgress {
102                snapshot_row_count, ..
103            } => {
104                *state = BackfillProgressPerVnode::InProgress {
105                    current_pos: new_pos,
106                    snapshot_row_count: *snapshot_row_count + snapshot_row_count_delta,
107                };
108            }
109            BackfillProgressPerVnode::Completed { .. } => unreachable!(),
110        }
111        Ok(())
112    }
113
114    pub(crate) fn finish_progress(&mut self, vnode: VirtualNode, pos_len: usize) {
115        let finished_placeholder_position = construct_initial_finished_state(pos_len);
116        let current_state = self.get_current_state(&vnode);
117        let (new_pos, snapshot_row_count) = match current_state {
118            BackfillProgressPerVnode::NotStarted => (finished_placeholder_position, 0),
119            BackfillProgressPerVnode::InProgress {
120                current_pos,
121                snapshot_row_count,
122            } => (current_pos.clone(), *snapshot_row_count),
123            BackfillProgressPerVnode::Completed { .. } => {
124                return;
125            }
126        };
127        *current_state = BackfillProgressPerVnode::Completed {
128            current_pos: new_pos,
129            snapshot_row_count,
130        };
131    }
132
133    /// Return state to be committed.
134    fn get_commit_state(&self, vnode: &VirtualNode) -> Option<(Option<Vec<Datum>>, Vec<Datum>)> {
135        let new_state = self.inner.get(vnode).unwrap().current_state().clone();
136        let new_encoded_state = match new_state {
137            BackfillProgressPerVnode::NotStarted => unreachable!(),
138            BackfillProgressPerVnode::InProgress {
139                current_pos,
140                snapshot_row_count,
141            } => {
142                let mut encoded_state = vec![None; current_pos.len() + METADATA_STATE_LEN];
143                encoded_state[0] = Some(vnode.to_scalar().into());
144                encoded_state[1..current_pos.len() + 1].clone_from_slice(current_pos.as_inner());
145                encoded_state[current_pos.len() + 1] = Some(false.into());
146                encoded_state[current_pos.len() + 2] = Some((snapshot_row_count as i64).into());
147                encoded_state
148            }
149            BackfillProgressPerVnode::Completed {
150                current_pos,
151                snapshot_row_count,
152            } => {
153                let mut encoded_state = vec![None; current_pos.len() + METADATA_STATE_LEN];
154                encoded_state[0] = Some(vnode.to_scalar().into());
155                encoded_state[1..current_pos.len() + 1].clone_from_slice(current_pos.as_inner());
156                encoded_state[current_pos.len() + 1] = Some(true.into());
157                encoded_state[current_pos.len() + 2] = Some((snapshot_row_count as i64).into());
158                encoded_state
159            }
160        };
161        let old_state = self.inner.get(vnode).unwrap().committed_state().clone();
162        let old_encoded_state = match old_state {
163            BackfillProgressPerVnode::NotStarted => None,
164            BackfillProgressPerVnode::InProgress {
165                current_pos,
166                snapshot_row_count,
167            } => {
168                let committed_pos = current_pos;
169                let mut encoded_state = vec![None; committed_pos.len() + METADATA_STATE_LEN];
170                encoded_state[0] = Some(vnode.to_scalar().into());
171                encoded_state[1..committed_pos.len() + 1]
172                    .clone_from_slice(committed_pos.as_inner());
173                encoded_state[committed_pos.len() + 1] = Some(false.into());
174                encoded_state[committed_pos.len() + 2] = Some((snapshot_row_count as i64).into());
175                Some(encoded_state)
176            }
177            BackfillProgressPerVnode::Completed {
178                current_pos,
179                snapshot_row_count,
180            } => {
181                let committed_pos = current_pos;
182                let mut encoded_state = vec![None; committed_pos.len() + METADATA_STATE_LEN];
183                encoded_state[0] = Some(vnode.to_scalar().into());
184                encoded_state[1..committed_pos.len() + 1]
185                    .clone_from_slice(committed_pos.as_inner());
186                encoded_state[committed_pos.len() + 1] = Some(true.into());
187                encoded_state[committed_pos.len() + 2] = Some((snapshot_row_count as i64).into());
188                Some(encoded_state)
189            }
190        };
191        Some((old_encoded_state, new_encoded_state))
192    }
193
194    // TODO: We can add a committed flag to speed up this check.
195    /// Checks if the state needs to be committed.
196    fn need_commit(&self, vnode: &VirtualNode) -> bool {
197        let state = self.inner.get(vnode).unwrap();
198        match state.current_state() {
199            // If current state and committed state are the same, we don't need to commit.
200            s @ BackfillProgressPerVnode::InProgress { .. }
201            | s @ BackfillProgressPerVnode::Completed { .. } => s != state.committed_state(),
202            BackfillProgressPerVnode::NotStarted => false,
203        }
204    }
205
206    fn mark_committed(&mut self, vnode: VirtualNode) {
207        let BackfillStatePerVnode {
208            committed_state,
209            current_state,
210        } = self.inner.get_mut(&vnode).unwrap();
211
212        assert!(matches!(
213            current_state,
214            BackfillProgressPerVnode::InProgress { .. }
215                | BackfillProgressPerVnode::Completed { .. }
216        ));
217        *committed_state = current_state.clone();
218    }
219
220    pub(crate) fn get_snapshot_row_count(&self) -> u64 {
221        self.inner
222            .values()
223            .map(|p| p.get_snapshot_row_count())
224            .sum()
225    }
226}
227
228#[derive(Clone, Debug, PartialEq, Eq)]
229pub struct BackfillStatePerVnode {
230    committed_state: BackfillProgressPerVnode,
231    current_state: BackfillProgressPerVnode,
232}
233
234impl BackfillStatePerVnode {
235    pub(crate) fn new(
236        committed_state: BackfillProgressPerVnode,
237        current_state: BackfillProgressPerVnode,
238    ) -> Self {
239        Self {
240            committed_state,
241            current_state,
242        }
243    }
244
245    pub(crate) fn committed_state(&self) -> &BackfillProgressPerVnode {
246        &self.committed_state
247    }
248
249    pub(crate) fn current_state(&self) -> &BackfillProgressPerVnode {
250        &self.current_state
251    }
252
253    pub(crate) fn get_snapshot_row_count(&self) -> u64 {
254        self.current_state().get_snapshot_row_count()
255    }
256}
257
258impl From<Vec<(VirtualNode, BackfillStatePerVnode)>> for BackfillState {
259    fn from(v: Vec<(VirtualNode, BackfillStatePerVnode)>) -> Self {
260        Self {
261            inner: v.into_iter().collect(),
262        }
263    }
264}
265
266/// Used for tracking backfill state per vnode
267/// The `OwnedRow` only contains the pk of upstream, to track `current_pos`.
268#[derive(Clone, Eq, PartialEq, Debug)]
269pub enum BackfillProgressPerVnode {
270    /// no entry exists for a vnode, or on initialization of the executor.
271    NotStarted,
272    InProgress {
273        /// The current snapshot offset
274        current_pos: OwnedRow,
275        /// Number of snapshot records read for this vnode.
276        snapshot_row_count: u64,
277    },
278    Completed {
279        /// The current snapshot offset
280        current_pos: OwnedRow,
281        /// Number of snapshot records read for this vnode.
282        snapshot_row_count: u64,
283    },
284}
285
286impl BackfillProgressPerVnode {
287    fn get_snapshot_row_count(&self) -> u64 {
288        match self {
289            BackfillProgressPerVnode::NotStarted => 0,
290            BackfillProgressPerVnode::InProgress {
291                snapshot_row_count, ..
292            }
293            | BackfillProgressPerVnode::Completed {
294                snapshot_row_count, ..
295            } => *snapshot_row_count,
296        }
297    }
298}
299
300/// Mark CDC rows that are safe to emit downstream as visible and all other rows as invisible
301///
302/// A row is safe when it's offset is `>= last_cdc_offset` and it's PK is `<= current_pos`
303pub(crate) fn mark_cdc_chunk(
304    offset_parse_func: &CdcOffsetParseFunc,
305    chunk: StreamChunk,
306    current_pos: &OwnedRow,
307    pk_in_output_indices: &[usize],
308    pk_order: &[OrderType],
309    pk_needs_unsigned_i64_compare: &[bool],
310    last_cdc_offset: Option<CdcOffset>,
311) -> StreamExecutorResult<StreamChunk> {
312    let chunk = chunk.compact_vis();
313    mark_cdc_chunk_inner(
314        offset_parse_func,
315        chunk,
316        current_pos,
317        last_cdc_offset,
318        pk_in_output_indices,
319        pk_order,
320        pk_needs_unsigned_i64_compare,
321    )
322}
323
324/// Compare two primary-key rows column by column, recovering the upstream unsigned order
325/// for `BIGINT UNSIGNED` pk columns.
326///
327/// `pk_needs_unsigned_i64_compare[i]` is true only for upstream `BIGINT UNSIGNED`. Frontend
328/// up-casts narrower unsigned integers so they stay non-negative in RisingWave, and unsigned
329/// float/double/decimal types must keep their native comparison semantics. Only `BIGINT UNSIGNED`
330/// can overflow into a negative `i64`, so we reinterpret both sides as `u64` to restore upstream
331/// MySQL ordering. The `ScalarRefImpl::Int64` match below is a defensive guard for that contract.
332pub(crate) fn cmp_pk_unsigned_aware<'a>(
333    lhs: impl Iterator<Item = DatumRef<'a>>,
334    rhs: impl Iterator<Item = DatumRef<'a>>,
335    pk_order: &[OrderType],
336    pk_needs_unsigned_i64_compare: &[bool],
337) -> Ordering {
338    for (((l, r), order), &needs_unsigned_i64_compare) in lhs
339        .zip_eq_debug(rhs)
340        .zip_eq_debug(pk_order.iter())
341        .zip_eq_debug(pk_needs_unsigned_i64_compare.iter())
342    {
343        let ord = match (needs_unsigned_i64_compare, l, r) {
344            (true, Some(ScalarRefImpl::Int64(a)), Some(ScalarRefImpl::Int64(b))) => {
345                let ord = (a as u64).cmp(&(b as u64));
346                // Apply the column's sort direction, matching `cmp_datum`. CDC backfill always
347                // reads the snapshot ascending, so the descending branch is only for parity.
348                if order.is_descending() {
349                    ord.reverse()
350                } else {
351                    ord
352                }
353            }
354            // Signed column, NULL, up-cast unsigned integer, or non-integer unsigned column:
355            // the original comparison is already correct.
356            _ => cmp_datum(l, r, *order),
357        };
358        if ord != Ordering::Equal {
359            return ord;
360        }
361    }
362    Ordering::Equal
363}
364
365/// Mark chunk:
366/// For each row of the chunk, forward it to downstream if its pk <= `current_pos` for the
367/// corresponding `vnode`, otherwise ignore it.
368/// We implement it by changing the visibility bitmap.
369pub(crate) fn mark_chunk_ref_by_vnode<S: StateStore, SD: ValueRowSerde>(
370    chunk: &StreamChunk,
371    backfill_state: &BackfillState,
372    pk_in_output_indices: &[usize],
373    upstream_table: &ReplicatedStateTable<S, SD>,
374    pk_order: &[OrderType],
375) -> StreamExecutorResult<StreamChunk> {
376    let chunk = chunk.clone();
377    let (data, ops) = chunk.into_parts();
378    let mut new_visibility = BitmapBuilder::with_capacity(ops.len());
379
380    let mut new_ops: Cow<'_, [Op]> = Cow::Borrowed(ops.as_ref());
381    let mut unmatched_update_delete = false;
382    let mut visible_update_delete = false;
383    for (i, (op, row)) in ops.iter().zip_eq_debug(data.rows()).enumerate() {
384        let pk = row.project(pk_in_output_indices);
385        let vnode = upstream_table.compute_vnode_by_pk(pk);
386        let visible = match backfill_state.get_progress(&vnode)? {
387            // We want to just forward the row, if the vnode has finished backfill.
388            BackfillProgressPerVnode::Completed { .. } => true,
389            // If not started, no need to forward.
390            BackfillProgressPerVnode::NotStarted => false,
391            // If in progress, we need to check row <= current_pos.
392            BackfillProgressPerVnode::InProgress { current_pos, .. } => {
393                cmp_datum_iter(pk.iter(), current_pos.iter(), pk_order.iter().copied()).is_le()
394            }
395        };
396        if !visible {
397            tracing::trace!(
398                source = "upstream",
399                state = "process_barrier",
400                action = "mark_chunk",
401                ?vnode,
402                ?op,
403                ?pk,
404                ?row,
405                "update_filtered",
406            );
407        }
408        new_visibility.append(visible);
409
410        normalize_unmatched_updates(
411            &mut new_ops,
412            &mut unmatched_update_delete,
413            &mut visible_update_delete,
414            visible,
415            i,
416            op,
417        );
418    }
419    let (columns, _) = data.into_parts();
420    let chunk = StreamChunk::with_visibility(new_ops, columns, new_visibility.finish());
421    Ok(chunk)
422}
423
424/// We will rewrite unmatched U-/U+ into +/- ops.
425/// They can be unmatched because while they will always have the same stream key,
426/// their storage pk might be different. Here we use storage pk (`current_pos`) to filter them,
427/// as such, a U+ might be filtered out, but their corresponding U- could be kept, and vice versa.
428///
429/// This hanging U-/U+ can lead to issues downstream, since we work with an assumption in the
430/// system that there's never hanging U-/U+.
431fn normalize_unmatched_updates(
432    normalized_ops: &mut Cow<'_, [Op]>,
433    unmatched_update_delete: &mut bool,
434    visible_update_delete: &mut bool,
435    current_visibility: bool,
436    current_op_index: usize,
437    current_op: &Op,
438) {
439    if *unmatched_update_delete {
440        assert_eq!(*current_op, Op::UpdateInsert);
441        let visible_update_insert = current_visibility;
442        match (visible_update_delete, visible_update_insert) {
443            (true, false) => {
444                // Lazily clone the ops here.
445                let ops = normalized_ops.to_mut();
446                ops[current_op_index - 1] = Op::Delete;
447            }
448            (false, true) => {
449                // Lazily clone the ops here.
450                let ops = normalized_ops.to_mut();
451                ops[current_op_index] = Op::Insert;
452            }
453            (true, true) | (false, false) => {}
454        }
455        *unmatched_update_delete = false;
456    } else {
457        match current_op {
458            Op::UpdateDelete => {
459                *unmatched_update_delete = true;
460                *visible_update_delete = current_visibility;
461            }
462            Op::UpdateInsert => {
463                unreachable!("UpdateInsert should not be present without UpdateDelete")
464            }
465            _ => {}
466        }
467    }
468}
469
470fn mark_cdc_chunk_inner(
471    offset_parse_func: &CdcOffsetParseFunc,
472    chunk: StreamChunk,
473    current_pos: &OwnedRow,
474    last_cdc_offset: Option<CdcOffset>,
475    pk_in_output_indices: &[usize],
476    pk_order: &[OrderType],
477    pk_needs_unsigned_i64_compare: &[bool],
478) -> StreamExecutorResult<StreamChunk> {
479    let (data, ops) = chunk.into_parts();
480    let mut new_visibility = BitmapBuilder::with_capacity(ops.len());
481
482    // `_rw_offset` must be placed at the last column right now
483    let offset_col_idx = data.dimension() - 1;
484    for v in data.rows().map(|row| {
485        let offset_datum = row.datum_at(offset_col_idx).unwrap();
486        let event_offset = (*offset_parse_func)(offset_datum.into_utf8())?;
487        let visible = {
488            // filter changelog events with binlog range
489            let in_binlog_range = if let Some(binlog_low) = &last_cdc_offset {
490                binlog_low <= &event_offset
491            } else {
492                true
493            };
494
495            if in_binlog_range {
496                let lhs = row.project(pk_in_output_indices);
497                let rhs = current_pos;
498                cmp_pk_unsigned_aware(
499                    lhs.iter(),
500                    rhs.iter(),
501                    pk_order,
502                    pk_needs_unsigned_i64_compare,
503                )
504                .is_le()
505            } else {
506                false
507            }
508        };
509        Ok::<_, ConnectorError>(visible)
510    }) {
511        new_visibility.append(v?);
512    }
513
514    let (columns, _) = data.into_parts();
515    Ok(StreamChunk::with_visibility(
516        ops,
517        columns,
518        new_visibility.finish(),
519    ))
520}
521
522/// Builds a new stream chunk with `output_indices`.
523pub(crate) fn mapping_chunk(chunk: StreamChunk, output_indices: &[usize]) -> StreamChunk {
524    let (ops, columns, visibility) = chunk.into_inner();
525    let mapped_columns = output_indices.iter().map(|&i| columns[i].clone()).collect();
526    StreamChunk::with_visibility(ops, mapped_columns, visibility)
527}
528
529fn mapping_watermark(watermark: Watermark, upstream_indices: &[usize]) -> Option<Watermark> {
530    watermark.transform_with_indices(upstream_indices)
531}
532
533pub(crate) fn mapping_message(msg: Message, upstream_indices: &[usize]) -> Option<Message> {
534    match msg {
535        Message::Barrier(_) => Some(msg),
536        Message::Watermark(watermark) => {
537            mapping_watermark(watermark, upstream_indices).map(Message::Watermark)
538        }
539        Message::Chunk(chunk) => Some(Message::Chunk(mapping_chunk(chunk, upstream_indices))),
540    }
541}
542
543fn same_key_columns(lhs: &[usize], rhs: &[usize]) -> bool {
544    lhs.len() == rhs.len() && lhs.iter().all(|idx| rhs.contains(idx))
545}
546
547/// Rewrites upstream updates when the input stream key is not the same column
548/// set as the current executor stream key.
549///
550/// If an upstream update keeps the input stream key unchanged but changes the
551/// current executor stream key, downstream state keyed by the current stream key
552/// must see it as a delete followed by an insert.
553pub(super) struct UpstreamStreamKeyUpdateNormalizer {
554    current_stream_key_indices: Option<Vec<usize>>,
555}
556
557impl UpstreamStreamKeyUpdateNormalizer {
558    /// Creates a normalizer for chunks with the given schema.
559    ///
560    /// `input_stream_key_indices` are the stream-key column indices of the input
561    /// executor in the incoming chunk schema.
562    ///
563    /// `current_stream_key_indices` are the stream-key column indices of the
564    /// current executor in the same incoming chunk schema.
565    ///
566    /// For example, if an upstream MV has input stream key `[k]` but the current
567    /// executor stream key is `[k, ts]`, then an update from
568    /// `(k = 1, ts = 10)` to `(k = 1, ts = 20)` is rewritten as
569    /// `Delete(k = 1, ts = 10)` plus `Insert(k = 1, ts = 20)`. If the two stream
570    /// keys are the same column set, or if an update does not change current
571    /// stream-key values, it is left unchanged.
572    pub(super) fn new(
573        input_stream_key_indices: &[usize],
574        current_stream_key_indices: Vec<usize>,
575    ) -> Self {
576        let current_stream_key_indices =
577            (!same_key_columns(input_stream_key_indices, &current_stream_key_indices))
578                .then_some(current_stream_key_indices);
579        Self {
580            current_stream_key_indices,
581        }
582    }
583
584    pub(super) fn normalize_chunk(&self, chunk: StreamChunk) -> Option<StreamChunk> {
585        if let Some(current_stream_key_indices) = &self.current_stream_key_indices {
586            normalize_update_chunk_by_key(chunk, current_stream_key_indices)
587        } else {
588            Some(chunk)
589        }
590    }
591
592    pub(super) fn normalize_message(&self, msg: Message) -> Option<Message> {
593        match msg {
594            Message::Chunk(chunk) => self.normalize_chunk(chunk).map(Message::Chunk),
595            msg => Some(msg),
596        }
597    }
598}
599
600fn normalize_update_chunk_by_key(chunk: StreamChunk, key_indices: &[usize]) -> Option<StreamChunk> {
601    let (data_chunk, ops) = chunk.into_parts();
602    let mut update_indices = vec![];
603    let mut row_idx = data_chunk.next_visible_row_idx(0);
604    while let Some(idx) = row_idx {
605        let row = data_chunk.row_at_unchecked_vis(idx);
606        match ops[idx] {
607            Op::UpdateDelete => {
608                let next_idx = data_chunk
609                    .next_visible_row_idx(idx + 1)
610                    .unwrap_or_else(|| panic!("expect a U+ after U-\nU- row: {}", row.display()));
611                let next_row = data_chunk.row_at_unchecked_vis(next_idx);
612                debug_assert_eq!(
613                    ops[next_idx],
614                    Op::UpdateInsert,
615                    "expect a U+ after U-\nU- row: {}\nrow after U-: {}",
616                    row.display(),
617                    next_row.display()
618                );
619                if row.project(key_indices) != next_row.project(key_indices) {
620                    update_indices.push((idx, next_idx));
621                }
622                row_idx = data_chunk.next_visible_row_idx(next_idx + 1);
623            }
624            Op::UpdateInsert => panic!("expect a U- before U+\nU+ row: {}", row.display()),
625            Op::Insert | Op::Delete => {
626                row_idx = data_chunk.next_visible_row_idx(idx + 1);
627            }
628        }
629    }
630
631    if update_indices.is_empty() {
632        return Some(StreamChunk::from_parts(ops, data_chunk));
633    }
634
635    let (columns, visibility) = data_chunk.into_parts();
636    let mut ops = ops.to_vec();
637    for (delete_idx, insert_idx) in update_indices {
638        ops[delete_idx] = Op::Delete;
639        ops[insert_idx] = Op::Insert;
640    }
641    Some(StreamChunk::from_parts(
642        ops,
643        DataChunk::new(columns, visibility),
644    ))
645}
646
647/// Recovers progress per vnode, so we know which to backfill.
648/// See how it decodes the state with the inline comments.
649pub(crate) async fn get_progress_per_vnode<S: StateStore, const IS_REPLICATED: bool>(
650    state_table: &StateTableInner<S, BasicSerde, IS_REPLICATED>,
651) -> StreamExecutorResult<Vec<(VirtualNode, BackfillStatePerVnode)>> {
652    debug_assert!(!state_table.vnodes().is_empty());
653    let vnodes = state_table.vnodes().iter_vnodes();
654    let mut result = Vec::with_capacity(state_table.vnodes().len());
655    // 1. Get the vnode keys, so we can get the state per vnode.
656    let vnode_keys = vnodes.map(|vnode| {
657        let datum: [Datum; 1] = [Some(vnode.to_scalar().into())];
658        datum
659    });
660    let tasks = vnode_keys.map(|vnode_key| state_table.get_row(vnode_key));
661    // 2. Fetch the state for each vnode.
662    //    It should have the following schema, it should not contain vnode:
663    //    | pk | `backfill_finished` | `row_count` |
664    let state_for_vnodes = try_join_all(tasks).await?;
665    for (vnode, state_for_vnode) in state_table
666        .vnodes()
667        .iter_vnodes()
668        .zip_eq_debug(state_for_vnodes)
669    {
670        let backfill_progress = match state_for_vnode {
671            // There's some state, means there was progress made. It's either finished / in progress.
672            Some(row) => {
673                // 3. Decode the `snapshot_row_count`. Decode from the back, since
674                //    pk is variable length.
675                let snapshot_row_count = row.as_inner().get(row.len() - 1).unwrap();
676                let snapshot_row_count = (*snapshot_row_count.as_ref().unwrap().as_int64()) as u64;
677
678                // 4. Decode the `is_finished` flag (whether backfill has finished).
679                //    Decode from the back, since pk is variable length.
680                let vnode_is_finished = row.as_inner().get(row.len() - 2).unwrap();
681                let vnode_is_finished = vnode_is_finished.as_ref().unwrap();
682
683                // 5. Decode the `current_pos`.
684                let current_pos = row.as_inner().get(..row.len() - 2).unwrap();
685                let current_pos = current_pos.into_owned_row();
686
687                // 6. Construct the in-memory state per vnode, based on the decoded state.
688                if *vnode_is_finished.as_bool() {
689                    BackfillStatePerVnode::new(
690                        BackfillProgressPerVnode::Completed {
691                            current_pos: current_pos.clone(),
692                            snapshot_row_count,
693                        },
694                        BackfillProgressPerVnode::Completed {
695                            current_pos,
696                            snapshot_row_count,
697                        },
698                    )
699                } else {
700                    BackfillStatePerVnode::new(
701                        BackfillProgressPerVnode::InProgress {
702                            current_pos: current_pos.clone(),
703                            snapshot_row_count,
704                        },
705                        BackfillProgressPerVnode::InProgress {
706                            current_pos,
707                            snapshot_row_count,
708                        },
709                    )
710                }
711            }
712            // No state, means no progress made.
713            None => BackfillStatePerVnode::new(
714                BackfillProgressPerVnode::NotStarted,
715                BackfillProgressPerVnode::NotStarted,
716            ),
717        };
718        result.push((vnode, backfill_progress));
719    }
720    assert_eq!(result.len(), state_table.vnodes().count_ones());
721    Ok(result)
722}
723
724/// Update backfill pos by vnode.
725pub(crate) fn update_pos_by_vnode(
726    vnode: VirtualNode,
727    chunk: &StreamChunk,
728    pk_in_output_indices: &[usize],
729    backfill_state: &mut BackfillState,
730    snapshot_row_count_delta: u64,
731) -> StreamExecutorResult<()> {
732    let new_pos = get_new_pos(chunk, pk_in_output_indices);
733    assert_eq!(new_pos.len(), pk_in_output_indices.len());
734    backfill_state.update_progress(vnode, new_pos, snapshot_row_count_delta)?;
735    Ok(())
736}
737
738/// Get new backfill pos from the chunk. Since chunk should have ordered rows, we can just take the
739/// last row.
740pub(crate) fn get_new_pos(chunk: &StreamChunk, pk_in_output_indices: &[usize]) -> OwnedRow {
741    chunk
742        .rows()
743        .last()
744        .unwrap()
745        .1
746        .project(pk_in_output_indices)
747        .into_owned_row()
748}
749
750pub(crate) fn get_cdc_chunk_last_offset(
751    offset_parse_func: &CdcOffsetParseFunc,
752    chunk: &StreamChunk,
753) -> StreamExecutorResult<Option<CdcOffset>> {
754    let row = chunk.rows().last().unwrap().1;
755    let offset_col = row.iter().last().unwrap();
756    let output =
757        offset_col.map(|scalar| Ok::<_, ConnectorError>((*offset_parse_func)(scalar.into_utf8()))?);
758    output.transpose().map_err(|e| e.into())
759}
760
761// NOTE(kwannoel): ["None" ..] encoding should be appropriate to mark
762// the case where upstream snapshot is empty.
763// This is so we can persist backfill state as "finished".
764// It won't be confused with another case where pk position comprised of nulls,
765// because they both record that backfill is finished.
766pub(crate) fn construct_initial_finished_state(pos_len: usize) -> OwnedRow {
767    OwnedRow::new(vec![None; pos_len])
768}
769
770pub(crate) fn compute_bounds(
771    pk_indices: &[usize],
772    current_pos: Option<OwnedRow>,
773) -> Option<(Bound<OwnedRow>, Bound<OwnedRow>)> {
774    // `current_pos` is None means it needs to scan from the beginning, so we use Unbounded to
775    // scan. Otherwise, use Excluded.
776    if let Some(current_pos) = current_pos {
777        // If `current_pos` is an empty row which means upstream mv contains only one row and it
778        // has been consumed. The iter interface doesn't support
779        // `Excluded(empty_row)` range bound, so we can simply return `None`.
780        if current_pos.is_empty() {
781            assert!(pk_indices.is_empty());
782            return None;
783        }
784
785        Some((Bound::Excluded(current_pos), Bound::Unbounded))
786    } else {
787        Some((Bound::Unbounded, Bound::Unbounded))
788    }
789}
790
791#[try_stream(ok = StreamChunk, error = StreamExecutorError)]
792pub(crate) async fn iter_chunks<'a, S, E, R>(mut iter: S, builder: &'a mut DataChunkBuilder)
793where
794    StreamExecutorError: From<E>,
795    R: Row,
796    S: Stream<Item = Result<R, E>> + Unpin + 'a,
797{
798    while let Some(data_chunk) = collect_data_chunk_with_builder(&mut iter, builder)
799        .instrument_await("backfill_snapshot_read")
800        .await?
801    {
802        debug_assert!(data_chunk.cardinality() > 0);
803        let ops = vec![Op::Insert; data_chunk.capacity()];
804        let stream_chunk = StreamChunk::from_parts(ops, data_chunk);
805        yield stream_chunk;
806    }
807}
808
809/// Schema
810/// | vnode | pk | `backfill_finished` | `row_count` |
811/// Persists the state per vnode based on `BackfillState`.
812/// We track the current committed state via `committed_progress`
813/// so we know whether we need to persist the state or not.
814///
815/// The state is encoded as follows:
816/// `NotStarted`:
817/// - Not persist to store at all.
818///
819/// `InProgress`:
820/// - Format: | vnode | pk | false | `row_count` |
821/// - If change in current pos: Persist.
822/// - No change in current pos: Do not persist.
823///
824/// Completed
825/// - Format: | vnode | pk | true | `row_count` |
826/// - If previous state is `InProgress` / `NotStarted`: Persist.
827/// - If previous state is Completed: Do not persist.
828///
829/// TODO(kwannoel): we should check committed state to be all `finished` in the tests.
830/// TODO(kwannoel): Instead of persisting state per vnode each time,
831/// we can optimize by persisting state for a subset of vnodes which were updated.
832pub(crate) async fn persist_state_per_vnode<S: StateStore, const IS_REPLICATED: bool>(
833    epoch: EpochPair,
834    table: &mut StateTableInner<S, BasicSerde, IS_REPLICATED>,
835    backfill_state: &mut BackfillState,
836    #[cfg(debug_assertions)] state_len: usize,
837    vnodes: impl Iterator<Item = VirtualNode>,
838) -> StreamExecutorResult<()> {
839    for vnode in vnodes {
840        if !backfill_state.need_commit(&vnode) {
841            continue;
842        }
843        let (encoded_prev_state, encoded_current_state) =
844            match backfill_state.get_commit_state(&vnode) {
845                Some((old_state, new_state)) => (old_state, new_state),
846                None => continue,
847            };
848        if let Some(encoded_prev_state) = encoded_prev_state {
849            // There's some progress, update the state.
850            #[cfg(debug_assertions)]
851            {
852                let pk: &[Datum; 1] = &[Some(vnode.to_scalar().into())];
853                // old_row only contains the value segment.
854                let old_row = table.get_row(pk).await?;
855                match old_row {
856                    Some(old_row) => {
857                        let inner = old_row.as_inner();
858                        // value segment (without vnode) should be used for comparison
859                        assert_eq!(inner, &encoded_prev_state[1..]);
860                        assert_ne!(inner, &encoded_current_state[1..]);
861                        assert_eq!(old_row.len(), state_len - 1);
862                        assert_eq!(encoded_current_state.len(), state_len);
863                    }
864                    None => {
865                        bail!("row {:#?} not found", pk);
866                    }
867                }
868            }
869            table.write_record(Record::Update {
870                old_row: &encoded_prev_state[..],
871                new_row: &encoded_current_state[..],
872            });
873        } else {
874            // No existing state, create a new entry.
875            #[cfg(debug_assertions)]
876            {
877                let pk: &[Datum; 1] = &[Some(vnode.to_scalar().into())];
878                let row = table.get_row(pk).await?;
879                assert!(row.is_none(), "row {:#?}", row);
880                assert_eq!(encoded_current_state.len(), state_len);
881            }
882            table.write_record(Record::Insert {
883                new_row: &encoded_current_state[..],
884            });
885        }
886        backfill_state.mark_committed(vnode);
887    }
888
889    table.commit_assert_no_update_vnode_bitmap(epoch).await?;
890    Ok(())
891}
892
893/// Creates a data chunk builder for snapshot read.
894/// If the `rate_limit` is smaller than `chunk_size`, it will take precedence.
895/// This is so we can partition snapshot read into smaller chunks than chunk size.
896pub fn create_builder(
897    rate_limit: RateLimit,
898    chunk_size: usize,
899    data_types: Vec<DataType>,
900) -> DataChunkBuilder {
901    let batch_size = match rate_limit {
902        RateLimit::Disabled | RateLimit::Pause => chunk_size,
903        RateLimit::Fixed(limit) => min(limit.get() as usize, chunk_size),
904    };
905    // Ensure that the batch size is at least 2, to have enough space for two rows in a single update.
906    let batch_size = max(2, batch_size);
907    DataChunkBuilder::new(data_types, batch_size)
908}
909
910#[cfg(test)]
911mod tests {
912    use std::sync::Arc;
913
914    use super::*;
915
916    #[test]
917    fn test_normalizing_unmatched_updates() {
918        let ops = vec![
919            Op::UpdateDelete,
920            Op::UpdateInsert,
921            Op::UpdateDelete,
922            Op::UpdateInsert,
923        ];
924        let ops: Arc<[Op]> = ops.into();
925
926        {
927            let mut new_ops: Cow<'_, [Op]> = Cow::Borrowed(ops.as_ref());
928            let mut unmatched_update_delete = true;
929            let mut visible_update_delete = true;
930            let current_visibility = true;
931            normalize_unmatched_updates(
932                &mut new_ops,
933                &mut unmatched_update_delete,
934                &mut visible_update_delete,
935                current_visibility,
936                1,
937                &Op::UpdateInsert,
938            );
939            assert_eq!(
940                &new_ops[..],
941                vec![
942                    Op::UpdateDelete,
943                    Op::UpdateInsert,
944                    Op::UpdateDelete,
945                    Op::UpdateInsert
946                ]
947            );
948        }
949        {
950            let mut new_ops: Cow<'_, [Op]> = Cow::Borrowed(ops.as_ref());
951            let mut unmatched_update_delete = true;
952            let mut visible_update_delete = false;
953            let current_visibility = false;
954            normalize_unmatched_updates(
955                &mut new_ops,
956                &mut unmatched_update_delete,
957                &mut visible_update_delete,
958                current_visibility,
959                1,
960                &Op::UpdateInsert,
961            );
962            assert_eq!(
963                &new_ops[..],
964                vec![
965                    Op::UpdateDelete,
966                    Op::UpdateInsert,
967                    Op::UpdateDelete,
968                    Op::UpdateInsert
969                ]
970            );
971        }
972        {
973            let mut new_ops: Cow<'_, [Op]> = Cow::Borrowed(ops.as_ref());
974            let mut unmatched_update_delete = true;
975            let mut visible_update_delete = true;
976            let current_visibility = false;
977            normalize_unmatched_updates(
978                &mut new_ops,
979                &mut unmatched_update_delete,
980                &mut visible_update_delete,
981                current_visibility,
982                1,
983                &Op::UpdateInsert,
984            );
985            assert_eq!(
986                &new_ops[..],
987                vec![
988                    Op::Delete,
989                    Op::UpdateInsert,
990                    Op::UpdateDelete,
991                    Op::UpdateInsert
992                ]
993            );
994        }
995        {
996            let mut new_ops: Cow<'_, [Op]> = Cow::Borrowed(ops.as_ref());
997            let mut unmatched_update_delete = true;
998            let mut visible_update_delete = false;
999            let current_visibility = true;
1000            normalize_unmatched_updates(
1001                &mut new_ops,
1002                &mut unmatched_update_delete,
1003                &mut visible_update_delete,
1004                current_visibility,
1005                1,
1006                &Op::UpdateInsert,
1007            );
1008            assert_eq!(
1009                &new_ops[..],
1010                vec![
1011                    Op::UpdateDelete,
1012                    Op::Insert,
1013                    Op::UpdateDelete,
1014                    Op::UpdateInsert
1015                ]
1016            );
1017        }
1018    }
1019}