1use std::collections::HashMap;
16use std::ops::{Deref, DerefMut};
17
18use risingwave_common::array::Op;
19use risingwave_common::hash::HashKey;
20use risingwave_common::row::{RowDeserializer, RowExt};
21use risingwave_common::util::epoch::EpochPair;
22use risingwave_common::util::iter_util::ZipEqDebug;
23use risingwave_common::util::sort_util::{ColumnOrder, topn_watermark_forwardable_order_key};
24
25use super::top_n_cache::TopNCacheTrait;
26use super::utils::*;
27use super::{ManagedTopNState, TopNCache};
28use crate::cache::ManagedLruCache;
29use crate::common::metrics::MetricsInfo;
30use crate::common::table::state_table::StateTablePostCommit;
31use crate::executor::monitor::GroupTopNMetrics;
32use crate::executor::prelude::*;
33use crate::executor::top_n::top_n_cache::TopNStaging;
34
35pub type GroupTopNExecutor<K, S, const WITH_TIES: bool> =
36 TopNExecutorWrapper<InnerGroupTopNExecutor<K, S, WITH_TIES>>;
37
38impl<K: HashKey, S: StateStore, const WITH_TIES: bool> GroupTopNExecutor<K, S, WITH_TIES> {
39 #[expect(clippy::too_many_arguments)]
40 pub fn new(
41 input: Executor,
42 ctx: ActorContextRef,
43 schema: Schema,
44 storage_key: Vec<ColumnOrder>,
45 offset_and_limit: (usize, usize),
46 order_by: Vec<ColumnOrder>,
47 group_by: Vec<usize>,
48 state_table: StateTable<S>,
49 watermark_epoch: AtomicU64Ref,
50 ) -> StreamResult<Self> {
51 let inner = InnerGroupTopNExecutor::new(
52 schema,
53 storage_key,
54 offset_and_limit,
55 order_by,
56 group_by,
57 state_table,
58 watermark_epoch,
59 &ctx,
60 )?;
61 Ok(TopNExecutorWrapper { input, ctx, inner })
62 }
63}
64
65pub struct InnerGroupTopNExecutor<K: HashKey, S: StateStore, const WITH_TIES: bool> {
66 schema: Schema,
67
68 limit: usize,
70
71 offset: usize,
73
74 storage_key_indices: Vec<usize>,
76
77 managed_state: ManagedTopNState<S>,
78
79 group_by: Vec<usize>,
81
82 watermark_order_key: Option<usize>,
84
85 caches: GroupTopNCache<K, WITH_TIES>,
87
88 cache_key_serde: CacheKeySerde,
90
91 topn_cache_min_capacity: usize,
93
94 metrics: GroupTopNMetrics,
95}
96
97impl<K: HashKey, S: StateStore, const WITH_TIES: bool> InnerGroupTopNExecutor<K, S, WITH_TIES> {
98 #[expect(clippy::too_many_arguments)]
99 pub fn new(
100 schema: Schema,
101 storage_key: Vec<ColumnOrder>,
102 offset_and_limit: (usize, usize),
103 order_by: Vec<ColumnOrder>,
104 group_by: Vec<usize>,
105 state_table: StateTable<S>,
106 watermark_epoch: AtomicU64Ref,
107 ctx: &ActorContext,
108 ) -> StreamResult<Self> {
109 let metrics_info = MetricsInfo::new(
110 ctx.streaming_metrics.clone(),
111 state_table.table_id(),
112 ctx.id,
113 "GroupTopN",
114 );
115 let metrics = ctx.streaming_metrics.new_group_top_n_metrics(
116 state_table.table_id(),
117 ctx.id,
118 ctx.fragment_id,
119 );
120
121 let cache_key_serde = create_cache_key_serde(&storage_key, &schema, &order_by, &group_by);
122 let managed_state = ManagedTopNState::<S>::new(state_table, cache_key_serde.clone());
123
124 Ok(Self {
125 schema,
126 offset: offset_and_limit.0,
127 limit: offset_and_limit.1,
128 managed_state,
129 storage_key_indices: storage_key.into_iter().map(|op| op.column_index).collect(),
130 watermark_order_key: topn_watermark_forwardable_order_key(&order_by),
131 group_by,
132 caches: GroupTopNCache::new(watermark_epoch, metrics_info),
133 cache_key_serde,
134 topn_cache_min_capacity: ctx.config.developer.topn_cache_min_capacity,
135 metrics,
136 })
137 }
138}
139
140pub struct GroupTopNCache<K: HashKey, const WITH_TIES: bool> {
141 data: ManagedLruCache<K, TopNCache<WITH_TIES>>,
142}
143
144impl<K: HashKey, const WITH_TIES: bool> GroupTopNCache<K, WITH_TIES> {
145 pub fn new(watermark_sequence: AtomicU64Ref, metrics_info: MetricsInfo) -> Self {
146 let cache = ManagedLruCache::unbounded(watermark_sequence, metrics_info);
147 Self { data: cache }
148 }
149}
150
151impl<K: HashKey, const WITH_TIES: bool> Deref for GroupTopNCache<K, WITH_TIES> {
152 type Target = ManagedLruCache<K, TopNCache<WITH_TIES>>;
153
154 fn deref(&self) -> &Self::Target {
155 &self.data
156 }
157}
158
159impl<K: HashKey, const WITH_TIES: bool> DerefMut for GroupTopNCache<K, WITH_TIES> {
160 fn deref_mut(&mut self) -> &mut Self::Target {
161 &mut self.data
162 }
163}
164
165impl<K: HashKey, S: StateStore, const WITH_TIES: bool> TopNExecutorBase
166 for InnerGroupTopNExecutor<K, S, WITH_TIES>
167where
168 TopNCache<WITH_TIES>: TopNCacheTrait,
169{
170 type State = S;
171
172 async fn apply_chunk(
173 &mut self,
174 chunk: StreamChunk,
175 ) -> StreamExecutorResult<Option<StreamChunk>> {
176 let keys = K::build_many(&self.group_by, chunk.data_chunk());
177 let mut stagings: HashMap<K, TopNStaging> = HashMap::new();
178
179 for (r, group_cache_key) in chunk.rows_with_holes().zip_eq_debug(keys.iter()) {
180 let Some((op, row_ref)) = r else {
181 continue;
182 };
183
184 let pk_row = row_ref.project(&self.storage_key_indices[self.group_by.len()..]);
186 let cache_key = serialize_pk_to_cache_key(pk_row, &self.cache_key_serde);
187
188 let group_key = row_ref.project(&self.group_by);
189 self.metrics.group_top_n_total_query_cache_count.inc();
190 if !self.caches.contains(group_cache_key) {
193 self.metrics.group_top_n_cache_miss_count.inc();
194 let mut topn_cache = TopNCache::with_min_capacity(
195 self.offset,
196 self.limit,
197 self.schema.data_types(),
198 self.topn_cache_min_capacity,
199 );
200 self.managed_state
201 .init_topn_cache(Some(group_key), &mut topn_cache)
202 .await?;
203 self.caches.put(group_cache_key.clone(), topn_cache);
204 }
205
206 let mut cache = self.caches.get_mut(group_cache_key).unwrap();
207 let staging = stagings.entry(group_cache_key.clone()).or_default();
208
209 match op {
211 Op::Insert | Op::UpdateInsert => {
212 self.managed_state.insert(row_ref);
213 cache.insert(cache_key, row_ref, staging);
214 }
215
216 Op::Delete | Op::UpdateDelete => {
217 self.managed_state.delete(row_ref);
218 cache
219 .delete(
220 Some(group_key),
221 &mut self.managed_state,
222 cache_key,
223 row_ref,
224 staging,
225 )
226 .await?;
227 }
228 }
229 }
230
231 self.metrics
232 .group_top_n_cached_entry_count
233 .set(self.caches.len() as i64);
234
235 let data_types = self.schema.data_types();
236 let deserializer = RowDeserializer::new(data_types.clone());
237 let mut chunk_builder = StreamChunkBuilder::unlimited(data_types, Some(chunk.capacity()));
238 for staging in stagings.into_values() {
239 for res in staging.into_deserialized_changes(&deserializer) {
240 let record = res?;
241 let _none = chunk_builder.append_record(record);
242 }
243 }
244 Ok(chunk_builder.take())
245 }
246
247 async fn flush_data(
248 &mut self,
249 epoch: EpochPair,
250 ) -> StreamExecutorResult<StateTablePostCommit<'_, S>> {
251 self.managed_state.flush(epoch).await
252 }
253
254 async fn try_flush_data(&mut self) -> StreamExecutorResult<()> {
255 self.managed_state.try_flush().await
256 }
257
258 fn clear_cache(&mut self) {
259 self.caches.clear();
260 }
261
262 fn evict(&mut self) {
263 self.caches.evict()
264 }
265
266 async fn init(&mut self, epoch: EpochPair) -> StreamExecutorResult<()> {
267 self.managed_state.init_epoch(epoch).await
268 }
269
270 async fn handle_watermark(&mut self, watermark: Watermark) -> Option<Watermark> {
271 if watermark.col_idx == self.group_by[0] {
272 self.managed_state.update_watermark(watermark.val.clone());
275 }
276 (self.group_by.contains(&watermark.col_idx)
281 || Some(watermark.col_idx) == self.watermark_order_key)
282 .then_some(watermark)
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use std::sync::atomic::AtomicU64;
289
290 use risingwave_common::array::stream_chunk::StreamChunkTestExt;
291 use risingwave_common::catalog::Field;
292 use risingwave_common::hash::SerializedKey;
293 use risingwave_common::util::epoch::test_epoch;
294 use risingwave_common::util::sort_util::OrderType;
295 use risingwave_storage::memory::MemoryStateStore;
296
297 use super::*;
298 use crate::executor::test_utils::top_n_executor::create_in_memory_state_table;
299 use crate::executor::test_utils::{MockSource, StreamExecutorTestExt};
300
301 fn create_schema() -> Schema {
302 Schema {
303 fields: vec![
304 Field::unnamed(DataType::Int64),
305 Field::unnamed(DataType::Int64),
306 Field::unnamed(DataType::Int64),
307 ],
308 }
309 }
310
311 fn storage_key() -> Vec<ColumnOrder> {
312 vec![
313 ColumnOrder::new(1, OrderType::ascending()),
314 ColumnOrder::new(2, OrderType::ascending()),
315 ColumnOrder::new(0, OrderType::ascending()),
316 ]
317 }
318
319 fn order_by_1() -> Vec<ColumnOrder> {
321 vec![ColumnOrder::new(2, OrderType::ascending())]
322 }
323
324 fn order_by_2() -> Vec<ColumnOrder> {
326 vec![ColumnOrder::new(0, OrderType::ascending())]
327 }
328
329 fn stream_key() -> StreamKey {
330 vec![1, 2, 0]
331 }
332
333 fn create_stream_chunks() -> Vec<StreamChunk> {
334 let chunk0 = StreamChunk::from_pretty(
335 " I I I
336 + 10 9 1
337 + 8 8 2
338 + 7 8 2
339 + 9 1 1
340 + 10 1 1
341 + 8 1 3",
342 );
343 let chunk1 = StreamChunk::from_pretty(
344 " I I I
345 - 10 9 1
346 - 8 8 2
347 - 10 1 1",
348 );
349 let chunk2 = StreamChunk::from_pretty(
350 " I I I
351 - 7 8 2
352 - 8 1 3
353 - 9 1 1",
354 );
355 let chunk3 = StreamChunk::from_pretty(
356 " I I I
357 + 5 1 1
358 + 2 1 1
359 + 3 1 2
360 + 4 1 3",
361 );
362 vec![chunk0, chunk1, chunk2, chunk3]
363 }
364
365 fn create_source() -> Executor {
366 let mut chunks = create_stream_chunks();
367 let schema = create_schema();
368 MockSource::with_messages(vec![
369 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
370 Message::Chunk(std::mem::take(&mut chunks[0])),
371 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
372 Message::Chunk(std::mem::take(&mut chunks[1])),
373 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
374 Message::Chunk(std::mem::take(&mut chunks[2])),
375 Message::Barrier(Barrier::new_test_barrier(test_epoch(4))),
376 Message::Chunk(std::mem::take(&mut chunks[3])),
377 Message::Barrier(Barrier::new_test_barrier(test_epoch(5))),
378 ])
379 .into_executor(schema, stream_key())
380 }
381
382 #[tokio::test]
383 async fn test_without_offset_and_with_limits() {
384 let source = create_source();
385 let state_table = create_in_memory_state_table(
386 &[DataType::Int64, DataType::Int64, DataType::Int64],
387 &[
388 OrderType::ascending(),
389 OrderType::ascending(),
390 OrderType::ascending(),
391 ],
392 &stream_key(),
393 )
394 .await;
395 let schema = source.schema().clone();
396 let top_n = GroupTopNExecutor::<SerializedKey, MemoryStateStore, false>::new(
397 source,
398 ActorContext::for_test(0),
399 schema,
400 storage_key(),
401 (0, 2),
402 order_by_1(),
403 vec![1],
404 state_table,
405 Arc::new(AtomicU64::new(0)),
406 )
407 .unwrap();
408 let mut top_n = top_n.boxed().execute();
409
410 top_n.expect_barrier().await;
412 assert_eq!(
413 top_n.expect_chunk().await.sort_rows(),
414 StreamChunk::from_pretty(
415 " I I I
416 + 10 9 1
417 + 8 8 2
418 + 7 8 2
419 + 9 1 1
420 + 10 1 1
421 ",
422 )
423 .sort_rows(),
424 );
425
426 top_n.expect_barrier().await;
428 assert_eq!(
429 top_n.expect_chunk().await.sort_rows(),
430 StreamChunk::from_pretty(
431 " I I I
432 - 10 9 1
433 - 8 8 2
434 - 10 1 1
435 + 8 1 3
436 ",
437 )
438 .sort_rows(),
439 );
440
441 top_n.expect_barrier().await;
443 assert_eq!(
444 top_n.expect_chunk().await.sort_rows(),
445 StreamChunk::from_pretty(
446 " I I I
447 - 7 8 2
448 - 8 1 3
449 - 9 1 1
450 ",
451 )
452 .sort_rows(),
453 );
454
455 top_n.expect_barrier().await;
457 assert_eq!(
458 top_n.expect_chunk().await.sort_rows(),
459 StreamChunk::from_pretty(
460 " I I I
461 + 5 1 1
462 + 2 1 1
463 ",
464 )
465 .sort_rows(),
466 );
467 }
468
469 #[tokio::test]
470 async fn test_with_offset_and_with_limits() {
471 let source = create_source();
472 let state_table = create_in_memory_state_table(
473 &[DataType::Int64, DataType::Int64, DataType::Int64],
474 &[
475 OrderType::ascending(),
476 OrderType::ascending(),
477 OrderType::ascending(),
478 ],
479 &stream_key(),
480 )
481 .await;
482 let schema = source.schema().clone();
483 let top_n = GroupTopNExecutor::<SerializedKey, MemoryStateStore, false>::new(
484 source,
485 ActorContext::for_test(0),
486 schema,
487 storage_key(),
488 (1, 2),
489 order_by_1(),
490 vec![1],
491 state_table,
492 Arc::new(AtomicU64::new(0)),
493 )
494 .unwrap();
495 let mut top_n = top_n.boxed().execute();
496
497 top_n.expect_barrier().await;
499 assert_eq!(
500 top_n.expect_chunk().await.sort_rows(),
501 StreamChunk::from_pretty(
502 " I I I
503 + 8 8 2
504 + 10 1 1
505 + 8 1 3
506 ",
507 )
508 .sort_rows(),
509 );
510
511 top_n.expect_barrier().await;
513 assert_eq!(
514 top_n.expect_chunk().await.sort_rows(),
515 StreamChunk::from_pretty(
516 " I I I
517 - 8 8 2
518 - 10 1 1
519 ",
520 )
521 .sort_rows(),
522 );
523
524 top_n.expect_barrier().await;
526 assert_eq!(
527 top_n.expect_chunk().await.sort_rows(),
528 StreamChunk::from_pretty(
529 " I I I
530 - 8 1 3",
531 )
532 .sort_rows(),
533 );
534
535 top_n.expect_barrier().await;
537 assert_eq!(
538 top_n.expect_chunk().await.sort_rows(),
539 StreamChunk::from_pretty(
540 " I I I
541 + 5 1 1
542 + 3 1 2
543 ",
544 )
545 .sort_rows(),
546 );
547 }
548
549 #[tokio::test]
550 async fn test_multi_group_key() {
551 let source = create_source();
552 let state_table = create_in_memory_state_table(
553 &[DataType::Int64, DataType::Int64, DataType::Int64],
554 &[
555 OrderType::ascending(),
556 OrderType::ascending(),
557 OrderType::ascending(),
558 ],
559 &stream_key(),
560 )
561 .await;
562 let schema = source.schema().clone();
563 let top_n = GroupTopNExecutor::<SerializedKey, MemoryStateStore, false>::new(
564 source,
565 ActorContext::for_test(0),
566 schema,
567 storage_key(),
568 (0, 2),
569 order_by_2(),
570 vec![1, 2],
571 state_table,
572 Arc::new(AtomicU64::new(0)),
573 )
574 .unwrap();
575 let mut top_n = top_n.boxed().execute();
576
577 top_n.expect_barrier().await;
579 assert_eq!(
580 top_n.expect_chunk().await.sort_rows(),
581 StreamChunk::from_pretty(
582 " I I I
583 + 10 9 1
584 + 8 8 2
585 + 7 8 2
586 + 9 1 1
587 + 10 1 1
588 + 8 1 3",
589 )
590 .sort_rows(),
591 );
592
593 top_n.expect_barrier().await;
595 assert_eq!(
596 top_n.expect_chunk().await.sort_rows(),
597 StreamChunk::from_pretty(
598 " I I I
599 - 10 9 1
600 - 8 8 2
601 - 10 1 1",
602 )
603 .sort_rows(),
604 );
605
606 top_n.expect_barrier().await;
608 assert_eq!(
609 top_n.expect_chunk().await.sort_rows(),
610 StreamChunk::from_pretty(
611 " I I I
612 - 7 8 2
613 - 8 1 3
614 - 9 1 1",
615 )
616 .sort_rows(),
617 );
618
619 top_n.expect_barrier().await;
621 assert_eq!(
622 top_n.expect_chunk().await.sort_rows(),
623 StreamChunk::from_pretty(
624 " I I I
625 + 5 1 1
626 + 2 1 1
627 + 3 1 2
628 + 4 1 3",
629 )
630 .sort_rows(),
631 );
632 }
633
634 #[tokio::test]
635 async fn test_compact_changes() {
636 let schema = create_schema();
637 let source = MockSource::with_messages(vec![
638 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
639 Message::Chunk(StreamChunk::from_pretty(
640 " I I I
641 + 0 0 9
642 + 0 0 8
643 + 0 0 7
644 + 0 0 6
645 + 0 1 15
646 + 0 1 14",
647 )),
648 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
649 Message::Chunk(StreamChunk::from_pretty(
650 " I I I
651 - 0 0 6
652 - 0 0 8
653 + 0 0 4
654 + 0 0 3
655 + 0 1 12
656 + 0 2 26
657 - 0 1 12
658 + 0 1 11",
659 )),
660 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
661 Message::Chunk(StreamChunk::from_pretty(
662 " I I I
663 + 0 0 11", )),
665 Message::Barrier(Barrier::new_test_barrier(test_epoch(4))),
666 ])
667 .into_executor(schema.clone(), vec![2]);
668
669 let state_table = create_in_memory_state_table(
670 &schema.data_types(),
671 &[
672 OrderType::ascending(),
673 OrderType::ascending(),
674 OrderType::ascending(),
675 ],
676 &[0, 1, 2], )
678 .await;
679
680 let top_n = GroupTopNExecutor::<SerializedKey, MemoryStateStore, false>::new(
681 source,
682 ActorContext::for_test(0),
683 schema,
684 vec![
685 ColumnOrder::new(0, OrderType::ascending()),
686 ColumnOrder::new(1, OrderType::ascending()),
687 ColumnOrder::new(2, OrderType::ascending()),
688 ],
689 (0, 2), vec![ColumnOrder::new(2, OrderType::ascending())],
691 vec![0, 1],
692 state_table,
693 Arc::new(AtomicU64::new(0)),
694 )
695 .unwrap();
696 let mut top_n = top_n.boxed().execute();
697
698 top_n.expect_barrier().await;
700
701 assert_eq!(
702 top_n.expect_chunk().await.sort_rows(),
703 StreamChunk::from_pretty(
704 " I I I
705 + 0 0 7
706 + 0 0 6
707 + 0 1 15
708 + 0 1 14",
709 )
710 .sort_rows(),
711 );
712 top_n.expect_barrier().await;
713
714 assert_eq!(
715 top_n.expect_chunk().await.sort_rows(),
716 StreamChunk::from_pretty(
717 " I I I
718 - 0 0 6
719 - 0 0 7
720 + 0 0 4
721 + 0 0 3
722 - 0 1 15
723 + 0 1 11
724 + 0 2 26",
725 )
726 .sort_rows(),
727 );
728 top_n.expect_barrier().await;
729
730 top_n.expect_barrier().await;
732 }
733}