risingwave_stream/executor/
changelog.rs1use 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}