Skip to main content

risingwave_stream/executor/over_window/
over_partition.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
15//! Types and functions that store or manipulate state/cache inside one single over window
16//! partition.
17
18use std::collections::BTreeMap;
19use std::marker::PhantomData;
20use std::ops::{Bound, RangeInclusive};
21
22use delta_btree_map::{Change, DeltaBTreeMap};
23use educe::Educe;
24use futures::StreamExt;
25use futures_async_stream::for_await;
26use risingwave_common::array::stream_record::Record;
27use risingwave_common::config::streaming::OverWindowCachePolicy as CachePolicy;
28use risingwave_common::row::{OwnedRow, Row, RowExt};
29use risingwave_common::types::{Datum, DefaultOrd, ScalarImpl, Sentinelled};
30use risingwave_common::util::iter_util::ZipEqFast;
31use risingwave_expr::window_function::{StateKey, WindowStates, create_window_state};
32use risingwave_storage::StateStore;
33use risingwave_storage::store::PrefetchOptions;
34
35use super::general::{Calls, RowConverter, StateCleaning};
36use super::range_cache::{CacheKey, PartitionCache};
37use crate::common::table::state_table::{BoxedRowStream, StateTable};
38use crate::consistency::{consistency_error, enable_strict_consistency};
39use crate::executor::StreamExecutorResult;
40use crate::executor::over_window::frame_finder::*;
41
42/// Changes happened in one over window partition.
43pub(super) type PartitionDelta = BTreeMap<CacheKey, Change<OwnedRow>>;
44
45#[derive(Default, Debug)]
46pub(super) struct OverPartitionStats {
47    // stats for range cache operations
48    pub lookup_count: u64,
49    pub left_miss_count: u64,
50    pub right_miss_count: u64,
51
52    // stats for window function state computation
53    pub accessed_entry_count: u64,
54    pub compute_count: u64,
55    pub same_output_count: u64,
56}
57
58/// [`AffectedRange`] represents a range of keys that are affected by a delta.
59/// The [`CacheKey`] fields are keys in the partition range cache + delta, which is
60/// represented by [`DeltaBTreeMap`].
61///
62/// - `first_curr_key` and `last_curr_key` are the current keys of the first and the last
63///   windows affected. They are used to pinpoint the bounds where state needs to be updated.
64/// - `first_frame_start` and `last_frame_end` are the frame start and end of the first and
65///   the last windows affected. They are used to pinpoint the bounds where state needs to be
66///   included for computing the new state.
67#[derive(Debug, Educe)]
68#[educe(Clone, Copy)]
69pub(super) struct AffectedRange<'a> {
70    pub first_frame_start: &'a CacheKey,
71    pub first_curr_key: &'a CacheKey,
72    pub last_curr_key: &'a CacheKey,
73    pub last_frame_end: &'a CacheKey,
74}
75
76impl<'a> AffectedRange<'a> {
77    fn new(
78        first_frame_start: &'a CacheKey,
79        first_curr_key: &'a CacheKey,
80        last_curr_key: &'a CacheKey,
81        last_frame_end: &'a CacheKey,
82    ) -> Self {
83        Self {
84            first_frame_start,
85            first_curr_key,
86            last_curr_key,
87            last_frame_end,
88        }
89    }
90}
91
92/// A wrapper of [`PartitionCache`] that provides helper methods to manipulate the cache.
93/// By putting this type inside `private` module, we can avoid misuse of the internal fields and
94/// methods.
95pub(super) struct OverPartition<'a, S: StateStore> {
96    deduped_part_key: &'a OwnedRow,
97    range_cache: &'a mut PartitionCache,
98    cache_policy: CachePolicy,
99
100    calls: &'a Calls,
101    row_conv: RowConverter<'a>,
102
103    stats: OverPartitionStats,
104
105    _phantom: PhantomData<S>,
106}
107
108const MAGIC_BATCH_SIZE: usize = 512;
109
110/// Maximum number of stale rows to delete from one partition in one round of state cleaning, to
111/// bound the number of writes buffered in one epoch when there's a large backlog of stale rows.
112const MAX_STALE_ROWS_TO_DELETE_PER_ROUND: usize = 1 << 16;
113
114impl<'a, S: StateStore> OverPartition<'a, S> {
115    pub fn new(
116        deduped_part_key: &'a OwnedRow,
117        cache: &'a mut PartitionCache,
118        cache_policy: CachePolicy,
119        calls: &'a Calls,
120        row_conv: RowConverter<'a>,
121    ) -> Self {
122        Self {
123            deduped_part_key,
124            range_cache: cache,
125            cache_policy,
126
127            calls,
128            row_conv,
129
130            stats: Default::default(),
131
132            _phantom: PhantomData,
133        }
134    }
135
136    /// Get a summary for the execution happened in the [`OverPartition`] in current round.
137    /// This will consume the [`OverPartition`] value itself.
138    pub fn summarize(self) -> OverPartitionStats {
139        // We may extend this function in the future.
140        self.stats
141    }
142
143    /// Get the number of cached entries ignoring sentinels.
144    pub fn cache_real_len(&self) -> usize {
145        self.range_cache.normal_len()
146    }
147
148    /// Build changes for the partition, with the given `delta`. Necessary maintenance of the range
149    /// cache will be done during this process, like loading rows from the `table` into the cache.
150    pub async fn build_changes(
151        &mut self,
152        table: &StateTable<S>,
153        mut delta: PartitionDelta,
154    ) -> StreamExecutorResult<(
155        BTreeMap<StateKey, Record<OwnedRow>>,
156        Option<RangeInclusive<StateKey>>,
157    )> {
158        let calls = self.calls;
159        let input_schema_len = table.get_data_types().len() - calls.len();
160        let numbering_only = calls.numbering_only;
161        let has_rank = calls.has_rank;
162
163        // return values
164        let mut part_changes = BTreeMap::new();
165        let mut accessed_range: Option<RangeInclusive<StateKey>> = None;
166
167        // stats
168        let mut accessed_entry_count = 0;
169        let mut compute_count = 0;
170        let mut same_output_count = 0;
171
172        // Find affected ranges, this also ensures that all rows in the affected ranges are loaded into the cache.
173        let (part_with_delta, affected_ranges) =
174            self.find_affected_ranges(table, &mut delta).await?;
175
176        let snapshot = part_with_delta.snapshot();
177        let delta = part_with_delta.delta();
178        let last_delta_key = delta.last_key_value().map(|(k, _)| k.as_normal_expect());
179
180        // Generate delete changes first, because deletes are skipped during iteration over
181        // `part_with_delta` in the next step.
182        for (key, change) in delta {
183            if change.is_delete() {
184                part_changes.insert(
185                    key.as_normal_expect().clone(),
186                    Record::Delete {
187                        old_row: snapshot.get(key).unwrap().clone(),
188                    },
189                );
190            }
191        }
192
193        for AffectedRange {
194            first_frame_start,
195            first_curr_key,
196            last_curr_key,
197            last_frame_end,
198        } in affected_ranges
199        {
200            assert!(first_frame_start <= first_curr_key);
201            assert!(first_curr_key <= last_curr_key);
202            assert!(last_curr_key <= last_frame_end);
203            assert!(first_frame_start.is_normal());
204            assert!(first_curr_key.is_normal());
205            assert!(last_curr_key.is_normal());
206            assert!(last_frame_end.is_normal());
207
208            let last_delta_key = last_delta_key.unwrap();
209
210            if let Some(accessed_range) = accessed_range.as_mut() {
211                let min_start = first_frame_start
212                    .as_normal_expect()
213                    .min(accessed_range.start())
214                    .clone();
215                let max_end = last_frame_end
216                    .as_normal_expect()
217                    .max(accessed_range.end())
218                    .clone();
219                *accessed_range = min_start..=max_end;
220            } else {
221                accessed_range = Some(
222                    first_frame_start.as_normal_expect().clone()
223                        ..=last_frame_end.as_normal_expect().clone(),
224                );
225            }
226
227            let mut states =
228                WindowStates::new(calls.iter().map(create_window_state).try_collect()?);
229
230            // Populate window states with the affected range of rows.
231            {
232                let mut cursor = part_with_delta
233                    .before(first_frame_start)
234                    .expect("first frame start key must exist");
235
236                while let Some((key, row)) = cursor.next() {
237                    accessed_entry_count += 1;
238
239                    for (call, state) in calls.iter().zip_eq_fast(states.iter_mut()) {
240                        // TODO(rc): batch appending
241                        // TODO(rc): append not only the arguments but also the old output for optimization
242                        state.append(
243                            key.as_normal_expect().clone(),
244                            row.project(call.args.val_indices())
245                                .into_owned_row()
246                                .as_inner()
247                                .into(),
248                        );
249                    }
250
251                    if key == last_frame_end {
252                        break;
253                    }
254                }
255            }
256
257            // Slide to the first affected key. We can safely pass in `first_curr_key` here
258            // because it definitely exists in the states by the definition of affected range.
259            states.just_slide_to(first_curr_key.as_normal_expect())?;
260            let mut curr_key_cursor = part_with_delta.before(first_curr_key).unwrap();
261            assert_eq!(
262                states.curr_key(),
263                curr_key_cursor
264                    .peek_next()
265                    .map(|(k, _)| k)
266                    .map(CacheKey::as_normal_expect)
267            );
268
269            // Slide and generate changes.
270            while let Some((key, row)) = curr_key_cursor.next() {
271                let mut should_stop = false;
272
273                let output = states.slide_no_evict_hint()?;
274                compute_count += 1;
275
276                let old_output = &row.as_inner()[input_schema_len..];
277                if !old_output.is_empty() && old_output == output {
278                    same_output_count += 1;
279
280                    if numbering_only {
281                        if has_rank {
282                            // It's possible that an `Insert` doesn't affect it's ties but affects
283                            // all the following rows, so we need to check the `order_key`.
284                            if key.as_normal_expect().order_key > last_delta_key.order_key {
285                                // there won't be any more changes after this point, we can stop early
286                                should_stop = true;
287                            }
288                        } else if key.as_normal_expect() >= last_delta_key {
289                            // there won't be any more changes after this point, we can stop early
290                            should_stop = true;
291                        }
292                    }
293                }
294
295                let new_row = OwnedRow::new(
296                    row.as_inner()
297                        .iter()
298                        .take(input_schema_len)
299                        .cloned()
300                        .chain(output)
301                        .collect(),
302                );
303
304                if let Some(old_row) = snapshot.get(key).cloned() {
305                    // update
306                    if old_row != new_row {
307                        part_changes.insert(
308                            key.as_normal_expect().clone(),
309                            Record::Update { old_row, new_row },
310                        );
311                    }
312                } else {
313                    // insert
314                    part_changes.insert(key.as_normal_expect().clone(), Record::Insert { new_row });
315                }
316
317                if should_stop || key == last_curr_key {
318                    break;
319                }
320            }
321        }
322
323        self.stats.accessed_entry_count += accessed_entry_count;
324        self.stats.compute_count += compute_count;
325        self.stats.same_output_count += same_output_count;
326
327        Ok((part_changes, accessed_range))
328    }
329
330    /// Write a change record to state table and cache.
331    /// This function must be called after finding affected ranges, which means the change records
332    /// should never exceed the cached range.
333    pub fn write_record(
334        &mut self,
335        table: &mut StateTable<S>,
336        key: StateKey,
337        record: Record<OwnedRow>,
338    ) {
339        table.write_record(record.as_ref());
340        match record {
341            Record::Insert { new_row } | Record::Update { new_row, .. } => {
342                self.range_cache.insert(CacheKey::from(key), new_row);
343            }
344            Record::Delete { .. } => {
345                self.range_cache.remove(&CacheKey::from(key));
346
347                if self.range_cache.normal_len() == 0 && self.range_cache.len() == 1 {
348                    // only one sentinel remains, should insert the other
349                    self.range_cache
350                        .insert(CacheKey::Smallest, OwnedRow::empty());
351                    self.range_cache
352                        .insert(CacheKey::Largest, OwnedRow::empty());
353                }
354            }
355        }
356    }
357
358    /// Clean up stale rows of the partition, i.e., rows whose watermark column value is below the
359    /// given `watermark`, except the `n_retain` ones that are closest to the watermark boundary.
360    /// See [`StateCleaning`] for why this is safe.
361    ///
362    /// Returns the number of deleted rows, and whether there may be more stale rows to delete
363    /// because of the per-round limit.
364    pub async fn clean_stale_rows(
365        &mut self,
366        table: &mut StateTable<S>,
367        cleaning: &StateCleaning,
368        watermark: &ScalarImpl,
369    ) -> StreamExecutorResult<(usize, bool)> {
370        let watermark_ref = watermark.as_scalar_ref_impl();
371        let is_stale = |row: &OwnedRow| match row.datum_at(cleaning.watermark_col_idx) {
372            Some(value) => value.default_cmp(&watermark_ref).is_lt(),
373            None => false, // NULLs are ordered as the largest values, never stale
374        };
375        // We collect at most this many stale rows, ordered from the farthest to the closest to
376        // the watermark boundary. If the limit is reached, there're at least `n_retain` collected
377        // stale rows closer to the boundary than the first `MAX_STALE_ROWS_TO_DELETE_PER_ROUND`
378        // ones, so it's safe to delete the latter.
379        let max_to_collect = MAX_STALE_ROWS_TO_DELETE_PER_ROUND.saturating_add(cleaning.n_retain);
380
381        let cache_covers_stale_end = if cleaning.stale_rows_at_front {
382            !self.range_cache.left_is_sentinel()
383        } else {
384            !self.range_cache.right_is_sentinel()
385        };
386
387        let mut stale_rows: Vec<(CacheKey, OwnedRow)> = Vec::new();
388        if cache_covers_stale_end {
389            // All stale rows are in the cache, no need to scan the table.
390            let entries: Box<dyn Iterator<Item = (&CacheKey, &OwnedRow)> + '_> =
391                if cleaning.stale_rows_at_front {
392                    Box::new(self.range_cache.inner().iter())
393                } else {
394                    Box::new(self.range_cache.inner().iter().rev())
395                };
396            stale_rows.extend(
397                entries
398                    .take_while(|(key, row)| key.is_normal() && is_stale(row))
399                    .take(max_to_collect)
400                    .map(|(key, row)| (key.clone(), row.clone())),
401            );
402        } else {
403            // The cache doesn't cover the stale end of the partition, scan the table instead.
404            let watermark_row = OwnedRow::new(vec![Some(watermark.clone())]);
405            let sub_range: (Bound<OwnedRow>, Bound<OwnedRow>) = if cleaning.stale_rows_at_front {
406                (Bound::Unbounded, Bound::Excluded(watermark_row))
407            } else {
408                (Bound::Excluded(watermark_row), Bound::Unbounded)
409            };
410            let stream: BoxedRowStream<'_> = if cleaning.stale_rows_at_front {
411                table
412                    .iter_with_prefix(
413                        self.deduped_part_key,
414                        &sub_range,
415                        PrefetchOptions::default(),
416                    )
417                    .await?
418                    .boxed()
419            } else {
420                table
421                    .rev_iter_with_prefix(
422                        self.deduped_part_key,
423                        &sub_range,
424                        PrefetchOptions::default(),
425                    )
426                    .await?
427                    .boxed()
428            };
429
430            #[for_await]
431            for row in stream {
432                let row: OwnedRow = row?.into_owned_row();
433                if !is_stale(&row) {
434                    break;
435                }
436                let key = self.row_conv.row_to_state_key(&row)?;
437                stale_rows.push((CacheKey::from(key), row));
438                if stale_rows.len() >= max_to_collect {
439                    break;
440                }
441            }
442        }
443
444        let has_more = stale_rows.len() >= max_to_collect;
445        let n_to_delete = if has_more {
446            MAX_STALE_ROWS_TO_DELETE_PER_ROUND
447        } else {
448            stale_rows.len().saturating_sub(cleaning.n_retain)
449        };
450        for (key, row) in stale_rows.into_iter().take(n_to_delete) {
451            table.delete(row);
452            self.range_cache.remove(&key);
453        }
454        if n_to_delete > 0 && self.range_cache.normal_len() == 0 && self.range_cache.len() == 1 {
455            // only one sentinel remains, should insert the other
456            self.range_cache
457                .insert(CacheKey::Smallest, OwnedRow::empty());
458            self.range_cache
459                .insert(CacheKey::Largest, OwnedRow::empty());
460        }
461
462        tracing::trace!(
463            partition=?self.deduped_part_key,
464            n_deleted=n_to_delete,
465            has_more,
466            "cleaned stale rows in the partition"
467        );
468
469        Ok((n_to_delete, has_more))
470    }
471
472    /// Find all ranges in the partition that are affected by the given delta.
473    /// The returned ranges are guaranteed to be sorted and non-overlapping. All keys in the ranges
474    /// are guaranteed to be cached, which means they should be [`Sentinelled::Normal`]s.
475    async fn find_affected_ranges<'s, 'delta>(
476        &'s mut self,
477        table: &StateTable<S>,
478        delta: &'delta mut PartitionDelta,
479    ) -> StreamExecutorResult<(
480        DeltaBTreeMap<'delta, CacheKey, OwnedRow>,
481        Vec<AffectedRange<'delta>>,
482    )>
483    where
484        'a: 'delta,
485        's: 'delta,
486    {
487        if delta.is_empty() {
488            return Ok((DeltaBTreeMap::new(self.range_cache.inner(), delta), vec![]));
489        }
490
491        self.ensure_delta_in_cache(table, delta).await?;
492        let delta = &*delta; // let's make it immutable
493
494        let delta_first = delta.first_key_value().unwrap().0.as_normal_expect();
495        let delta_last = delta.last_key_value().unwrap().0.as_normal_expect();
496
497        let range_frame_logical_curr =
498            calc_logical_curr_for_range_frames(&self.calls.range_frames, delta_first, delta_last);
499
500        loop {
501            // TERMINATEABILITY: `extend_cache_leftward_by_n` and `extend_cache_rightward_by_n` keep
502            // pushing the cache to the boundary of current partition. In these two methods, when
503            // any side of boundary is reached, the sentinel key will be removed, so finally
504            // `Self::find_affected_ranges_readonly` will return `Ok`.
505
506            // SAFETY: Here we shortly borrow the range cache and turn the reference into a
507            // `'delta` one to bypass the borrow checker. This is safe because we only return
508            // the reference once we don't need to do any further mutation.
509            let cache_inner = unsafe { &*(self.range_cache.inner() as *const _) };
510            let part_with_delta = DeltaBTreeMap::new(cache_inner, delta);
511
512            self.stats.lookup_count += 1;
513            let res = self
514                .find_affected_ranges_readonly(part_with_delta, range_frame_logical_curr.as_ref());
515
516            let (need_extend_leftward, need_extend_rightward) = match res {
517                Ok(ranges) => return Ok((part_with_delta, ranges)),
518                Err(cache_extend_hint) => cache_extend_hint,
519            };
520
521            if need_extend_leftward {
522                self.stats.left_miss_count += 1;
523                tracing::trace!(partition=?self.deduped_part_key, "partition cache left extension triggered");
524                let left_most = self
525                    .range_cache
526                    .first_normal_key()
527                    .unwrap_or(delta_first)
528                    .clone();
529                self.extend_cache_leftward_by_n(table, &left_most).await?;
530            }
531            if need_extend_rightward {
532                self.stats.right_miss_count += 1;
533                tracing::trace!(partition=?self.deduped_part_key, "partition cache right extension triggered");
534                let right_most = self
535                    .range_cache
536                    .last_normal_key()
537                    .unwrap_or(delta_last)
538                    .clone();
539                self.extend_cache_rightward_by_n(table, &right_most).await?;
540            }
541            tracing::trace!(partition=?self.deduped_part_key, "partition cache extended");
542        }
543    }
544
545    async fn ensure_delta_in_cache(
546        &mut self,
547        table: &StateTable<S>,
548        delta: &mut PartitionDelta,
549    ) -> StreamExecutorResult<()> {
550        if delta.is_empty() {
551            return Ok(());
552        }
553
554        let delta_first = delta.first_key_value().unwrap().0.as_normal_expect();
555        let delta_last = delta.last_key_value().unwrap().0.as_normal_expect();
556
557        if self.cache_policy.is_full() {
558            // ensure everything is in the cache
559            self.extend_cache_to_boundary(table).await?;
560        } else {
561            // TODO(rc): later we should extend cache using `self.calls.super_rows_frame_bounds` and
562            // `range_frame_logical_curr` as hints.
563
564            // ensure the cache covers all delta (if possible)
565            self.extend_cache_by_range(table, delta_first..=delta_last)
566                .await?;
567        }
568
569        if !enable_strict_consistency() {
570            // in non-strict mode, we should ensure the delta is consistent with the cache
571            let cache = self.range_cache.inner();
572            delta.retain(|key, change| match &*change {
573                Change::Insert(_) => {
574                    // this also includes the case of double-insert and ghost-update,
575                    // but since we already lost the information, let's just ignore it
576                    true
577                }
578                Change::Delete => {
579                    // if the key is not in the cache, it's a ghost-delete
580                    let consistent = cache.contains_key(key);
581                    if !consistent {
582                        consistency_error!(?key, "removing a row with non-existing key");
583                    }
584                    consistent
585                }
586            });
587        }
588
589        Ok(())
590    }
591
592    /// Try to find affected ranges on immutable range cache + delta. If the algorithm reaches
593    /// any sentinel node in the cache, which means some entries in the affected range may be
594    /// in the state table, it returns an `Err((bool, bool))` to notify the caller that the
595    /// left side or the right side or both sides of the cache should be extended.
596    ///
597    /// TODO(rc): Currently at most one range will be in the result vector. Ideally we should
598    /// recognize uncontinuous changes in the delta and find multiple ranges, but that will be
599    /// too complex for now.
600    fn find_affected_ranges_readonly<'delta>(
601        &self,
602        part_with_delta: DeltaBTreeMap<'delta, CacheKey, OwnedRow>,
603        range_frame_logical_curr: Option<&(Sentinelled<Datum>, Sentinelled<Datum>)>,
604    ) -> std::result::Result<Vec<AffectedRange<'delta>>, (bool, bool)> {
605        if part_with_delta.first_key().is_none() {
606            // nothing is left after applying the delta, meaning all entries are deleted
607            return Ok(vec![]);
608        }
609
610        let delta_first_key = part_with_delta.delta().first_key_value().unwrap().0;
611        let delta_last_key = part_with_delta.delta().last_key_value().unwrap().0;
612        let cache_key_pk_len = delta_first_key.as_normal_expect().pk.len();
613
614        if part_with_delta.snapshot().is_empty() {
615            // all existing keys are inserted in the delta
616            return Ok(vec![AffectedRange::new(
617                delta_first_key,
618                delta_first_key,
619                delta_last_key,
620                delta_last_key,
621            )]);
622        }
623
624        let first_key = part_with_delta.first_key().unwrap();
625        let last_key = part_with_delta.last_key().unwrap();
626
627        let first_curr_key = if self.calls.end_is_unbounded || delta_first_key == first_key {
628            // If the frame end is unbounded, or, the first key is in delta, then the frame corresponding
629            // to the first key is always affected.
630            first_key
631        } else {
632            let mut key = find_first_curr_for_rows_frame(
633                &self.calls.super_rows_frame_bounds,
634                part_with_delta,
635                delta_first_key,
636            );
637
638            if let Some((logical_first_curr, _)) = range_frame_logical_curr {
639                let logical_curr = logical_first_curr.as_normal_expect(); // otherwise should go `end_is_unbounded` branch
640                let new_key = find_left_for_range_frames(
641                    &self.calls.range_frames,
642                    part_with_delta,
643                    logical_curr,
644                    cache_key_pk_len,
645                );
646                key = std::cmp::min(key, new_key);
647            }
648
649            key
650        };
651
652        let last_curr_key = if self.calls.start_is_unbounded || delta_last_key == last_key {
653            // similar to `first_curr_key`
654            last_key
655        } else {
656            let mut key = find_last_curr_for_rows_frame(
657                &self.calls.super_rows_frame_bounds,
658                part_with_delta,
659                delta_last_key,
660            );
661
662            if let Some((_, logical_last_curr)) = range_frame_logical_curr {
663                let logical_curr = logical_last_curr.as_normal_expect(); // otherwise should go `start_is_unbounded` branch
664                let new_key = find_right_for_range_frames(
665                    &self.calls.range_frames,
666                    part_with_delta,
667                    logical_curr,
668                    cache_key_pk_len,
669                );
670                key = std::cmp::max(key, new_key);
671            }
672
673            key
674        };
675
676        {
677            // We quickly return if there's any sentinel in `[first_curr_key, last_curr_key]`,
678            // just for the sake of simplicity.
679            let mut need_extend_leftward = false;
680            let mut need_extend_rightward = false;
681            for key in [first_curr_key, last_curr_key] {
682                if key.is_smallest() {
683                    need_extend_leftward = true;
684                } else if key.is_largest() {
685                    need_extend_rightward = true;
686                }
687            }
688            if need_extend_leftward || need_extend_rightward {
689                return Err((need_extend_leftward, need_extend_rightward));
690            }
691        }
692
693        // From now on we definitely have two normal `curr_key`s.
694
695        if first_curr_key > last_curr_key {
696            // Note that we cannot move the this check before the above block, because for example,
697            // if the range cache contains `[Smallest, 5, Largest]`, and the delta contains only
698            // `Delete 5`, the frame is `RANGE BETWEEN CURRENT ROW AND CURRENT ROW`, then
699            // `first_curr_key` will be `Largest`, `last_curr_key` will be `Smallest`, in this case
700            // there may be some other entries with order value `5` in the table, which should be
701            // *affected*.
702            return Ok(vec![]);
703        }
704
705        let range_frame_logical_boundary = calc_logical_boundary_for_range_frames(
706            &self.calls.range_frames,
707            first_curr_key.as_normal_expect(),
708            last_curr_key.as_normal_expect(),
709        );
710
711        let first_frame_start = if self.calls.start_is_unbounded || first_curr_key == first_key {
712            // If the frame start is unbounded, or, the first curr key is the first key, then the first key
713            // always need to be included in the affected range.
714            first_key
715        } else {
716            let mut key = find_frame_start_for_rows_frame(
717                &self.calls.super_rows_frame_bounds,
718                part_with_delta,
719                first_curr_key,
720            );
721
722            if let Some((logical_first_start, _)) = range_frame_logical_boundary.as_ref() {
723                let logical_boundary = logical_first_start.as_normal_expect(); // otherwise should go `end_is_unbounded` branch
724                let new_key = find_left_for_range_frames(
725                    &self.calls.range_frames,
726                    part_with_delta,
727                    logical_boundary,
728                    cache_key_pk_len,
729                );
730                key = std::cmp::min(key, new_key);
731            }
732
733            key
734        };
735        assert!(first_frame_start <= first_curr_key);
736
737        let last_frame_end = if self.calls.end_is_unbounded || last_curr_key == last_key {
738            // similar to `first_frame_start`
739            last_key
740        } else {
741            let mut key = find_frame_end_for_rows_frame(
742                &self.calls.super_rows_frame_bounds,
743                part_with_delta,
744                last_curr_key,
745            );
746
747            if let Some((_, logical_last_end)) = range_frame_logical_boundary.as_ref() {
748                let logical_boundary = logical_last_end.as_normal_expect(); // otherwise should go `end_is_unbounded` branch
749                let new_key = find_right_for_range_frames(
750                    &self.calls.range_frames,
751                    part_with_delta,
752                    logical_boundary,
753                    cache_key_pk_len,
754                );
755                key = std::cmp::max(key, new_key);
756            }
757
758            key
759        };
760        assert!(last_frame_end >= last_curr_key);
761
762        let mut need_extend_leftward = false;
763        let mut need_extend_rightward = false;
764        for key in [
765            first_curr_key,
766            last_curr_key,
767            first_frame_start,
768            last_frame_end,
769        ] {
770            if key.is_smallest() {
771                need_extend_leftward = true;
772            } else if key.is_largest() {
773                need_extend_rightward = true;
774            }
775        }
776
777        if need_extend_leftward || need_extend_rightward {
778            Err((need_extend_leftward, need_extend_rightward))
779        } else {
780            Ok(vec![AffectedRange::new(
781                first_frame_start,
782                first_curr_key,
783                last_curr_key,
784                last_frame_end,
785            )])
786        }
787    }
788
789    async fn extend_cache_to_boundary(
790        &mut self,
791        table: &StateTable<S>,
792    ) -> StreamExecutorResult<()> {
793        if self.range_cache.normal_len() == self.range_cache.len() {
794            // no sentinel in the cache, meaning we already cached all entries of this partition
795            return Ok(());
796        }
797
798        tracing::trace!(partition=?self.deduped_part_key, "loading the whole partition into cache");
799
800        let mut new_cache = PartitionCache::new_without_sentinels(); // shouldn't use `new` here because we are extending to boundary
801        let sub_range: &(Bound<OwnedRow>, Bound<OwnedRow>) = &(Bound::Unbounded, Bound::Unbounded);
802        let table_iter = table
803            .iter_with_prefix(self.deduped_part_key, sub_range, PrefetchOptions::default())
804            .await?;
805
806        #[for_await]
807        for row in table_iter {
808            let row: OwnedRow = row?.into_owned_row();
809            new_cache.insert(self.row_conv.row_to_state_key(&row)?.into(), row);
810        }
811        *self.range_cache = new_cache;
812
813        Ok(())
814    }
815
816    /// Try to load the given range of entries from table into cache.
817    /// When the function returns, it's guaranteed that there's no entry in the table that is within
818    /// the given range but not in the cache.
819    async fn extend_cache_by_range(
820        &mut self,
821        table: &StateTable<S>,
822        range: RangeInclusive<&StateKey>,
823    ) -> StreamExecutorResult<()> {
824        if self.range_cache.normal_len() == self.range_cache.len() {
825            // no sentinel in the cache, meaning we already cached all entries of this partition
826            return Ok(());
827        }
828        assert!(self.range_cache.len() >= 2);
829
830        let cache_first_normal_key = self.range_cache.first_normal_key();
831        let cache_last_normal_key = self.range_cache.last_normal_key();
832
833        if cache_first_normal_key.is_some() && *range.end() < cache_first_normal_key.unwrap()
834            || cache_last_normal_key.is_some() && *range.start() > cache_last_normal_key.unwrap()
835        {
836            // completely not overlapping, for the sake of simplicity, we re-init the cache
837            tracing::debug!(
838                partition=?self.deduped_part_key,
839                cache_first=?cache_first_normal_key,
840                cache_last=?cache_last_normal_key,
841                range=?range,
842                "modified range is completely non-overlapping with the cached range, re-initializing the cache"
843            );
844            *self.range_cache = PartitionCache::new();
845        }
846
847        if self.cache_real_len() == 0 {
848            // no normal entry in the cache, just load the given range
849            let table_sub_range = (
850                Bound::Included(self.row_conv.state_key_to_table_sub_pk(range.start())?),
851                Bound::Included(self.row_conv.state_key_to_table_sub_pk(range.end())?),
852            );
853            tracing::debug!(
854                partition=?self.deduped_part_key,
855                table_sub_range=?table_sub_range,
856                "cache is empty, just loading the given range"
857            );
858            return self
859                .extend_cache_by_range_inner(table, table_sub_range)
860                .await;
861        }
862
863        let cache_real_first_key = self
864            .range_cache
865            .first_normal_key()
866            .expect("cache real len is not 0");
867        if self.range_cache.left_is_sentinel() && *range.start() < cache_real_first_key {
868            // extend leftward only if there's smallest sentinel
869            let table_sub_range = (
870                Bound::Included(self.row_conv.state_key_to_table_sub_pk(range.start())?),
871                Bound::Excluded(
872                    self.row_conv
873                        .state_key_to_table_sub_pk(cache_real_first_key)?,
874                ),
875            );
876            tracing::trace!(
877                partition=?self.deduped_part_key,
878                table_sub_range=?table_sub_range,
879                "loading the left half of given range"
880            );
881            self.extend_cache_by_range_inner(table, table_sub_range)
882                .await?;
883        }
884
885        let cache_real_last_key = self
886            .range_cache
887            .last_normal_key()
888            .expect("cache real len is not 0");
889        if self.range_cache.right_is_sentinel() && *range.end() > cache_real_last_key {
890            // extend rightward only if there's largest sentinel
891            let table_sub_range = (
892                Bound::Excluded(
893                    self.row_conv
894                        .state_key_to_table_sub_pk(cache_real_last_key)?,
895                ),
896                Bound::Included(self.row_conv.state_key_to_table_sub_pk(range.end())?),
897            );
898            tracing::trace!(
899                partition=?self.deduped_part_key,
900                table_sub_range=?table_sub_range,
901                "loading the right half of given range"
902            );
903            self.extend_cache_by_range_inner(table, table_sub_range)
904                .await?;
905        }
906
907        // prefetch rows before the start of the range
908        self.extend_cache_leftward_by_n(table, range.start())
909            .await?;
910
911        // prefetch rows after the end of the range
912        self.extend_cache_rightward_by_n(table, range.end()).await
913    }
914
915    async fn extend_cache_leftward_by_n(
916        &mut self,
917        table: &StateTable<S>,
918        hint_key: &StateKey,
919    ) -> StreamExecutorResult<()> {
920        if self.range_cache.normal_len() == self.range_cache.len() {
921            // no sentinel in the cache, meaning we already cached all entries of this partition
922            return Ok(());
923        }
924        assert!(self.range_cache.len() >= 2);
925
926        let left_second = {
927            let mut iter = self.range_cache.inner().iter();
928            let left_first = iter.next().unwrap().0;
929            if left_first.is_normal() {
930                // the leftside already reaches the beginning of this partition in the table
931                return Ok(());
932            }
933            iter.next().unwrap().0
934        };
935        let range_to_exclusive = match left_second {
936            CacheKey::Normal(smallest_in_cache) => smallest_in_cache,
937            CacheKey::Largest => hint_key, // no normal entry in the cache
938            _ => unreachable!(),
939        }
940        .clone();
941
942        self.extend_cache_leftward_by_n_inner(table, &range_to_exclusive)
943            .await?;
944
945        if self.cache_real_len() == 0 {
946            // Cache was empty, and extending leftward didn't add anything to the cache, but we
947            // can't just remove the smallest sentinel, we must also try extending rightward.
948            self.extend_cache_rightward_by_n_inner(table, hint_key)
949                .await?;
950            if self.cache_real_len() == 0 {
951                // still empty, meaning the table is empty
952                self.range_cache.remove(&CacheKey::Smallest);
953                self.range_cache.remove(&CacheKey::Largest);
954            }
955        }
956
957        Ok(())
958    }
959
960    async fn extend_cache_rightward_by_n(
961        &mut self,
962        table: &StateTable<S>,
963        hint_key: &StateKey,
964    ) -> StreamExecutorResult<()> {
965        if self.range_cache.normal_len() == self.range_cache.len() {
966            // no sentinel in the cache, meaning we already cached all entries of this partition
967            return Ok(());
968        }
969        assert!(self.range_cache.len() >= 2);
970
971        let right_second = {
972            let mut iter = self.range_cache.inner().iter();
973            let right_first = iter.next_back().unwrap().0;
974            if right_first.is_normal() {
975                // the rightside already reaches the end of this partition in the table
976                return Ok(());
977            }
978            iter.next_back().unwrap().0
979        };
980        let range_from_exclusive = match right_second {
981            CacheKey::Normal(largest_in_cache) => largest_in_cache,
982            CacheKey::Smallest => hint_key, // no normal entry in the cache
983            _ => unreachable!(),
984        }
985        .clone();
986
987        self.extend_cache_rightward_by_n_inner(table, &range_from_exclusive)
988            .await?;
989
990        if self.cache_real_len() == 0 {
991            // Cache was empty, and extending rightward didn't add anything to the cache, but we
992            // can't just remove the smallest sentinel, we must also try extending leftward.
993            self.extend_cache_leftward_by_n_inner(table, hint_key)
994                .await?;
995            if self.cache_real_len() == 0 {
996                // still empty, meaning the table is empty
997                self.range_cache.remove(&CacheKey::Smallest);
998                self.range_cache.remove(&CacheKey::Largest);
999            }
1000        }
1001
1002        Ok(())
1003    }
1004
1005    async fn extend_cache_by_range_inner(
1006        &mut self,
1007        table: &StateTable<S>,
1008        table_sub_range: (Bound<impl Row>, Bound<impl Row>),
1009    ) -> StreamExecutorResult<()> {
1010        let stream = table
1011            .iter_with_prefix(
1012                self.deduped_part_key,
1013                &table_sub_range,
1014                PrefetchOptions::default(),
1015            )
1016            .await?;
1017
1018        #[for_await]
1019        for row in stream {
1020            let row: OwnedRow = row?.into_owned_row();
1021            let key = self.row_conv.row_to_state_key(&row)?;
1022            self.range_cache.insert(CacheKey::from(key), row);
1023        }
1024
1025        Ok(())
1026    }
1027
1028    async fn extend_cache_leftward_by_n_inner(
1029        &mut self,
1030        table: &StateTable<S>,
1031        range_to_exclusive: &StateKey,
1032    ) -> StreamExecutorResult<()> {
1033        let mut n_extended = 0usize;
1034        {
1035            let sub_range = (
1036                Bound::<OwnedRow>::Unbounded,
1037                Bound::Excluded(
1038                    self.row_conv
1039                        .state_key_to_table_sub_pk(range_to_exclusive)?,
1040                ),
1041            );
1042            let rev_stream = table
1043                .rev_iter_with_prefix(
1044                    self.deduped_part_key,
1045                    &sub_range,
1046                    PrefetchOptions::default(),
1047                )
1048                .await?;
1049
1050            #[for_await]
1051            for row in rev_stream {
1052                let row: OwnedRow = row?.into_owned_row();
1053
1054                let key = self.row_conv.row_to_state_key(&row)?;
1055                self.range_cache.insert(CacheKey::from(key), row);
1056
1057                n_extended += 1;
1058                if n_extended == MAGIC_BATCH_SIZE {
1059                    break;
1060                }
1061            }
1062        }
1063
1064        if n_extended < MAGIC_BATCH_SIZE && self.cache_real_len() > 0 {
1065            // we reached the beginning of this partition in the table
1066            self.range_cache.remove(&CacheKey::Smallest);
1067        }
1068
1069        Ok(())
1070    }
1071
1072    async fn extend_cache_rightward_by_n_inner(
1073        &mut self,
1074        table: &StateTable<S>,
1075        range_from_exclusive: &StateKey,
1076    ) -> StreamExecutorResult<()> {
1077        let mut n_extended = 0usize;
1078        {
1079            let sub_range = (
1080                Bound::Excluded(
1081                    self.row_conv
1082                        .state_key_to_table_sub_pk(range_from_exclusive)?,
1083                ),
1084                Bound::<OwnedRow>::Unbounded,
1085            );
1086            let stream = table
1087                .iter_with_prefix(
1088                    self.deduped_part_key,
1089                    &sub_range,
1090                    PrefetchOptions::default(),
1091                )
1092                .await?;
1093
1094            #[for_await]
1095            for row in stream {
1096                let row: OwnedRow = row?.into_owned_row();
1097
1098                let key = self.row_conv.row_to_state_key(&row)?;
1099                self.range_cache.insert(CacheKey::from(key), row);
1100
1101                n_extended += 1;
1102                if n_extended == MAGIC_BATCH_SIZE {
1103                    break;
1104                }
1105            }
1106        }
1107
1108        if n_extended < MAGIC_BATCH_SIZE && self.cache_real_len() > 0 {
1109            // we reached the end of this partition in the table
1110            self.range_cache.remove(&CacheKey::Largest);
1111        }
1112
1113        Ok(())
1114    }
1115}