Skip to main content

risingwave_stream/executor/top_n/
top_n_appendonly.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use risingwave_common::array::Op;
16use risingwave_common::row::{RowDeserializer, RowExt};
17use risingwave_common::util::epoch::EpochPair;
18use risingwave_common::util::sort_util::{ColumnOrder, topn_watermark_forwardable_order_key};
19
20use super::top_n_cache::{AppendOnlyTopNCacheTrait, TopNStaging};
21use super::utils::*;
22use super::{ManagedTopNState, TopNCache};
23use crate::common::table::state_table::StateTablePostCommit;
24use crate::executor::prelude::*;
25
26/// If the input is append-only, `AppendOnlyGroupTopNExecutor` does not need
27/// to keep all the rows seen. As long as a record
28/// is no longer in the result set, it can be deleted.
29///
30/// TODO: Optimization: primary key may contain several columns and is used to determine
31/// the order, therefore the value part should not contain the same columns to save space.
32pub type AppendOnlyTopNExecutor<S, const WITH_TIES: bool> =
33    TopNExecutorWrapper<InnerAppendOnlyTopNExecutor<S, WITH_TIES>>;
34
35impl<S: StateStore, const WITH_TIES: bool> AppendOnlyTopNExecutor<S, WITH_TIES> {
36    pub fn new(
37        input: Executor,
38        ctx: ActorContextRef,
39        schema: Schema,
40        storage_key: Vec<ColumnOrder>,
41        offset_and_limit: (usize, usize),
42        order_by: Vec<ColumnOrder>,
43        state_table: StateTable<S>,
44    ) -> StreamResult<Self> {
45        Ok(TopNExecutorWrapper {
46            input,
47            ctx,
48            inner: InnerAppendOnlyTopNExecutor::new(
49                schema,
50                storage_key,
51                offset_and_limit,
52                order_by,
53                state_table,
54            )?,
55        })
56    }
57}
58
59pub struct InnerAppendOnlyTopNExecutor<S: StateStore, const WITH_TIES: bool> {
60    schema: Schema,
61
62    /// The storage key indices of the `TopNExecutor`
63    storage_key_indices: Vec<usize>,
64
65    /// We are interested in which element is in the range of [offset, offset+limit).
66    managed_state: ManagedTopNState<S>,
67
68    /// In-memory cache of top (N + N * `TOPN_CACHE_HIGH_CAPACITY_FACTOR`) rows
69    /// TODO: support WITH TIES
70    cache: TopNCache<WITH_TIES>,
71
72    /// Used for serializing pk into `CacheKey`.
73    cache_key_serde: CacheKeySerde,
74
75    /// The `ORDER BY` column whose watermarks can be forwarded, if any.
76    watermark_order_key: Option<usize>,
77}
78
79impl<S: StateStore, const WITH_TIES: bool> InnerAppendOnlyTopNExecutor<S, WITH_TIES> {
80    pub fn new(
81        schema: Schema,
82        storage_key: Vec<ColumnOrder>,
83        offset_and_limit: (usize, usize),
84        order_by: Vec<ColumnOrder>,
85        state_table: StateTable<S>,
86    ) -> StreamResult<Self> {
87        let num_offset = offset_and_limit.0;
88        let num_limit = offset_and_limit.1;
89
90        let cache_key_serde = create_cache_key_serde(&storage_key, &schema, &order_by, &[]);
91        let managed_state = ManagedTopNState::<S>::new(state_table, cache_key_serde.clone());
92        let data_types = schema.data_types();
93
94        Ok(Self {
95            schema,
96            managed_state,
97            storage_key_indices: storage_key.into_iter().map(|op| op.column_index).collect(),
98            cache: TopNCache::new(num_offset, num_limit, data_types),
99            cache_key_serde,
100            watermark_order_key: topn_watermark_forwardable_order_key(&order_by),
101        })
102    }
103}
104
105impl<S: StateStore, const WITH_TIES: bool> TopNExecutorBase
106    for InnerAppendOnlyTopNExecutor<S, WITH_TIES>
107where
108    TopNCache<WITH_TIES>: AppendOnlyTopNCacheTrait,
109{
110    type State = S;
111
112    async fn apply_chunk(
113        &mut self,
114        chunk: StreamChunk,
115    ) -> StreamExecutorResult<Option<StreamChunk>> {
116        let mut staging = TopNStaging::new();
117        let data_types = self.schema.data_types();
118        let deserializer = RowDeserializer::new(data_types.clone());
119        // apply the chunk to state table
120        for (op, row_ref) in chunk.rows() {
121            debug_assert_eq!(op, Op::Insert);
122            let pk_row = row_ref.project(&self.storage_key_indices);
123            let cache_key = serialize_pk_to_cache_key(pk_row, &self.cache_key_serde);
124            self.cache.insert(
125                cache_key,
126                row_ref,
127                &mut staging,
128                &mut self.managed_state,
129                &deserializer,
130            )?;
131        }
132
133        if staging.is_empty() {
134            return Ok(None);
135        }
136        let mut chunk_builder = StreamChunkBuilder::unlimited(data_types, Some(staging.len()));
137        for res in staging.into_deserialized_changes(&deserializer) {
138            let record = res?;
139            let _none = chunk_builder.append_record(record);
140        }
141        Ok(chunk_builder.take())
142    }
143
144    async fn flush_data(
145        &mut self,
146        epoch: EpochPair,
147    ) -> StreamExecutorResult<StateTablePostCommit<'_, S>> {
148        self.managed_state.flush(epoch).await
149    }
150
151    async fn try_flush_data(&mut self) -> StreamExecutorResult<()> {
152        self.managed_state.try_flush().await
153    }
154
155    async fn init(&mut self, epoch: EpochPair) -> StreamExecutorResult<()> {
156        self.managed_state.init_epoch(epoch).await?;
157        self.managed_state
158            .init_topn_cache(NO_GROUP_KEY, &mut self.cache)
159            .await
160    }
161
162    async fn handle_watermark(&mut self, watermark: Watermark) -> Option<Watermark> {
163        // Only watermarks on the first `ORDER BY` column ordered `ASC NULLS LAST` can be forwarded.
164        // See `topn_watermark_forwardable_order_key` for the reasoning.
165        (Some(watermark.col_idx) == self.watermark_order_key).then_some(watermark)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171
172    use risingwave_common::array::StreamChunk;
173    use risingwave_common::array::stream_chunk::StreamChunkTestExt;
174    use risingwave_common::catalog::{Field, Schema};
175    use risingwave_common::types::DataType;
176    use risingwave_common::util::epoch::test_epoch;
177    use risingwave_common::util::sort_util::{ColumnOrder, OrderType};
178
179    use super::AppendOnlyTopNExecutor;
180    use crate::executor::test_utils::top_n_executor::create_in_memory_state_table;
181    use crate::executor::test_utils::{MockSource, StreamExecutorTestExt};
182    use crate::executor::{ActorContext, Barrier, Execute, Executor, Message, StreamKey};
183
184    fn create_stream_chunks() -> Vec<StreamChunk> {
185        let chunk1 = StreamChunk::from_pretty(
186            "  I I
187            +  1 0
188            +  2 1
189            +  3 2
190            + 10 3
191            +  9 4
192            +  8 5",
193        );
194        let chunk2 = StreamChunk::from_pretty(
195            "  I I
196            +  7 6
197            +  3 7
198            +  1 8
199            +  9 9",
200        );
201        let chunk3 = StreamChunk::from_pretty(
202            " I  I
203            + 1 12
204            + 1 13
205            + 2 14
206            + 3 15",
207        );
208        vec![chunk1, chunk2, chunk3]
209    }
210
211    fn create_schema() -> Schema {
212        Schema {
213            fields: vec![
214                Field::unnamed(DataType::Int64),
215                Field::unnamed(DataType::Int64),
216            ],
217        }
218    }
219
220    fn storage_key() -> Vec<ColumnOrder> {
221        order_by()
222    }
223
224    fn order_by() -> Vec<ColumnOrder> {
225        vec![
226            ColumnOrder::new(0, OrderType::ascending()),
227            ColumnOrder::new(1, OrderType::ascending()),
228        ]
229    }
230
231    fn stream_key() -> StreamKey {
232        vec![0, 1]
233    }
234
235    fn create_source() -> Executor {
236        let mut chunks = create_stream_chunks();
237        MockSource::with_messages(vec![
238            Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
239            Message::Chunk(std::mem::take(&mut chunks[0])),
240            Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
241            Message::Chunk(std::mem::take(&mut chunks[1])),
242            Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
243            Message::Chunk(std::mem::take(&mut chunks[2])),
244        ])
245        .into_executor(create_schema(), stream_key())
246    }
247
248    #[tokio::test]
249    async fn test_append_only_top_n_executor_with_limit() {
250        let storage_key = storage_key();
251        let source = create_source();
252        let state_table = create_in_memory_state_table(
253            &[DataType::Int64, DataType::Int64],
254            &[OrderType::ascending(), OrderType::ascending()],
255            &stream_key(),
256        )
257        .await;
258
259        let schema = source.schema().clone();
260        let top_n = AppendOnlyTopNExecutor::<_, false>::new(
261            source,
262            ActorContext::for_test(0),
263            schema,
264            storage_key,
265            (0, 5),
266            order_by(),
267            state_table,
268        )
269        .unwrap();
270        let mut top_n = top_n.boxed().execute();
271
272        // consume the init epoch
273        top_n.expect_barrier().await;
274        assert_eq!(
275            top_n.expect_chunk().await.sort_rows(),
276            StreamChunk::from_pretty(
277                "  I I
278                +  1 0
279                +  2 1
280                +  3 2
281                +  9 4
282                +  8 5"
283            )
284            .sort_rows(),
285        );
286        // We added (1, 2, 3, 10, 9, 8).
287        // Now (1, 2, 3, 8, 9)
288        // Barrier
289        top_n.expect_barrier().await;
290        assert_eq!(
291            top_n.expect_chunk().await.sort_rows(),
292            StreamChunk::from_pretty(
293                " I I
294                - 9 4
295                - 8 5
296                + 3 7
297                + 1 8"
298            )
299            .sort_rows(),
300        );
301        // We added (7, 3, 1, 9).
302        // Now (1, 1, 2, 3, 3)
303        // Barrier
304        top_n.expect_barrier().await;
305        assert_eq!(
306            top_n.expect_chunk().await.sort_rows(),
307            StreamChunk::from_pretty(
308                " I  I
309                - 3  7
310                + 1 12
311                - 3  2
312                + 1 13"
313            )
314            .sort_rows(),
315        );
316        // We added (1, 1, 2, 3).
317        // Now (1, 1, 1, 1, 2)
318    }
319
320    #[tokio::test]
321    async fn test_append_only_top_n_executor_with_offset_and_limit() {
322        let source = create_source();
323        let state_table = create_in_memory_state_table(
324            &[DataType::Int64, DataType::Int64],
325            &[OrderType::ascending(), OrderType::ascending()],
326            &stream_key(),
327        )
328        .await;
329
330        let schema = source.schema().clone();
331        let top_n = AppendOnlyTopNExecutor::<_, false>::new(
332            source,
333            ActorContext::for_test(0),
334            schema,
335            storage_key(),
336            (3, 4),
337            order_by(),
338            state_table,
339        )
340        .unwrap();
341        let mut top_n = top_n.boxed().execute();
342
343        // consume the init epoch
344        top_n.expect_barrier().await;
345        assert_eq!(
346            top_n.expect_chunk().await.sort_rows(),
347            StreamChunk::from_pretty(
348                "  I I
349                + 10 3
350                +  9 4
351                +  8 5"
352            )
353            .sort_rows(),
354        );
355        // We added (1, 2, 3, 10, 9, 8).
356        // Now (1, 2, 3) -> (8, 9, 10)
357        // barrier
358        top_n.expect_barrier().await;
359        assert_eq!(
360            top_n.expect_chunk().await.sort_rows(),
361            StreamChunk::from_pretty(
362                "  I I
363                +  7 6
364                - 10 3
365                +  3 7
366                -  9 4
367                +  3 2"
368            )
369            .sort_rows(),
370        );
371        // We added (7, 3, 1, 9).
372        // Now (1, 1, 2) -> (3, 3, 7, 8)
373        // barrier
374        top_n.expect_barrier().await;
375        assert_eq!(
376            top_n.expect_chunk().await.sort_rows(),
377            StreamChunk::from_pretty(
378                " I  I
379                - 8  5
380                + 2  1
381                - 7  6
382                + 1 13
383                - 3  7
384                + 2 14"
385            )
386            .sort_rows(),
387        );
388        // We added (1, 1, 2, 3).
389        // Now (1, 1, 1) -> (1, 2, 2, 3)
390    }
391}