risingwave_stream/executor/top_n/
group_top_n_appendonly.rs1use std::collections::HashMap;
16
17use risingwave_common::array::Op;
18use risingwave_common::hash::HashKey;
19use risingwave_common::row::{RowDeserializer, RowExt};
20use risingwave_common::util::epoch::EpochPair;
21use risingwave_common::util::iter_util::ZipEqDebug;
22use risingwave_common::util::sort_util::{ColumnOrder, topn_watermark_forwardable_order_key};
23
24use super::group_top_n::GroupTopNCache;
25use super::top_n_cache::AppendOnlyTopNCacheTrait;
26use super::utils::*;
27use super::{ManagedTopNState, TopNCache};
28use crate::common::metrics::MetricsInfo;
29use crate::common::table::state_table::StateTablePostCommit;
30use crate::executor::monitor::GroupTopNMetrics;
31use crate::executor::prelude::*;
32use crate::executor::top_n::top_n_cache::TopNStaging;
33
34pub type AppendOnlyGroupTopNExecutor<K, S, const WITH_TIES: bool> =
38 TopNExecutorWrapper<InnerAppendOnlyGroupTopNExecutor<K, S, WITH_TIES>>;
39
40impl<K: HashKey, S: StateStore, const WITH_TIES: bool>
41 AppendOnlyGroupTopNExecutor<K, S, WITH_TIES>
42{
43 #[expect(clippy::too_many_arguments)]
44 pub fn new(
45 input: Executor,
46 ctx: ActorContextRef,
47 schema: Schema,
48 storage_key: Vec<ColumnOrder>,
49 offset_and_limit: (usize, usize),
50 order_by: Vec<ColumnOrder>,
51 group_by: Vec<usize>,
52 state_table: StateTable<S>,
53 watermark_epoch: AtomicU64Ref,
54 ) -> StreamResult<Self> {
55 let inner = InnerAppendOnlyGroupTopNExecutor::new(
56 schema,
57 storage_key,
58 offset_and_limit,
59 order_by,
60 group_by,
61 state_table,
62 watermark_epoch,
63 &ctx,
64 )?;
65 Ok(TopNExecutorWrapper { input, ctx, inner })
66 }
67}
68
69pub struct InnerAppendOnlyGroupTopNExecutor<K: HashKey, S: StateStore, const WITH_TIES: bool> {
70 schema: Schema,
71
72 limit: usize,
74
75 offset: usize,
77
78 storage_key_indices: Vec<usize>,
80
81 managed_state: ManagedTopNState<S>,
82
83 group_by: Vec<usize>,
85
86 watermark_order_key: Option<usize>,
88
89 caches: GroupTopNCache<K, WITH_TIES>,
91
92 cache_key_serde: CacheKeySerde,
94
95 topn_cache_min_capacity: usize,
97
98 metrics: GroupTopNMetrics,
99}
100
101impl<K: HashKey, S: StateStore, const WITH_TIES: bool>
102 InnerAppendOnlyGroupTopNExecutor<K, S, WITH_TIES>
103{
104 #[expect(clippy::too_many_arguments)]
105 pub fn new(
106 schema: Schema,
107 storage_key: Vec<ColumnOrder>,
108 offset_and_limit: (usize, usize),
109 order_by: Vec<ColumnOrder>,
110 group_by: Vec<usize>,
111 state_table: StateTable<S>,
112 watermark_epoch: AtomicU64Ref,
113 ctx: &ActorContext,
114 ) -> StreamResult<Self> {
115 let metrics_info = MetricsInfo::new(
116 ctx.streaming_metrics.clone(),
117 state_table.table_id(),
118 ctx.id,
119 "AppendOnlyGroupTopN",
120 );
121 let metrics = ctx.streaming_metrics.new_append_only_group_top_n_metrics(
122 state_table.table_id(),
123 ctx.id,
124 ctx.fragment_id,
125 );
126
127 let cache_key_serde = create_cache_key_serde(&storage_key, &schema, &order_by, &group_by);
128 let managed_state = ManagedTopNState::<S>::new(state_table, cache_key_serde.clone());
129
130 Ok(Self {
131 schema,
132 offset: offset_and_limit.0,
133 limit: offset_and_limit.1,
134 managed_state,
135 storage_key_indices: storage_key.into_iter().map(|op| op.column_index).collect(),
136 watermark_order_key: topn_watermark_forwardable_order_key(&order_by),
137 group_by,
138 caches: GroupTopNCache::new(watermark_epoch, metrics_info),
139 cache_key_serde,
140 topn_cache_min_capacity: ctx.config.developer.topn_cache_min_capacity,
141 metrics,
142 })
143 }
144}
145
146impl<K: HashKey, S: StateStore, const WITH_TIES: bool> TopNExecutorBase
147 for InnerAppendOnlyGroupTopNExecutor<K, S, WITH_TIES>
148where
149 TopNCache<WITH_TIES>: AppendOnlyTopNCacheTrait,
150{
151 type State = S;
152
153 async fn apply_chunk(
154 &mut self,
155 chunk: StreamChunk,
156 ) -> StreamExecutorResult<Option<StreamChunk>> {
157 let keys = K::build_many(&self.group_by, chunk.data_chunk());
158 let mut stagings: HashMap<K, TopNStaging> = HashMap::new(); let data_types = self.schema.data_types();
161 let deserializer = RowDeserializer::new(data_types.clone());
162 for (r, group_cache_key) in chunk.rows_with_holes().zip_eq_debug(keys.iter()) {
163 let Some((op, row_ref)) = r else {
164 continue;
165 };
166
167 let pk_row = row_ref.project(&self.storage_key_indices[self.group_by.len()..]);
169 let cache_key = serialize_pk_to_cache_key(pk_row, &self.cache_key_serde);
170
171 let group_key = row_ref.project(&self.group_by);
172 self.metrics.group_top_n_total_query_cache_count.inc();
173 if !self.caches.contains(group_cache_key) {
176 self.metrics.group_top_n_cache_miss_count.inc();
177 let mut topn_cache = TopNCache::with_min_capacity(
178 self.offset,
179 self.limit,
180 data_types.clone(),
181 self.topn_cache_min_capacity,
182 );
183 self.managed_state
184 .init_append_only_topn_cache(Some(group_key), &mut topn_cache)
185 .await?;
186 self.caches.put(group_cache_key.clone(), topn_cache);
187 }
188
189 let mut cache = self.caches.get_mut(group_cache_key).unwrap();
190 let staging = stagings.entry(group_cache_key.clone()).or_default();
191
192 debug_assert_eq!(op, Op::Insert);
193 cache.insert(
194 cache_key,
195 row_ref,
196 staging,
197 &mut self.managed_state,
198 &deserializer,
199 )?;
200 }
201
202 self.metrics
203 .group_top_n_cached_entry_count
204 .set(self.caches.len() as i64);
205
206 let mut chunk_builder = StreamChunkBuilder::unlimited(data_types, Some(chunk.capacity()));
207 for staging in stagings.into_values() {
208 for res in staging.into_deserialized_changes(&deserializer) {
209 let record = res?;
210 let _none = chunk_builder.append_record(record);
211 }
212 }
213
214 Ok(chunk_builder.take())
215 }
216
217 async fn flush_data(
218 &mut self,
219 epoch: EpochPair,
220 ) -> StreamExecutorResult<StateTablePostCommit<'_, S>> {
221 self.managed_state.flush(epoch).await
222 }
223
224 async fn try_flush_data(&mut self) -> StreamExecutorResult<()> {
225 self.managed_state.try_flush().await
226 }
227
228 fn clear_cache(&mut self) {
229 self.caches.clear();
230 }
231
232 fn evict(&mut self) {
233 self.caches.evict()
234 }
235
236 async fn init(&mut self, epoch: EpochPair) -> StreamExecutorResult<()> {
237 self.managed_state.init_epoch(epoch).await
238 }
239
240 async fn handle_watermark(&mut self, watermark: Watermark) -> Option<Watermark> {
241 if watermark.col_idx == self.group_by[0] {
242 self.managed_state.update_watermark(watermark.val.clone());
245 }
246 (self.group_by.contains(&watermark.col_idx)
251 || Some(watermark.col_idx) == self.watermark_order_key)
252 .then_some(watermark)
253 }
254}