Skip to main content

risingwave_stream/executor/
changelog.rs

1// Copyright 2024 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 core::fmt::Formatter;
16use std::fmt::Debug;
17use std::sync::Arc;
18
19use futures::prelude::stream::StreamExt;
20use futures_async_stream::try_stream;
21use itertools::Itertools;
22use risingwave_common::array::stream_chunk_builder::StreamChunkBuilder;
23use risingwave_common::array::{ArrayImpl, I16Array, Op, SerialArray, StreamChunk};
24use risingwave_common::bitmap::Bitmap;
25use risingwave_common::hash::VirtualNode;
26use risingwave_common::row::{OwnedRow, RowExt};
27use risingwave_common::types::{DataType, ScalarImpl, Serial};
28use risingwave_common::util::row_id::ChangelogRowIdGenerator;
29
30use super::{ActorContextRef, BoxedMessageStream, Execute, Executor, Message, StreamExecutorError};
31use crate::common::change_buffer::InconsistencyBehavior;
32use crate::common::change_buffer::output_kind::RETRACT;
33use crate::common::compact_chunk::StreamChunkCompactor;
34
35pub struct ChangeLogExecutor {
36    ctx: ActorContextRef,
37    input: Executor,
38    need_op: bool,
39    all_vnode_count: usize,
40    mode: ChangeLogMode,
41    distribution_keys: Vec<usize>,
42    stream_keys: Vec<usize>,
43    changelog_row_id_generator: ChangelogRowIdGenerator,
44}
45
46impl Execute for ChangeLogExecutor {
47    fn execute(self: Box<Self>) -> BoxedMessageStream {
48        if matches!(self.mode, ChangeLogMode::Keyed) {
49            self.execute_keyed().boxed()
50        } else {
51            self.execute_normal().boxed()
52        }
53    }
54}
55
56impl ChangeLogExecutor {
57    #[allow(clippy::too_many_arguments)]
58    pub fn new(
59        ctx: ActorContextRef,
60        input: Executor,
61        need_op: bool,
62        all_vnode_count: usize,
63        vnodes: Bitmap,
64        mode: ChangeLogMode,
65        distribution_keys: Vec<usize>,
66        stream_keys: Vec<usize>,
67    ) -> Self {
68        let changelog_row_id_generator = ChangelogRowIdGenerator::new(vnodes, all_vnode_count);
69        Self {
70            ctx,
71            input,
72            need_op,
73            all_vnode_count,
74            mode,
75            distribution_keys,
76            stream_keys,
77            changelog_row_id_generator,
78        }
79    }
80
81    #[try_stream(ok = Message, error = StreamExecutorError)]
82    async fn execute_normal(mut self) {
83        let input = self.input.execute();
84        #[for_await]
85        for msg in input {
86            let msg = msg?;
87            match msg {
88                Message::Chunk(chunk) => {
89                    let data_chunk = chunk.data_chunk();
90                    let vnodes = VirtualNode::compute_chunk(
91                        data_chunk,
92                        &self.distribution_keys,
93                        self.all_vnode_count,
94                    );
95
96                    let (ops, mut columns, bitmap) = chunk.into_inner();
97
98                    if self.need_op {
99                        let ops = ops.iter().map(|op| Some(op.to_i16())).collect_vec();
100                        let ops_array = Arc::new(ArrayImpl::Int16(I16Array::from_iter(ops)));
101
102                        columns.push(ops_array);
103                    }
104
105                    let changelog_row_ids = vnodes
106                        .iter()
107                        .map(|vnode| self.changelog_row_id_generator.next(vnode));
108                    let changelog_row_id_array = Arc::new(ArrayImpl::Serial(
109                        SerialArray::from_iter(changelog_row_ids.map(Serial::from)),
110                    ));
111
112                    columns.push(changelog_row_id_array);
113
114                    let new_ops = vec![Op::Insert; ops.len()];
115                    let new_chunk = StreamChunk::with_visibility(new_ops, columns, bitmap);
116
117                    yield Message::Chunk(new_chunk);
118                }
119                Message::Watermark(_w) => {}
120                Message::Barrier(barrier) => {
121                    if let Some(vnodes) = barrier.as_update_vnode_bitmap(self.ctx.id) {
122                        self.changelog_row_id_generator = ChangelogRowIdGenerator::new(
123                            vnodes.as_ref().clone(),
124                            self.all_vnode_count,
125                        );
126                    }
127                    yield Message::Barrier(barrier);
128                }
129            }
130        }
131    }
132
133    #[try_stream(ok = Message, error = StreamExecutorError)]
134    async fn execute_keyed(mut self) {
135        let compaction_keys = self
136            .stream_keys
137            .iter()
138            .chain(&self.distribution_keys)
139            .copied()
140            .unique()
141            .collect_vec();
142        let mut builder = StreamChunkBuilder::new(
143            self.ctx.config.developer.chunk_size,
144            self.output_data_types(),
145        );
146        let mut chunk_buffer: Vec<StreamChunk> = vec![];
147        let input = self.input.execute();
148
149        #[for_await]
150        for msg in input {
151            match msg? {
152                Message::Chunk(chunk) => chunk_buffer.push(chunk),
153                Message::Watermark(_w) => {}
154                Message::Barrier(barrier) => {
155                    let chunks = StreamChunkCompactor::new(
156                        compaction_keys.clone(),
157                        std::mem::take(&mut chunk_buffer),
158                    )
159                    .into_compacted_chunks_inline::<RETRACT>(InconsistencyBehavior::Warn);
160
161                    let mut deletes = Vec::with_capacity(chunks.len());
162                    let mut inserts = Vec::with_capacity(chunks.len());
163
164                    for chunk in chunks {
165                        let vnodes: Arc<[VirtualNode]> = VirtualNode::compute_chunk(
166                            chunk.data_chunk(),
167                            &self.distribution_keys,
168                            self.all_vnode_count,
169                        )
170                        .into();
171
172                        let delete_view = chunk.clone().retain_ops(&[Op::Delete, Op::UpdateDelete]);
173                        if delete_view.has_visible_rows() {
174                            deletes.push((delete_view, vnodes.clone()));
175                        }
176
177                        let insert_view = chunk.retain_ops(&[Op::Insert, Op::UpdateInsert]);
178                        if insert_view.has_visible_rows() {
179                            inserts.push((insert_view, vnodes));
180                        }
181                    }
182
183                    for (chunk, vnodes) in deletes.into_iter().chain(inserts) {
184                        for (event, vnode) in chunk.rows_with_holes().zip_eq(vnodes.iter()) {
185                            let Some((op, row)) = event else { continue };
186
187                            let id = Serial::from(self.changelog_row_id_generator.next(vnode));
188
189                            let mut suffix = Vec::with_capacity(2);
190
191                            if self.need_op {
192                                suffix.push(Some(ScalarImpl::Int16(op.to_i16())));
193                            }
194
195                            suffix.push(Some(ScalarImpl::Serial(id)));
196
197                            if let Some(chunk) =
198                                builder.append_row(Op::Insert, row.chain(OwnedRow::new(suffix)))
199                            {
200                                yield Message::Chunk(chunk);
201                            }
202                        }
203                    }
204
205                    if let Some(chunk) = builder.take() {
206                        yield Message::Chunk(chunk);
207                    }
208
209                    if let Some(vnodes) = barrier.as_update_vnode_bitmap(self.ctx.id) {
210                        self.changelog_row_id_generator = ChangelogRowIdGenerator::new(
211                            vnodes.as_ref().clone(),
212                            self.all_vnode_count,
213                        );
214                    }
215
216                    yield Message::Barrier(barrier);
217                }
218            }
219        }
220    }
221
222    fn output_data_types(&self) -> Vec<DataType> {
223        let mut data_types = self.input.schema().data_types();
224
225        if self.need_op {
226            data_types.push(DataType::Int16);
227        }
228
229        data_types.push(DataType::Serial);
230        data_types
231    }
232}
233
234#[derive(Debug)]
235pub enum ChangeLogMode {
236    Normal,
237    Keyed,
238}
239
240impl Debug for ChangeLogExecutor {
241    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
242        f.debug_struct("ChangeLogExecutor").finish()
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use itertools::Itertools;
249    use risingwave_common::catalog::{Field, Schema};
250    use risingwave_common::row::{Row, RowExt};
251    use risingwave_common::test_prelude::StreamChunkTestExt;
252    use risingwave_common::types::{DataType, ScalarRefImpl};
253    use risingwave_common::util::epoch::test_epoch;
254
255    use super::*;
256    use crate::executor::test_utils::MockSource;
257    use crate::executor::{ActorContext, Barrier};
258
259    #[tokio::test]
260    async fn test_keyed_changelog_reorders_replacement() {
261        let source = MockSource::with_messages(vec![
262            Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
263            Message::Chunk(StreamChunk::from_pretty("I T B\n+ 42 Beginner false")),
264            Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
265            Message::Chunk(StreamChunk::from_pretty("I T B\n+ 42 Advanced true")),
266            Message::Chunk(StreamChunk::from_pretty("I T B\n- 42 Beginner false")),
267            Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
268        ])
269        .stop_on_finish(false)
270        .into_executor(
271            Schema::new(vec![
272                Field::unnamed(DataType::Int64),
273                Field::unnamed(DataType::Varchar),
274                Field::unnamed(DataType::Boolean),
275            ]),
276            vec![0, 1, 2],
277        );
278
279        let mut output = ChangeLogExecutor::new(
280            ActorContext::for_test(1),
281            source,
282            true,
283            VirtualNode::COUNT_FOR_TEST,
284            Bitmap::ones(VirtualNode::COUNT_FOR_TEST),
285            ChangeLogMode::Keyed,
286            vec![0],
287            vec![0, 1, 2],
288        )
289        .boxed()
290        .execute();
291
292        let mut events = Vec::new();
293
294        while let Some(message) = output.next().await {
295            match message.unwrap() {
296                Message::Chunk(chunk) => {
297                    for (op, row) in chunk.rows() {
298                        assert_eq!(op, Op::Insert, "changelog output must be append-only");
299
300                        let Some(ScalarRefImpl::Serial(id)) = row.datum_at(4) else {
301                            panic!("expected a non-null changelog event ID");
302                        };
303
304                        let Some(ScalarRefImpl::Int16(change_op)) = row.datum_at(3) else {
305                            panic!("expected a non-null changelog operation");
306                        };
307
308                        let row = row.project(&[0, 1, 2]).into_owned_row();
309
310                        events.push((row, change_op, id));
311                    }
312                }
313                Message::Barrier(_) => {}
314                Message::Watermark(_) => panic!("unexpected watermark"),
315            }
316        }
317
318        let expected = StreamChunk::from_pretty(
319            "I T B\n+ 42 Beginner false\n- 42 Beginner false\n+ 42 Advanced true",
320        );
321        let ids = expected
322            .rows()
323            .map(|(op, row)| {
324                let row = row.to_owned_row();
325
326                events
327                    .iter()
328                    .find(|(event, change_op, _)| *event == row && *change_op == op.to_i16())
329                    .unwrap_or_else(|| panic!("missing changelog event: {row:?}"))
330                    .2
331            })
332            .collect_vec();
333
334        let [old_insert_id, old_delete_id, new_insert_id] = ids[..] else {
335            panic!("expected exactly three changelog events");
336        };
337
338        assert!(
339            old_insert_id < old_delete_id,
340            "original insert must precede deletion"
341        );
342
343        assert!(
344            old_delete_id < new_insert_id,
345            "old deletion must precede replacement insert"
346        );
347    }
348
349    #[tokio::test]
350    async fn test_keyed_changelog_does_not_pair_different_keys() {
351        let source = MockSource::with_messages(vec![
352            Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
353            Message::Chunk(StreamChunk::from_pretty("I T I\n+ 1 Alice 7")),
354            Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
355            Message::Chunk(StreamChunk::from_pretty("I T I\n- 1 Alice 7\n+ 2 Alice 7")),
356            Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
357        ])
358        .stop_on_finish(false)
359        .into_executor(
360            Schema::new(vec![
361                Field::unnamed(DataType::Int64),
362                Field::unnamed(DataType::Varchar),
363                Field::unnamed(DataType::Int64),
364            ]),
365            vec![2],
366        );
367
368        let mut output = ChangeLogExecutor::new(
369            ActorContext::for_test(1),
370            source,
371            true,
372            VirtualNode::COUNT_FOR_TEST,
373            Bitmap::ones(VirtualNode::COUNT_FOR_TEST),
374            ChangeLogMode::Keyed,
375            vec![0],
376            vec![2],
377        )
378        .boxed()
379        .execute();
380
381        let mut events = Vec::new();
382        while let Some(message) = output.next().await {
383            match message.unwrap() {
384                Message::Chunk(chunk) => {
385                    for (op, row) in chunk.rows() {
386                        assert_eq!(op, Op::Insert, "changelog output must be append-only");
387                        let Some(ScalarRefImpl::Int64(key)) = row.datum_at(0) else {
388                            panic!("expected a non-null changelog key");
389                        };
390                        let Some(ScalarRefImpl::Int16(change_op)) = row.datum_at(3) else {
391                            panic!("expected a non-null changelog operation");
392                        };
393                        events.push((key, change_op));
394                    }
395                }
396                Message::Barrier(_) => {}
397                Message::Watermark(_) => panic!("unexpected watermark"),
398            }
399        }
400
401        assert_eq!(
402            events,
403            vec![
404                (1, Op::Insert.to_i16()),
405                (1, Op::Delete.to_i16()),
406                (2, Op::Insert.to_i16())
407            ]
408        );
409    }
410}