1use std::collections::{BTreeMap, HashSet, btree_map};
16use std::marker::PhantomData;
17use std::ops::RangeInclusive;
18
19use delta_btree_map::Change;
20use itertools::Itertools;
21use risingwave_common::array::stream_record::Record;
22use risingwave_common::config::streaming::OverWindowCachePolicy as CachePolicy;
23use risingwave_common::row::RowExt;
24use risingwave_common::types::{DefaultOrd, DefaultOrdered, ScalarImpl};
25use risingwave_common::util::memcmp_encoding::{self, MemcmpEncoded};
26use risingwave_common::util::sort_util::OrderType;
27use risingwave_expr::window_function::{
28 RangeFrameBounds, RowsFrameBounds, StateKey, WindowFuncCall,
29};
30
31use super::frame_finder::merge_rows_frames;
32use super::over_partition::{OverPartition, PartitionDelta};
33use super::range_cache::{CacheKey, PartitionCache};
34use crate::cache::ManagedLruCache;
35use crate::common::change_buffer::ChangeBuffer;
36use crate::common::metrics::MetricsInfo;
37use crate::consistency::consistency_panic;
38use crate::executor::monitor::OverWindowMetrics;
39use crate::executor::prelude::*;
40
41pub struct OverWindowExecutor<S: StateStore> {
49 input: Executor,
50 inner: ExecutorInner<S>,
51}
52
53struct ExecutorInner<S: StateStore> {
54 actor_ctx: ActorContextRef,
55
56 schema: Schema,
57 calls: Calls,
58 deduped_part_key_indices: Vec<usize>,
59 order_key_indices: Vec<usize>,
60 order_key_data_types: Vec<DataType>,
61 order_key_order_types: Vec<OrderType>,
62 input_stream_key: Vec<usize>,
63 state_key_to_table_sub_pk_proj: Vec<usize>,
64
65 state_table: StateTable<S>,
66 watermark_sequence: AtomicU64Ref,
67
68 chunk_size: usize,
70 cache_policy: CachePolicy,
71 state_cleaning: Option<StateCleaning>,
73}
74
75struct ExecutionVars<S: StateStore> {
76 cached_partitions: ManagedLruCache<OwnedRow, PartitionCache>,
78 recently_accessed_ranges: BTreeMap<DefaultOrdered<OwnedRow>, RangeInclusive<StateKey>>,
80 cleaning_watermark: Option<ScalarImpl>,
82 touched_partitions: HashSet<OwnedRow>,
84 stats: ExecutionStats,
85 _phantom: PhantomData<S>,
86}
87
88#[derive(Debug)]
107pub(super) struct StateCleaning {
108 pub watermark_col_idx: usize,
110 pub stale_rows_at_front: bool,
113 pub n_retain: usize,
116}
117
118#[derive(Default)]
119struct ExecutionStats {
120 cache_miss: u64,
121 cache_lookup: u64,
122}
123
124impl<S: StateStore> Execute for OverWindowExecutor<S> {
125 fn execute(self: Box<Self>) -> crate::executor::BoxedMessageStream {
126 self.executor_inner().boxed()
127 }
128}
129
130impl<S: StateStore> ExecutorInner<S> {
131 fn get_partition_key(&self, full_row: impl Row) -> OwnedRow {
133 full_row
134 .project(&self.deduped_part_key_indices)
135 .into_owned_row()
136 }
137
138 fn get_input_pk(&self, full_row: impl Row) -> OwnedRow {
139 full_row.project(&self.input_stream_key).into_owned_row()
140 }
141
142 fn encode_order_key(&self, full_row: impl Row) -> StreamExecutorResult<MemcmpEncoded> {
144 Ok(memcmp_encoding::encode_row(
145 full_row.project(&self.order_key_indices),
146 &self.order_key_order_types,
147 )?)
148 }
149
150 fn row_to_cache_key(&self, full_row: impl Row + Copy) -> StreamExecutorResult<CacheKey> {
151 Ok(CacheKey::Normal(StateKey {
152 order_key: self.encode_order_key(full_row)?,
153 pk: self.get_input_pk(full_row).into(),
154 }))
155 }
156}
157
158pub struct OverWindowExecutorArgs<S: StateStore> {
159 pub actor_ctx: ActorContextRef,
160
161 pub input: Executor,
162
163 pub schema: Schema,
164 pub calls: Vec<WindowFuncCall>,
165 pub partition_key_indices: Vec<usize>,
166 pub order_key_indices: Vec<usize>,
167 pub order_key_order_types: Vec<OrderType>,
168
169 pub state_table: StateTable<S>,
170 pub watermark_epoch: AtomicU64Ref,
171 pub metrics: Arc<StreamingMetrics>,
172
173 pub chunk_size: usize,
174 pub cache_policy: CachePolicy,
175 pub enable_state_cleaning: bool,
177}
178
179pub(super) struct Calls {
183 calls: Vec<WindowFuncCall>,
184
185 pub(super) super_rows_frame_bounds: RowsFrameBounds,
187 pub(super) range_frames: Vec<RangeFrameBounds>,
189 pub(super) start_is_unbounded: bool,
190 pub(super) end_is_unbounded: bool,
191 pub(super) all_arg_indices: Vec<usize>,
193
194 pub(super) numbering_only: bool,
197 pub(super) has_rank: bool,
198}
199
200impl Calls {
201 fn new(calls: Vec<WindowFuncCall>) -> Self {
202 let rows_frames = calls
203 .iter()
204 .filter_map(|call| call.frame.bounds.as_rows())
205 .collect::<Vec<_>>();
206 let super_rows_frame_bounds = merge_rows_frames(&rows_frames);
207 let range_frames = calls
208 .iter()
209 .filter_map(|call| call.frame.bounds.as_range())
210 .cloned()
211 .collect::<Vec<_>>();
212
213 let start_is_unbounded = calls
214 .iter()
215 .any(|call| call.frame.bounds.start_is_unbounded());
216 let end_is_unbounded = calls
217 .iter()
218 .any(|call| call.frame.bounds.end_is_unbounded());
219
220 let all_arg_indices = calls
221 .iter()
222 .flat_map(|call| call.args.val_indices().iter().copied())
223 .dedup()
224 .collect();
225
226 let numbering_only = calls.iter().all(|call| call.kind.is_numbering());
227 let has_rank = calls.iter().any(|call| call.kind.is_rank());
228
229 Self {
230 calls,
231 super_rows_frame_bounds,
232 range_frames,
233 start_is_unbounded,
234 end_is_unbounded,
235 all_arg_indices,
236 numbering_only,
237 has_rank,
238 }
239 }
240
241 pub(super) fn iter(&self) -> impl ExactSizeIterator<Item = &WindowFuncCall> {
242 self.calls.iter()
243 }
244
245 pub(super) fn len(&self) -> usize {
246 self.calls.len()
247 }
248}
249
250impl<S: StateStore> OverWindowExecutor<S> {
251 pub fn new(args: OverWindowExecutorArgs<S>) -> Self {
252 let calls = Calls::new(args.calls);
253
254 let input_info = args.input.info().clone();
255 let input_schema = &input_info.schema;
256
257 let has_unbounded_frame = calls.start_is_unbounded || calls.end_is_unbounded;
258 let cache_policy = if has_unbounded_frame {
259 CachePolicy::Full
262 } else {
263 args.cache_policy
264 };
265
266 let order_key_data_types = args
267 .order_key_indices
268 .iter()
269 .map(|i| input_schema[*i].data_type())
270 .collect();
271
272 let state_key_to_table_sub_pk_proj = RowConverter::calc_state_key_to_table_sub_pk_proj(
273 &args.partition_key_indices,
274 &args.order_key_indices,
275 &input_info.stream_key,
276 );
277
278 let deduped_part_key_indices = {
279 let mut dedup = HashSet::new();
280 args.partition_key_indices
281 .iter()
282 .filter(|i| dedup.insert(**i))
283 .copied()
284 .collect()
285 };
286
287 let state_cleaning = if args.enable_state_cleaning {
288 let all_frames_bounded_rows = calls
289 .iter()
290 .all(|call| call.frame.bounds.is_rows() && !call.frame.bounds.is_unbounded());
291 let bounds = &calls.super_rows_frame_bounds;
292 match (bounds.n_preceding_rows(), bounds.n_following_rows()) {
293 (Some(n_preceding), Some(n_following))
294 if all_frames_bounded_rows && !args.order_key_indices.is_empty() =>
295 {
296 Some(StateCleaning {
297 watermark_col_idx: args.order_key_indices[0],
298 stale_rows_at_front: args.order_key_order_types[0].is_ascending(),
299 n_retain: n_preceding.saturating_add(n_following),
300 })
301 }
302 _ => {
303 tracing::warn!(
305 "state cleaning is enabled for over window with unbounded or non-`ROWS` frames, ignoring"
306 );
307 None
308 }
309 }
310 } else {
311 None
312 };
313
314 Self {
315 input: args.input,
316 inner: ExecutorInner {
317 actor_ctx: args.actor_ctx,
318 schema: args.schema,
319 calls,
320 deduped_part_key_indices,
321 order_key_indices: args.order_key_indices,
322 order_key_data_types,
323 order_key_order_types: args.order_key_order_types,
324 input_stream_key: input_info.stream_key,
325 state_key_to_table_sub_pk_proj,
326 state_table: args.state_table,
327 watermark_sequence: args.watermark_epoch,
328 chunk_size: args.chunk_size,
329 cache_policy,
330 state_cleaning,
331 },
332 }
333 }
334
335 fn merge_changes_in_chunk<'a>(
342 this: &'_ ExecutorInner<S>,
343 chunk: &'a StreamChunk,
344 ) -> impl Iterator<Item = Record<RowRef<'a>>> {
345 let mut cb = ChangeBuffer::with_capacity(chunk.cardinality());
346 for record in chunk.records() {
347 cb.apply_record(record, |row| this.get_input_pk(row));
348 }
349 cb.into_records()
350 }
351
352 #[try_stream(ok = StreamChunk, error = StreamExecutorError)]
353 async fn apply_chunk<'a>(
354 this: &'a mut ExecutorInner<S>,
355 vars: &'a mut ExecutionVars<S>,
356 chunk: StreamChunk,
357 metrics: &'a OverWindowMetrics,
358 ) {
359 let mut deltas: BTreeMap<DefaultOrdered<OwnedRow>, (PartitionDelta, PartitionDelta)> =
364 BTreeMap::new();
365 let mut key_change_updated_pks = HashSet::new();
367
368 for record in Self::merge_changes_in_chunk(this, &chunk) {
370 match record {
371 Record::Insert { new_row } => {
372 let part_key = this.get_partition_key(new_row).into();
373 let (delta, _) = deltas.entry(part_key).or_default();
374 delta.insert(
375 this.row_to_cache_key(new_row)?,
376 Change::Insert(new_row.into_owned_row()),
377 );
378 }
379 Record::Delete { old_row } => {
380 let part_key = this.get_partition_key(old_row).into();
381 let (delta, _) = deltas.entry(part_key).or_default();
382 delta.insert(this.row_to_cache_key(old_row)?, Change::Delete);
383 }
384 Record::Update { old_row, new_row } => {
385 let old_part_key = this.get_partition_key(old_row).into();
386 let new_part_key = this.get_partition_key(new_row).into();
387 let old_state_key = this.row_to_cache_key(old_row)?;
388 let new_state_key = this.row_to_cache_key(new_row)?;
389 if old_part_key == new_part_key && old_state_key == new_state_key {
390 let (delta, no_effect_delta) = deltas.entry(old_part_key).or_default();
392 if old_row.project(&this.calls.all_arg_indices)
393 == new_row.project(&this.calls.all_arg_indices)
394 {
395 no_effect_delta
397 .insert(old_state_key, Change::Insert(new_row.into_owned_row()));
398 } else {
399 delta.insert(old_state_key, Change::Insert(new_row.into_owned_row()));
400 }
401 } else if old_part_key == new_part_key {
402 key_change_updated_pks.insert(this.get_input_pk(old_row));
405 let (delta, _) = deltas.entry(old_part_key).or_default();
406 delta.insert(old_state_key, Change::Delete);
407 delta.insert(new_state_key, Change::Insert(new_row.into_owned_row()));
408 } else {
409 let (old_part_delta, _) = deltas.entry(old_part_key).or_default();
414 old_part_delta.insert(old_state_key, Change::Delete);
415 let (new_part_delta, _) = deltas.entry(new_part_key).or_default();
416 new_part_delta
417 .insert(new_state_key, Change::Insert(new_row.into_owned_row()));
418 }
419 }
420 }
421 }
422
423 let mut key_change_update_buffer: BTreeMap<DefaultOrdered<OwnedRow>, Record<OwnedRow>> =
425 BTreeMap::new();
426 let mut chunk_builder = StreamChunkBuilder::new(this.chunk_size, this.schema.data_types());
427
428 for (part_key, (delta, no_effect_delta)) in deltas {
430 vars.stats.cache_lookup += 1;
431 if !vars.cached_partitions.contains(&part_key.0) {
432 vars.stats.cache_miss += 1;
433 vars.cached_partitions
434 .put(part_key.0.clone(), PartitionCache::new());
435 }
436 let mut cache = vars.cached_partitions.get_mut(&part_key).unwrap();
437
438 for (key, change) in no_effect_delta {
443 let new_row = change.into_insert().unwrap(); let (old_row, from_cache) = if let Some(old_row) = cache.inner().get(&key).cloned()
446 {
447 (old_row, true)
449 } else {
450 let table_pk = (&new_row).project(this.state_table.pk_indices());
452 if let Some(old_row) = this.state_table.get_row(table_pk).await? {
455 (old_row, false)
456 } else {
457 consistency_panic!(?part_key, ?key, ?new_row, "updating non-existing row");
458 continue;
459 }
460 };
461
462 let input_len = new_row.len();
464 let new_row = OwnedRow::new(
465 new_row
466 .into_iter()
467 .chain(old_row.as_inner().iter().skip(input_len).cloned()) .collect(),
469 );
470
471 let record = Record::Update {
473 old_row: &old_row,
474 new_row: &new_row,
475 };
476 if let Some(chunk) = chunk_builder.append_record(record.as_ref()) {
477 yield chunk;
478 }
479 this.state_table.write_record(record);
480 if from_cache {
481 cache.insert(key, new_row);
482 }
483 }
484
485 let mut partition = OverPartition::new(
486 &part_key,
487 &mut cache,
488 this.cache_policy,
489 &this.calls,
490 RowConverter {
491 state_key_to_table_sub_pk_proj: &this.state_key_to_table_sub_pk_proj,
492 order_key_indices: &this.order_key_indices,
493 order_key_data_types: &this.order_key_data_types,
494 order_key_order_types: &this.order_key_order_types,
495 input_stream_key_indices: &this.input_stream_key,
496 },
497 );
498
499 if delta.is_empty() {
500 continue;
501 }
502
503 if this.state_cleaning.is_some() {
504 vars.touched_partitions.insert(part_key.0.clone());
505 }
506
507 let (part_changes, accessed_range) =
509 partition.build_changes(&this.state_table, delta).await?;
510
511 for (key, record) in part_changes {
512 if !key_change_updated_pks.contains(&key.pk) {
514 if let Some(chunk) = chunk_builder.append_record(record.as_ref()) {
515 yield chunk;
516 }
517 } else {
518 let pk = key.pk.clone();
521 let record = record.clone();
522 if let Some(existed) = key_change_update_buffer.remove(&key.pk) {
523 match (existed, record) {
524 (Record::Insert { new_row }, Record::Delete { old_row })
525 | (Record::Delete { old_row }, Record::Insert { new_row }) => {
526 if let Some(chunk) =
528 chunk_builder.append_record(Record::Update { old_row, new_row })
529 {
530 yield chunk;
531 }
532 }
533 (existed, record) => {
534 consistency_panic!(
536 ?existed,
537 ?record,
538 "other cases should not exist",
539 );
540
541 key_change_update_buffer.insert(pk, record);
542 if let Some(chunk) = chunk_builder.append_record(existed) {
543 yield chunk;
544 }
545 }
546 }
547 } else {
548 key_change_update_buffer.insert(pk, record);
549 }
550 }
551
552 partition.write_record(&mut this.state_table, key, record);
554 }
555
556 if !key_change_update_buffer.is_empty() {
557 consistency_panic!(
558 ?key_change_update_buffer,
559 "key-change update buffer should be empty after processing"
560 );
561 }
564
565 let cache_len = partition.cache_real_len();
566 let stats = partition.summarize();
567 metrics
568 .over_window_range_cache_entry_count
569 .set(cache_len as i64);
570 metrics
571 .over_window_range_cache_lookup_count
572 .inc_by(stats.lookup_count);
573 metrics
574 .over_window_range_cache_left_miss_count
575 .inc_by(stats.left_miss_count);
576 metrics
577 .over_window_range_cache_right_miss_count
578 .inc_by(stats.right_miss_count);
579 metrics
580 .over_window_accessed_entry_count
581 .inc_by(stats.accessed_entry_count);
582 metrics
583 .over_window_compute_count
584 .inc_by(stats.compute_count);
585 metrics
586 .over_window_same_output_count
587 .inc_by(stats.same_output_count);
588
589 if !this.cache_policy.is_full()
591 && let Some(accessed_range) = accessed_range
592 {
593 match vars.recently_accessed_ranges.entry(part_key) {
594 btree_map::Entry::Vacant(vacant) => {
595 vacant.insert(accessed_range);
596 }
597 btree_map::Entry::Occupied(mut occupied) => {
598 let recently_accessed_range = occupied.get_mut();
599 let min_start = accessed_range
600 .start()
601 .min(recently_accessed_range.start())
602 .clone();
603 let max_end = accessed_range
604 .end()
605 .max(recently_accessed_range.end())
606 .clone();
607 *recently_accessed_range = min_start..=max_end;
608 }
609 }
610 }
611 }
612
613 if let Some(chunk) = chunk_builder.take() {
615 yield chunk;
616 }
617 }
618
619 async fn clean_state(
623 this: &mut ExecutorInner<S>,
624 vars: &mut ExecutionVars<S>,
625 ) -> StreamExecutorResult<usize> {
626 let touched = std::mem::take(&mut vars.touched_partitions);
627 let (Some(cleaning), Some(watermark)) = (&this.state_cleaning, &vars.cleaning_watermark)
628 else {
629 return Ok(0);
630 };
631 if touched.is_empty() {
632 return Ok(0);
633 }
634
635 let row_conv = RowConverter {
636 state_key_to_table_sub_pk_proj: &this.state_key_to_table_sub_pk_proj,
637 order_key_indices: &this.order_key_indices,
638 order_key_data_types: &this.order_key_data_types,
639 order_key_order_types: &this.order_key_order_types,
640 input_stream_key_indices: &this.input_stream_key,
641 };
642
643 let mut n_deleted = 0;
644 for part_key in touched {
645 let mut cache_guard = vars.cached_partitions.get_mut(&part_key);
646 let mut temp_cache = PartitionCache::new();
649 let cache = match cache_guard.as_deref_mut() {
650 Some(cache) => cache,
651 None => &mut temp_cache,
652 };
653 let mut partition =
654 OverPartition::new(&part_key, cache, this.cache_policy, &this.calls, row_conv);
655 let (n, has_more) = partition
656 .clean_stale_rows(&mut this.state_table, cleaning, watermark)
657 .await?;
658 n_deleted += n;
659 if has_more {
660 vars.touched_partitions.insert(part_key.clone());
662 }
663 }
664 Ok(n_deleted)
665 }
666
667 #[try_stream(ok = Message, error = StreamExecutorError)]
668 async fn executor_inner(self) {
669 let OverWindowExecutor {
670 input,
671 inner: mut this,
672 } = self;
673
674 let metrics_info = MetricsInfo::new(
675 this.actor_ctx.streaming_metrics.clone(),
676 this.state_table.table_id(),
677 this.actor_ctx.id,
678 "OverWindow",
679 );
680
681 let metrics = metrics_info.metrics.new_over_window_metrics(
682 this.state_table.table_id(),
683 this.actor_ctx.id,
684 this.actor_ctx.fragment_id,
685 );
686
687 let mut vars = ExecutionVars {
688 cached_partitions: ManagedLruCache::unbounded(
689 this.watermark_sequence.clone(),
690 metrics_info,
691 ),
692 recently_accessed_ranges: Default::default(),
693 cleaning_watermark: None,
694 touched_partitions: Default::default(),
695 stats: Default::default(),
696 _phantom: PhantomData::<S>,
697 };
698
699 let mut input = input.execute();
700 let barrier = expect_first_barrier(&mut input).await?;
701 let first_epoch = barrier.epoch;
702 yield Message::Barrier(barrier);
703 this.state_table.init_epoch(first_epoch).await?;
704
705 #[for_await]
706 for msg in input {
707 let msg = msg?;
708 match msg {
709 Message::Watermark(watermark) => {
710 if let Some(cleaning) = &this.state_cleaning
711 && watermark.col_idx == cleaning.watermark_col_idx
712 && vars
713 .cleaning_watermark
714 .as_ref()
715 .is_none_or(|old| old.default_cmp(&watermark.val).is_lt())
716 {
717 vars.cleaning_watermark = Some(watermark.val);
719 }
720 continue;
724 }
725 Message::Chunk(chunk) => {
726 #[for_await]
727 for chunk in Self::apply_chunk(&mut this, &mut vars, chunk, &metrics) {
728 yield Message::Chunk(chunk?);
729 }
730 this.state_table.try_flush().await?;
731
732 vars.cached_partitions.evict();
740 }
741 Message::Barrier(barrier) => {
742 let n_cleaned = Self::clean_state(&mut this, &mut vars).await?;
743 metrics
744 .over_window_state_cleaned_row_count
745 .inc_by(n_cleaned as u64);
746
747 let post_commit = this.state_table.commit(barrier.epoch).await?;
748
749 let update_vnode_bitmap = barrier.as_update_vnode_bitmap(this.actor_ctx.id);
750 yield Message::Barrier(barrier);
751
752 vars.cached_partitions.evict();
753
754 metrics
755 .over_window_cached_entry_count
756 .set(vars.cached_partitions.len() as _);
757 metrics
758 .over_window_cache_lookup_count
759 .inc_by(std::mem::take(&mut vars.stats.cache_lookup));
760 metrics
761 .over_window_cache_miss_count
762 .inc_by(std::mem::take(&mut vars.stats.cache_miss));
763
764 if let Some((_, cache_may_stale)) =
765 post_commit.post_yield_barrier(update_vnode_bitmap).await?
766 && cache_may_stale
767 {
768 vars.cached_partitions.clear();
769 vars.recently_accessed_ranges.clear();
770 vars.touched_partitions.clear();
771 }
772
773 if !this.cache_policy.is_full() {
774 for (part_key, recently_accessed_range) in
775 std::mem::take(&mut vars.recently_accessed_ranges)
776 {
777 if let Some(mut range_cache) =
778 vars.cached_partitions.get_mut(&part_key.0)
779 {
780 range_cache.shrink(
781 &part_key.0,
782 this.cache_policy,
783 recently_accessed_range,
784 );
785 }
786 }
787 }
788 }
789 }
790 }
791 }
792}
793
794#[derive(Debug, Clone, Copy)]
807pub(super) struct RowConverter<'a> {
808 state_key_to_table_sub_pk_proj: &'a [usize],
809 order_key_indices: &'a [usize],
810 order_key_data_types: &'a [DataType],
811 order_key_order_types: &'a [OrderType],
812 input_stream_key_indices: &'a [usize],
813}
814
815impl<'a> RowConverter<'a> {
816 pub(super) fn calc_state_key_to_table_sub_pk_proj(
820 partition_key_indices: &[usize],
821 order_key_indices: &[usize],
822 input_stream_key_indices: &'a [usize],
823 ) -> Vec<usize> {
824 let mut projection =
826 Vec::with_capacity(order_key_indices.len() + input_stream_key_indices.len());
827 let mut col_dedup: HashSet<usize> = partition_key_indices.iter().copied().collect();
828 for (proj_idx, key_idx) in order_key_indices
829 .iter()
830 .chain(input_stream_key_indices.iter())
831 .enumerate()
832 {
833 if col_dedup.insert(*key_idx) {
834 projection.push(proj_idx);
835 }
836 }
837 projection.shrink_to_fit();
838 projection
839 }
840
841 pub(super) fn state_key_to_table_sub_pk(
843 &self,
844 key: &StateKey,
845 ) -> StreamExecutorResult<OwnedRow> {
846 Ok(memcmp_encoding::decode_row(
847 &key.order_key,
848 self.order_key_data_types,
849 self.order_key_order_types,
850 )?
851 .chain(key.pk.as_inner())
852 .project(self.state_key_to_table_sub_pk_proj)
853 .into_owned_row())
854 }
855
856 pub(super) fn row_to_state_key(
858 &self,
859 full_row: impl Row + Copy,
860 ) -> StreamExecutorResult<StateKey> {
861 Ok(StateKey {
862 order_key: memcmp_encoding::encode_row(
863 full_row.project(self.order_key_indices),
864 self.order_key_order_types,
865 )?,
866 pk: full_row
867 .project(self.input_stream_key_indices)
868 .into_owned_row()
869 .into(),
870 })
871 }
872}
873
874#[cfg(test)]
875mod tests {
876 use std::ops::Bound;
877
878 use futures::TryStreamExt;
879 use risingwave_common::catalog::{ColumnDesc, ColumnId, TableId};
880 use risingwave_common::util::epoch::{EpochPair, test_epoch};
881 use risingwave_storage::memory::MemoryStateStore;
882 use risingwave_storage::store::PrefetchOptions;
883
884 use super::*;
885 use crate::common::table::test_utils::gen_pbtable;
886
887 #[tokio::test]
888 async fn test_state_cleaning_large_retention() {
889 for order_type in [OrderType::ascending(), OrderType::descending()] {
890 for cached in [true, false] {
891 for n_retain in [usize::MAX - 65_534, usize::MAX - 1] {
895 let mut table = StateTable::from_table_catalog(
896 &gen_pbtable(
897 TableId::new(1),
898 vec![ColumnDesc::unnamed(ColumnId::new(0), DataType::Int64)],
899 vec![order_type],
900 vec![0],
901 0,
902 ),
903 MemoryStateStore::new(),
904 None,
905 )
906 .await;
907 table
908 .init_epoch(EpochPair::new_test_epoch(test_epoch(1)))
909 .await
910 .unwrap();
911 let rows = [10i64, 20, 30].map(|value| OwnedRow::new(vec![Some(value.into())]));
912 let row_conv = RowConverter {
913 state_key_to_table_sub_pk_proj: &[0],
914 order_key_indices: &[0],
915 order_key_data_types: &[DataType::Int64],
916 order_key_order_types: &[order_type],
917 input_stream_key_indices: &[0],
918 };
919 let mut cache = if cached {
920 PartitionCache::new_without_sentinels()
921 } else {
922 PartitionCache::new()
923 };
924 for row in &rows {
925 table.insert(row.clone());
926 if cached {
927 cache.insert(
928 CacheKey::from(row_conv.row_to_state_key(row).unwrap()),
929 row.clone(),
930 );
931 }
932 }
933 table
934 .commit_for_test(EpochPair::new_test_epoch(test_epoch(2)))
935 .await
936 .unwrap();
937
938 let partition_key = OwnedRow::empty();
939 let calls = Calls::new(vec![]);
940 let mut partition = OverPartition::new(
941 &partition_key,
942 &mut cache,
943 CachePolicy::Full,
944 &calls,
945 row_conv,
946 );
947 let cleaning = StateCleaning {
948 watermark_col_idx: 0,
949 stale_rows_at_front: order_type.is_ascending(),
950 n_retain,
951 };
952 assert_eq!(
953 partition
954 .clean_stale_rows(&mut table, &cleaning, &100i64.into())
955 .await
956 .unwrap(),
957 (0, false),
958 "order={order_type:?}, cached={cached}, n_retain={n_retain}",
959 );
960 table
961 .commit_for_test(EpochPair::new_test_epoch(test_epoch(3)))
962 .await
963 .unwrap();
964 let range: (Bound<OwnedRow>, Bound<OwnedRow>) =
965 (Bound::Unbounded, Bound::Unbounded);
966 let remaining: Vec<OwnedRow> = table
967 .iter_with_prefix(&partition_key, &range, PrefetchOptions::default())
968 .await
969 .unwrap()
970 .try_collect()
971 .await
972 .unwrap();
973 let mut expected = rows.to_vec();
974 if !order_type.is_ascending() {
975 expected.reverse();
976 }
977 assert_eq!(remaining, expected);
978 assert_eq!(cache.normal_len(), if cached { rows.len() } else { 0 });
979 }
980 }
981 }
982 }
983}