1use std::collections::BTreeMap;
16
17use either::Either;
18use futures::stream;
19use futures::stream::select_with_strategy;
20use itertools::Itertools;
21use risingwave_common::bitmap::BitmapBuilder;
22use risingwave_common::catalog::{ColumnDesc, Field};
23use risingwave_common::row::RowDeserializer;
24use risingwave_common::util::iter_util::ZipEqFast;
25use risingwave_common::util::sort_util::{OrderType, cmp_datum};
26use risingwave_connector::source::cdc::CdcScanOptions;
27use risingwave_connector::source::cdc::external::{
28 CdcOffset, ExternalCdcTableType, ExternalTableReaderImpl,
29};
30use risingwave_connector::source::{CdcTableSnapshotSplit, CdcTableSnapshotSplitRaw};
31use risingwave_pb::common::ThrottleType;
32use rw_futures_util::pausable;
33use thiserror_ext::AsReport;
34use tracing::Instrument;
35
36use crate::executor::backfill::cdc::cdc_backfill::{
37 build_reader_and_poll_upstream, get_cdc_json_parse_handling_from_properties, transform_upstream,
38};
39use crate::executor::backfill::cdc::state_v2::ParallelizedCdcBackfillState;
40use crate::executor::backfill::cdc::upstream_table::external::ExternalStorageTable;
41use crate::executor::backfill::cdc::upstream_table::snapshot::{
42 SplitSnapshotReadArgs, UpstreamTableRead, UpstreamTableReader,
43};
44use crate::executor::backfill::utils::{get_cdc_chunk_last_offset, mapping_chunk, mapping_message};
45use crate::executor::prelude::*;
46use crate::executor::source::get_infinite_backoff_strategy;
47use crate::task::cdc_progress::CdcProgressReporter;
48pub struct ParallelizedCdcBackfillExecutor<S: StateStore> {
49 actor_ctx: ActorContextRef,
50
51 external_table: ExternalStorageTable,
53
54 upstream: Executor,
56
57 output_indices: Vec<usize>,
59
60 output_columns: Vec<ColumnDesc>,
62
63 rate_limit_rps: Option<u32>,
65
66 options: CdcScanOptions,
67
68 state_table: StateTable<S>,
69
70 properties: BTreeMap<String, String>,
71
72 progress: Option<CdcProgressReporter>,
73}
74
75impl<S: StateStore> ParallelizedCdcBackfillExecutor<S> {
76 #[expect(clippy::too_many_arguments)]
77 pub fn new(
78 actor_ctx: ActorContextRef,
79 external_table: ExternalStorageTable,
80 upstream: Executor,
81 output_indices: Vec<usize>,
82 output_columns: Vec<ColumnDesc>,
83 _metrics: Arc<StreamingMetrics>,
84 state_table: StateTable<S>,
85 rate_limit_rps: Option<u32>,
86 options: CdcScanOptions,
87 properties: BTreeMap<String, String>,
88 progress: Option<CdcProgressReporter>,
89 ) -> Self {
90 Self {
91 actor_ctx,
92 external_table,
93 upstream,
94 output_indices,
95 output_columns,
96 rate_limit_rps,
97 options,
98 state_table,
99 properties,
100 progress,
101 }
102 }
103
104 #[try_stream(ok = Message, error = StreamExecutorError)]
105 async fn execute_inner(mut self) {
106 assert!(!self.options.disable_backfill);
107 let pk_indices = self.external_table.pk_indices().to_vec();
109 let table_id = self.external_table.table_id();
110 let upstream_table_name = self.external_table.qualified_table_name();
111 let schema_table_name = self.external_table.schema_table_name().clone();
112 let external_database_name = self.external_table.database_name().to_owned();
113 let additional_columns = self
114 .output_columns
115 .iter()
116 .filter(|col| col.additional_column.column_type.is_some())
117 .cloned()
118 .collect_vec();
119 assert!(
120 (self.options.backfill_split_pk_column_index as usize) < pk_indices.len(),
121 "split pk column index {} out of bound",
122 self.options.backfill_split_pk_column_index
123 );
124 let snapshot_split_column_index =
125 pk_indices[self.options.backfill_split_pk_column_index as usize];
126 let cdc_table_snapshot_split_column =
127 vec![self.external_table.schema().fields[snapshot_split_column_index].clone()];
128
129 let mut upstream = self.upstream.execute();
130 let first_barrier = expect_first_barrier(&mut upstream).await?;
132 let (timestamp_handling, timestamptz_handling, time_handling, bigint_unsigned_handling) =
138 get_cdc_json_parse_handling_from_properties(&self.properties);
139 let handle_toast_columns: bool =
141 self.external_table.table_type() == &ExternalCdcTableType::Postgres;
142 let mut upstream = transform_upstream(
143 upstream,
144 self.output_columns.clone(),
145 timestamp_handling,
146 timestamptz_handling,
147 time_handling,
148 bigint_unsigned_handling,
149 handle_toast_columns,
150 )
151 .boxed();
152 let mut next_reset_barrier = Some(first_barrier);
153 let mut is_reset = false;
154 let mut state_impl = ParallelizedCdcBackfillState::new(self.state_table);
155 let mut upstream_chunk_buffer: Vec<StreamChunk> = vec![];
157
158 'with_cdc_table_snapshot_splits: loop {
160 assert!(upstream_chunk_buffer.is_empty());
161 let reset_barrier = next_reset_barrier.take().unwrap();
162 let all_snapshot_splits = match reset_barrier.mutation.as_deref() {
163 Some(Mutation::Add(add)) => &add.actor_cdc_table_snapshot_splits.splits,
164
165 Some(Mutation::Update(update)) => &update.actor_cdc_table_snapshot_splits.splits,
166 _ => {
167 return Err(anyhow::anyhow!("ParallelizedCdcBackfillExecutor expects either Mutation::Add or Mutation::Update to initialize CDC table snapshot splits.").into());
168 }
169 };
170 let mut actor_snapshot_splits = vec![];
171 let mut generation = None;
172 if let Some((splits, snapshot_generation)) = all_snapshot_splits.get(&self.actor_ctx.id)
174 {
175 actor_snapshot_splits = splits
176 .iter()
177 .map(|s: &CdcTableSnapshotSplitRaw| {
178 let de = RowDeserializer::new(
179 cdc_table_snapshot_split_column
180 .iter()
181 .map(Field::data_type)
182 .collect_vec(),
183 );
184 let left_bound_inclusive =
185 de.deserialize(s.left_bound_inclusive.as_ref()).unwrap();
186 let right_bound_exclusive =
187 de.deserialize(s.right_bound_exclusive.as_ref()).unwrap();
188 CdcTableSnapshotSplit {
189 split_id: s.split_id,
190 left_bound_inclusive,
191 right_bound_exclusive,
192 }
193 })
194 .collect();
195 generation = Some(*snapshot_generation);
196 }
197 tracing::debug!(?actor_snapshot_splits, ?generation, "actor splits");
198 assert_consecutive_splits(&actor_snapshot_splits);
199
200 let mut is_snapshot_paused = reset_barrier.is_pause_on_startup();
201 let barrier_epoch = reset_barrier.epoch;
202 yield Message::Barrier(reset_barrier);
203 if !is_reset {
204 state_impl.init_epoch(barrier_epoch).await?;
205 is_reset = true;
206 tracing::info!(%table_id, "Initialize executor.");
207 } else {
208 tracing::info!(%table_id, "Reset executor.");
209 }
210
211 let mut current_actor_bounds = None;
212 let mut actor_cdc_offset_high: Option<CdcOffset> = None;
213 let mut actor_cdc_offset_low: Option<CdcOffset> = None;
214 let mut next_split_idx = actor_snapshot_splits.len();
216 for (idx, split) in actor_snapshot_splits.iter().enumerate() {
217 let state = state_impl.restore_state(split.split_id).await?;
218 if !state.is_finished {
219 next_split_idx = idx;
220 break;
221 }
222 extends_current_actor_bound(&mut current_actor_bounds, split);
223 if let Some(ref cdc_offset) = state.cdc_offset_low {
224 if let Some(ref cur) = actor_cdc_offset_low {
225 if *cur > *cdc_offset {
226 actor_cdc_offset_low = state.cdc_offset_low.clone();
227 }
228 } else {
229 actor_cdc_offset_low = state.cdc_offset_low.clone();
230 }
231 }
232 if let Some(ref cdc_offset) = state.cdc_offset_high {
233 if let Some(ref cur) = actor_cdc_offset_high {
234 if *cur < *cdc_offset {
235 actor_cdc_offset_high = state.cdc_offset_high.clone();
236 }
237 } else {
238 actor_cdc_offset_high = state.cdc_offset_high.clone();
239 }
240 }
241 }
242 for split in actor_snapshot_splits.iter().skip(next_split_idx) {
243 state_impl
245 .mutate_state(split.split_id, false, 0, None, None)
246 .await?;
247 }
248 let mut should_report_actor_backfill_progress = if next_split_idx > 0 {
249 Some((
250 actor_snapshot_splits[0].split_id,
251 actor_snapshot_splits[next_split_idx - 1].split_id,
252 ))
253 } else {
254 None
255 };
256
257 let mut table_reader: Option<ExternalTableReaderImpl> = None;
260 let external_table = self.external_table.clone();
261 let actor_id = self.actor_ctx.id;
262 let fragment_id = self.actor_ctx.fragment_id;
263 let mut future = Box::pin(async move {
264 let backoff = get_infinite_backoff_strategy();
265 tokio_retry::Retry::spawn(backoff, || async {
266 match external_table.create_table_reader().await {
267 Ok(reader) => Ok(reader),
268 Err(e) => {
269 tracing::warn!(error = %e.as_report(), actor_id = %actor_id, fragment_id = %fragment_id, "failed to create cdc table reader, retrying...");
270 Err(e)
271 }
272 }
273 })
274 .instrument(tracing::info_span!("create_cdc_table_reader_with_retry"))
275 .await
276 .expect("Retry create cdc table reader until success.")
277 });
278 loop {
279 if let Some(msg) =
280 build_reader_and_poll_upstream(&mut upstream, &mut table_reader, &mut future)
281 .await?
282 {
283 if let Some(msg) = mapping_message(msg, &self.output_indices) {
284 match msg {
285 Message::Barrier(barrier) => {
286 state_impl.commit_state(barrier.epoch).await?;
287 if is_reset_barrier(&barrier, self.actor_ctx.id) {
288 next_reset_barrier = Some(barrier);
289 continue 'with_cdc_table_snapshot_splits;
290 }
291 yield Message::Barrier(barrier);
292 }
293 Message::Chunk(chunk) => {
294 if chunk.cardinality() == 0 {
295 continue;
296 }
297 if let Some(filtered_chunk) = filter_stream_chunk(
298 chunk,
299 ¤t_actor_bounds,
300 snapshot_split_column_index,
301 ) && filtered_chunk.cardinality() > 0
302 {
303 yield Message::Chunk(filtered_chunk);
304 }
305 }
306 Message::Watermark(_) => {
307 }
309 }
310 }
311 } else {
312 assert!(table_reader.is_some(), "table reader must created");
313 tracing::info!(
314 %table_id,
315 upstream_table_name,
316 "table reader created successfully"
317 );
318 break;
319 }
320 }
321 let upstream_table_reader = UpstreamTableReader::new(
322 self.external_table.clone(),
323 table_reader.expect("table reader must created"),
324 );
325 let offset_parse_func = upstream_table_reader.reader.get_cdc_offset_parser();
327
328 for split in actor_snapshot_splits.iter().skip(next_split_idx) {
330 tracing::info!(
331 %table_id,
332 upstream_table_name,
333 ?split,
334 is_snapshot_paused,
335 "start cdc backfill split"
336 );
337 let finished_split_bounds = current_actor_bounds.clone();
338 let current_split_bounds = Some((
339 split.left_bound_inclusive.clone(),
340 split.right_bound_exclusive.clone(),
341 ));
342 extends_current_actor_bound(&mut current_actor_bounds, split);
343
344 let split_cdc_offset_low = {
345 static CDC_CONN_SEMAPHORE: tokio::sync::Semaphore =
347 tokio::sync::Semaphore::const_new(10);
348
349 let _permit = CDC_CONN_SEMAPHORE.acquire().await.unwrap();
350 upstream_table_reader.current_cdc_offset().await?
351 };
352 if let Some(ref cdc_offset) = split_cdc_offset_low {
353 if let Some(ref cur) = actor_cdc_offset_low {
354 if *cur > *cdc_offset {
355 actor_cdc_offset_low = split_cdc_offset_low.clone();
356 }
357 } else {
358 actor_cdc_offset_low = split_cdc_offset_low.clone();
359 }
360 }
361 let mut split_cdc_offset_high = None;
362
363 let left_upstream = upstream.by_ref().map(Either::Left);
364 let read_args = SplitSnapshotReadArgs::new(
365 split.left_bound_inclusive.clone(),
366 split.right_bound_exclusive.clone(),
367 cdc_table_snapshot_split_column.clone(),
368 self.rate_limit_rps,
369 additional_columns.clone(),
370 schema_table_name.clone(),
371 external_database_name.clone(),
372 );
373 let right_snapshot = pin!(
374 upstream_table_reader
375 .snapshot_read_table_split(read_args)
376 .map(Either::Right)
377 );
378 let (right_snapshot, snapshot_valve) = pausable(right_snapshot);
379 if is_snapshot_paused {
380 snapshot_valve.pause();
381 }
382 let mut backfill_stream =
383 select_with_strategy(left_upstream, right_snapshot, |_: &mut ()| {
384 stream::PollNext::Left
385 });
386 let mut row_count: u64 = 0;
387 #[for_await]
388 for either in &mut backfill_stream {
389 match either {
390 Either::Left(msg) => {
392 match msg? {
393 Message::Barrier(barrier) => {
394 state_impl.commit_state(barrier.epoch).await?;
395 if let Some(mutation) = barrier.mutation.as_deref() {
396 use crate::executor::Mutation;
397 match mutation {
398 Mutation::Pause => {
399 is_snapshot_paused = true;
400 snapshot_valve.pause();
401 }
402 Mutation::Resume => {
403 is_snapshot_paused = false;
404 snapshot_valve.resume();
405 }
406 Mutation::Throttle(some) => {
407 if let Some(entry) =
411 some.get(&self.actor_ctx.fragment_id)
412 && entry.throttle_type()
413 == ThrottleType::Backfill
414 && entry.rate_limit != self.rate_limit_rps
415 {
416 self.rate_limit_rps = entry.rate_limit;
418 }
419 }
420 mutation if mutation.is_stop(self.actor_ctx.id) => {
421 tracing::info!(
422 %table_id,
423 upstream_table_name,
424 "CdcBackfill has been dropped due to config change"
425 );
426 for chunk in upstream_chunk_buffer.drain(..) {
427 yield Message::Chunk(chunk);
428 }
429 yield Message::Barrier(barrier);
430 let () = futures::future::pending().await;
431 unreachable!();
432 }
433 _ => (),
434 }
435 }
436 if is_reset_barrier(&barrier, self.actor_ctx.id) {
437 next_reset_barrier = Some(barrier);
438 for chunk in upstream_chunk_buffer.drain(..) {
439 yield Message::Chunk(chunk);
440 }
441 continue 'with_cdc_table_snapshot_splits;
442 }
443 if let Some(split_range) =
444 should_report_actor_backfill_progress.take()
445 && let Some(ref progress) = self.progress
446 {
447 progress.update(
448 self.actor_ctx.fragment_id,
449 self.actor_ctx.id,
450 barrier.epoch,
451 generation.expect("should have set generation when having progress to report"),
452 split_range,
453 );
454 }
455 yield Message::Barrier(barrier);
457 }
458 Message::Chunk(chunk) => {
459 if chunk.cardinality() == 0 {
461 continue;
462 }
463
464 let chunk = mapping_chunk(chunk, &self.output_indices);
477 let (finished_chunk, current_chunk) =
478 split_finished_and_current_chunk(
479 chunk,
480 &finished_split_bounds,
481 ¤t_split_bounds,
482 snapshot_split_column_index,
483 );
484 if let Some(finished_chunk) = finished_chunk
485 && finished_chunk.cardinality() > 0
486 {
487 yield Message::Chunk(finished_chunk);
488 }
489 if let Some(filtered_chunk) = current_chunk
490 && filtered_chunk.cardinality() > 0
491 {
492 upstream_chunk_buffer.push(filtered_chunk);
494 }
495 }
496 Message::Watermark(_) => {
497 }
499 }
500 }
501 Either::Right(msg) => {
503 match msg? {
504 None => {
505 tracing::info!(
506 %table_id,
507 split_id = split.split_id,
508 "snapshot read stream ends"
509 );
510 for chunk in upstream_chunk_buffer.drain(..) {
511 yield Message::Chunk(chunk);
512 }
513
514 split_cdc_offset_high = {
515 static CDC_CONN_SEMAPHORE: tokio::sync::Semaphore =
517 tokio::sync::Semaphore::const_new(10);
518
519 let _permit = CDC_CONN_SEMAPHORE.acquire().await.unwrap();
520 upstream_table_reader.current_cdc_offset().await?
521 };
522 if let Some(ref cdc_offset) = split_cdc_offset_high {
523 if let Some(ref cur) = actor_cdc_offset_high {
524 if *cur < *cdc_offset {
525 actor_cdc_offset_high =
526 split_cdc_offset_high.clone();
527 }
528 } else {
529 actor_cdc_offset_high = split_cdc_offset_high.clone();
530 }
531 }
532 break;
534 }
535 Some(chunk) => {
536 let chunk_cardinality = chunk.cardinality() as u64;
537 row_count = row_count.saturating_add(chunk_cardinality);
538 yield Message::Chunk(mapping_chunk(
539 chunk,
540 &self.output_indices,
541 ));
542 }
543 }
544 }
545 }
546 }
547 state_impl
549 .mutate_state(
550 split.split_id,
551 true,
552 row_count,
553 split_cdc_offset_low,
554 split_cdc_offset_high,
555 )
556 .await?;
557 if let Some((_, right_split)) = &mut should_report_actor_backfill_progress {
558 assert!(
559 *right_split < split.split_id,
560 "{} {}",
561 *right_split,
562 split.split_id
563 );
564 *right_split = split.split_id;
565 } else {
566 should_report_actor_backfill_progress = Some((split.split_id, split.split_id));
567 }
568 }
569
570 upstream_table_reader.disconnect().await?;
571 tracing::info!(
572 %table_id,
573 upstream_table_name,
574 "CdcBackfill has already finished and will forward messages directly to the downstream"
575 );
576
577 let mut should_report_actor_backfill_done = false;
578 #[for_await]
582 for msg in &mut upstream {
583 let msg = msg?;
584 match msg {
585 Message::Barrier(barrier) => {
586 state_impl.commit_state(barrier.epoch).await?;
587 if is_reset_barrier(&barrier, self.actor_ctx.id) {
588 next_reset_barrier = Some(barrier);
589 continue 'with_cdc_table_snapshot_splits;
590 }
591 if let Some(split_range) = should_report_actor_backfill_progress.take()
592 && let Some(ref progress) = self.progress
593 {
594 progress.update(
595 self.actor_ctx.fragment_id,
596 self.actor_ctx.id,
597 barrier.epoch,
598 generation.expect(
599 "should have set generation when having progress to report",
600 ),
601 split_range,
602 );
603 }
604 if should_report_actor_backfill_done {
605 should_report_actor_backfill_done = false;
606 assert!(!actor_snapshot_splits.is_empty());
607 if let Some(ref progress) = self.progress {
608 progress.finish(
609 self.actor_ctx.fragment_id,
610 self.actor_ctx.id,
611 barrier.epoch,
612 generation.expect(
613 "should have set generation when having progress to report",
614 ),
615 (
616 actor_snapshot_splits[0].split_id,
617 actor_snapshot_splits[actor_snapshot_splits.len() - 1]
618 .split_id,
619 ),
620 );
621 }
622 }
623 yield Message::Barrier(barrier);
624 }
625 Message::Chunk(chunk) => {
626 if actor_snapshot_splits.is_empty() {
627 continue;
628 }
629 if chunk.cardinality() == 0 {
630 continue;
631 }
632
633 let chunk_cdc_offset =
634 get_cdc_chunk_last_offset(&offset_parse_func, &chunk)?;
635 if let Some(high) = actor_cdc_offset_high.as_ref() {
646 if state_impl.is_legacy_state() {
647 actor_cdc_offset_high = None;
649 should_report_actor_backfill_done = true;
650 } else if let Some(ref chunk_offset) = chunk_cdc_offset
651 && *chunk_offset >= *high
652 {
653 actor_cdc_offset_high = None;
655 should_report_actor_backfill_done = true;
656 }
657 }
658 let chunk = mapping_chunk(chunk, &self.output_indices);
659 if let Some(filtered_chunk) = filter_stream_chunk(
660 chunk,
661 ¤t_actor_bounds,
662 snapshot_split_column_index,
663 ) && filtered_chunk.cardinality() > 0
664 {
665 yield Message::Chunk(filtered_chunk);
666 }
667 }
668 msg @ Message::Watermark(_) => {
669 if let Some(msg) = mapping_message(msg, &self.output_indices) {
670 yield msg;
671 }
672 }
673 }
674 }
675 }
676 }
677}
678
679fn split_finished_and_current_chunk(
680 chunk: StreamChunk,
681 finished_split_bounds: &Option<(OwnedRow, OwnedRow)>,
682 current_split_bounds: &Option<(OwnedRow, OwnedRow)>,
683 snapshot_split_column_index: usize,
684) -> (Option<StreamChunk>, Option<StreamChunk>) {
685 let finished_chunk = filter_stream_chunk(
686 chunk.clone(),
687 finished_split_bounds,
688 snapshot_split_column_index,
689 )
690 .map(StreamChunk::compact_vis);
691 let current_chunk =
692 filter_stream_chunk(chunk, current_split_bounds, snapshot_split_column_index)
693 .map(StreamChunk::compact_vis);
694 (finished_chunk, current_chunk)
695}
696
697fn filter_stream_chunk(
698 chunk: StreamChunk,
699 bound: &Option<(OwnedRow, OwnedRow)>,
700 snapshot_split_column_index: usize,
701) -> Option<StreamChunk> {
702 let Some((left, right)) = bound else {
703 return None;
704 };
705 assert_eq!(left.len(), 1, "multiple split columns is not supported yet");
706 assert_eq!(
707 right.len(),
708 1,
709 "multiple split columns is not supported yet"
710 );
711 let left_split_key = left.datum_at(0);
712 let right_split_key = right.datum_at(0);
713 let is_leftmost_bound = is_leftmost_bound(left);
714 let is_rightmost_bound = is_rightmost_bound(right);
715 if is_leftmost_bound && is_rightmost_bound {
716 return Some(chunk);
717 }
718 let mut new_bitmap = BitmapBuilder::with_capacity(chunk.capacity());
719 let (ops, columns, visibility) = chunk.into_inner();
720 for (row_split_key, v) in columns[snapshot_split_column_index]
721 .iter()
722 .zip_eq_fast(visibility.iter())
723 {
724 if !v {
725 new_bitmap.append(false);
726 continue;
727 }
728 let mut is_in_range = true;
729 if !is_leftmost_bound {
730 is_in_range = cmp_datum(
731 row_split_key,
732 left_split_key,
733 OrderType::ascending_nulls_first(),
734 )
735 .is_ge();
736 }
737 if is_in_range && !is_rightmost_bound {
738 is_in_range = cmp_datum(
739 row_split_key,
740 right_split_key,
741 OrderType::ascending_nulls_first(),
742 )
743 .is_lt();
744 }
745 if !is_in_range {
746 tracing::trace!(?row_split_key, ?left_split_key, ?right_split_key, snapshot_split_column_index, data_type = ?columns[snapshot_split_column_index].data_type(), "filter out row")
747 }
748 new_bitmap.append(is_in_range);
749 }
750 Some(StreamChunk::with_visibility(
751 ops,
752 columns,
753 new_bitmap.finish(),
754 ))
755}
756
757fn is_leftmost_bound(row: &OwnedRow) -> bool {
758 row.iter().all(|d| d.is_none())
759}
760
761fn is_rightmost_bound(row: &OwnedRow) -> bool {
762 row.iter().all(|d| d.is_none())
763}
764
765impl<S: StateStore> Execute for ParallelizedCdcBackfillExecutor<S> {
766 fn execute(self: Box<Self>) -> BoxedMessageStream {
767 self.execute_inner().boxed()
768 }
769}
770
771fn extends_current_actor_bound(
772 current: &mut Option<(OwnedRow, OwnedRow)>,
773 split: &CdcTableSnapshotSplit,
774) {
775 if current.is_none() {
776 *current = Some((
777 split.left_bound_inclusive.clone(),
778 split.right_bound_exclusive.clone(),
779 ));
780 } else {
781 current.as_mut().unwrap().1 = split.right_bound_exclusive.clone();
782 }
783}
784
785fn is_reset_barrier(barrier: &Barrier, actor_id: ActorId) -> bool {
786 match barrier.mutation.as_deref() {
787 Some(Mutation::Update(update)) => update
788 .actor_cdc_table_snapshot_splits
789 .splits
790 .contains_key(&actor_id),
791 _ => false,
792 }
793}
794
795fn assert_consecutive_splits(actor_snapshot_splits: &[CdcTableSnapshotSplit]) {
796 for i in 1..actor_snapshot_splits.len() {
797 assert_eq!(
798 actor_snapshot_splits[i].split_id,
799 actor_snapshot_splits[i - 1].split_id + 1,
800 "{:?}",
801 actor_snapshot_splits
802 );
803 assert!(
804 cmp_datum(
805 actor_snapshot_splits[i - 1]
806 .right_bound_exclusive
807 .datum_at(0),
808 actor_snapshot_splits[i].right_bound_exclusive.datum_at(0),
809 OrderType::ascending_nulls_last(),
810 )
811 .is_lt()
812 );
813 }
814}
815
816#[cfg(test)]
817mod tests {
818 use risingwave_common::array::StreamChunk;
819 use risingwave_common::row::OwnedRow;
820 use risingwave_common::types::ScalarImpl;
821
822 use crate::executor::backfill::cdc::cdc_backill_v2::{
823 filter_stream_chunk, split_finished_and_current_chunk,
824 };
825
826 #[test]
827 fn test_filter_stream_chunk() {
828 use risingwave_common::array::StreamChunkTestExt;
829 let chunk = StreamChunk::from_pretty(
830 " I I
831 + 1 6
832 - 2 .
833 U- 3 7
834 U+ 4 .",
835 );
836 let bound = None;
837 let c = filter_stream_chunk(chunk.clone(), &bound, 0);
838 assert!(c.is_none());
839
840 let bound = Some((OwnedRow::new(vec![None]), OwnedRow::new(vec![None])));
841 let c = filter_stream_chunk(chunk.clone(), &bound, 0);
842 assert_eq!(c.unwrap().compact_vis(), chunk);
843
844 let bound = Some((
845 OwnedRow::new(vec![None]),
846 OwnedRow::new(vec![Some(ScalarImpl::Int64(3))]),
847 ));
848 let c = filter_stream_chunk(chunk.clone(), &bound, 0);
849 assert_eq!(
850 c.unwrap().compact_vis(),
851 StreamChunk::from_pretty(
852 " I I
853 + 1 6
854 - 2 .",
855 )
856 );
857
858 let bound = Some((
859 OwnedRow::new(vec![Some(ScalarImpl::Int64(3))]),
860 OwnedRow::new(vec![None]),
861 ));
862 let c = filter_stream_chunk(chunk.clone(), &bound, 0);
863 assert_eq!(
864 c.unwrap().compact_vis(),
865 StreamChunk::from_pretty(
866 " I I
867 U- 3 7
868 U+ 4 .",
869 )
870 );
871
872 let bound = Some((
873 OwnedRow::new(vec![Some(ScalarImpl::Int64(2))]),
874 OwnedRow::new(vec![Some(ScalarImpl::Int64(4))]),
875 ));
876 let c = filter_stream_chunk(chunk.clone(), &bound, 0);
877 assert_eq!(
878 c.unwrap().compact_vis(),
879 StreamChunk::from_pretty(
880 " I I
881 - 2 .
882 U- 3 7",
883 )
884 );
885
886 let bound = None;
888 let c = filter_stream_chunk(chunk.clone(), &bound, 1);
889 assert!(c.is_none());
890
891 let bound = Some((OwnedRow::new(vec![None]), OwnedRow::new(vec![None])));
892 let c = filter_stream_chunk(chunk.clone(), &bound, 1);
893 assert_eq!(c.unwrap().compact_vis(), chunk);
894
895 let bound = Some((
896 OwnedRow::new(vec![None]),
897 OwnedRow::new(vec![Some(ScalarImpl::Int64(7))]),
898 ));
899 let c = filter_stream_chunk(chunk.clone(), &bound, 1);
900 assert_eq!(
901 c.unwrap().compact_vis(),
902 StreamChunk::from_pretty(
903 " I I
904 + 1 6
905 - 2 .
906 U+ 4 .",
907 )
908 );
909
910 let bound = Some((
911 OwnedRow::new(vec![Some(ScalarImpl::Int64(7))]),
912 OwnedRow::new(vec![None]),
913 ));
914 let c = filter_stream_chunk(chunk, &bound, 1);
915 assert_eq!(
916 c.unwrap().compact_vis(),
917 StreamChunk::from_pretty(
918 " I I
919 U- 3 7",
920 )
921 );
922 }
923
924 #[test]
925 fn test_split_finished_and_current_chunk() {
926 use risingwave_common::array::StreamChunkTestExt;
927
928 let chunk = StreamChunk::from_pretty(
929 " I I
930 + 1 11
931 + 6 10
932 + 199 40",
933 );
934 let finished_split_bounds = Some((
935 OwnedRow::new(vec![Some(ScalarImpl::Int64(1))]),
936 OwnedRow::new(vec![Some(ScalarImpl::Int64(6))]),
937 ));
938 let current_split_bounds = Some((
939 OwnedRow::new(vec![Some(ScalarImpl::Int64(6))]),
940 OwnedRow::new(vec![Some(ScalarImpl::Int64(100))]),
941 ));
942
943 let (finished_chunk, current_chunk) = split_finished_and_current_chunk(
944 chunk,
945 &finished_split_bounds,
946 ¤t_split_bounds,
947 0,
948 );
949
950 assert_eq!(
951 finished_chunk.unwrap(),
952 StreamChunk::from_pretty(
953 " I I
954 + 1 11",
955 )
956 );
957 assert_eq!(
958 current_chunk.unwrap(),
959 StreamChunk::from_pretty(
960 " I I
961 + 6 10",
962 )
963 );
964 }
965}