1use std::collections::{BTreeMap, HashMap};
16use std::ops::Bound;
17use std::sync::Arc;
18
19use futures::future::{Either as FutureEither, pending, select};
20use futures::{StreamExt, TryStreamExt, pin_mut};
21use futures_async_stream::try_stream;
22use itertools::Itertools;
23use risingwave_common::array::stream_record::Record;
24use risingwave_common::array::{
25 ArrayImpl, DataChunk, I16Array, I64Array, Op, StreamChunk, StreamChunkBuilder,
26};
27use risingwave_common::catalog::Schema;
28use risingwave_common::hash::{VirtualNode, VnodeBitmapExt};
29use risingwave_common::row::{OwnedRow, Row, RowExt};
30use risingwave_common::types::{DataType, Datum, ScalarImpl, ToOwnedDatum};
31use risingwave_common::util::chunk_coalesce::DataChunkBuilder;
32use risingwave_common::util::sort_util::cmp_datum_iter;
33use risingwave_common_rate_limit::{MonitoredRateLimiter, RateLimit, RateLimiter};
34use risingwave_pb::common::ThrottleType;
35use risingwave_storage::StateStore;
36use risingwave_storage::store::PrefetchOptions;
37
38use crate::common::table::state_table::{FlushedStateTableReader, StateTable};
39use crate::consistency::consistency_panic;
40use crate::executor::backfill::utils::create_builder;
41use crate::executor::prelude::*;
42use crate::task::{CreateMviewProgressReporter, FragmentId};
43
44type Builders = HashMap<VirtualNode, DataChunkBuilder>;
45
46#[derive(Debug, Clone, Copy)]
48pub struct SortBufferSettings {
49 pub enabled: bool,
52 pub activate_threshold: u64,
54}
55
56fn op_to_code(op: Op) -> i16 {
60 op.normalize_update().to_i16()
61}
62
63fn apply_change_to_slot(slot: &mut Option<Record<OwnedRow>>, is_insert: bool, row: OwnedRow) {
66 let prev = slot.take();
67 *slot = if is_insert {
68 match prev {
69 None => Some(Record::Insert { new_row: row }),
70 Some(Record::Delete { old_row }) => Some(Record::Update {
71 old_row,
72 new_row: row,
73 }),
74 Some(Record::Insert { .. }) => {
75 consistency_panic!("locality sort buffer: double-inserting the same key");
76 Some(Record::Insert { new_row: row })
77 }
78 Some(Record::Update { old_row, .. }) => {
79 consistency_panic!("locality sort buffer: double-inserting the same key");
80 Some(Record::Update {
81 old_row,
82 new_row: row,
83 })
84 }
85 }
86 } else {
87 match prev {
88 None => Some(Record::Delete { old_row: row }),
89 Some(Record::Insert { .. }) => None,
90 Some(Record::Update { old_row, .. }) => Some(Record::Delete { old_row }),
91 Some(Record::Delete { .. }) => {
92 consistency_panic!("locality sort buffer: double-deleting the same key");
93 Some(Record::Delete { old_row: row })
94 }
95 }
96 };
97}
98
99fn take_compacted_record(slot: &mut Option<Record<OwnedRow>>) -> Option<Record<OwnedRow>> {
101 match slot.take() {
102 Some(Record::Update { old_row, new_row }) if old_row == new_row => None,
103 record => record,
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
109enum LocalityBackfillProgress {
110 NotStarted,
112 InProgress {
114 current_pos: OwnedRow,
116 processed_rows: u64,
118 },
119 Completed {
121 final_pos: OwnedRow,
123 total_rows: u64,
125 },
126}
127
128#[derive(Clone, Debug)]
130struct LocalityBackfillState {
131 per_vnode: HashMap<VirtualNode, LocalityBackfillProgress>,
133 total_snapshot_rows: u64,
135}
136
137impl LocalityBackfillState {
138 fn new(vnodes: impl Iterator<Item = VirtualNode>) -> Self {
139 let per_vnode = vnodes
140 .map(|vnode| (vnode, LocalityBackfillProgress::NotStarted))
141 .collect();
142 Self {
143 per_vnode,
144 total_snapshot_rows: 0,
145 }
146 }
147
148 fn is_completed(&self) -> bool {
149 self.per_vnode
150 .values()
151 .all(|progress| matches!(progress, LocalityBackfillProgress::Completed { .. }))
152 }
153
154 fn vnodes(&self) -> impl Iterator<Item = (VirtualNode, &LocalityBackfillProgress)> {
155 self.per_vnode
156 .iter()
157 .map(|(&vnode, progress)| (vnode, progress))
158 }
159
160 fn has_progress(&self) -> bool {
162 self.per_vnode
163 .values()
164 .any(|progress| !matches!(progress, LocalityBackfillProgress::NotStarted))
165 }
166
167 fn update_progress(&mut self, vnode: VirtualNode, new_pos: OwnedRow, row_count_delta: u64) {
168 let progress = self.per_vnode.get_mut(&vnode).unwrap();
169 match progress {
170 LocalityBackfillProgress::NotStarted => {
171 *progress = LocalityBackfillProgress::InProgress {
172 current_pos: new_pos,
173 processed_rows: row_count_delta,
174 };
175 }
176 LocalityBackfillProgress::InProgress { processed_rows, .. } => {
177 *progress = LocalityBackfillProgress::InProgress {
178 current_pos: new_pos,
179 processed_rows: *processed_rows + row_count_delta,
180 };
181 }
182 LocalityBackfillProgress::Completed { .. } => {
183 }
185 }
186 self.total_snapshot_rows += row_count_delta;
187 }
188
189 fn finish_vnode(&mut self, vnode: VirtualNode, pk_len: usize) {
190 let progress = self.per_vnode.get_mut(&vnode).unwrap();
191 match progress {
192 LocalityBackfillProgress::NotStarted => {
193 let final_pos = OwnedRow::new(vec![None; pk_len]);
195 *progress = LocalityBackfillProgress::Completed {
196 final_pos,
197 total_rows: 0,
198 };
199 }
200 LocalityBackfillProgress::InProgress {
201 current_pos,
202 processed_rows,
203 } => {
204 *progress = LocalityBackfillProgress::Completed {
205 final_pos: current_pos.clone(),
206 total_rows: *processed_rows,
207 };
208 }
209 LocalityBackfillProgress::Completed { .. } => {
210 }
212 }
213 }
214
215 fn get_progress(&self, vnode: &VirtualNode) -> &LocalityBackfillProgress {
216 self.per_vnode.get(vnode).unwrap()
217 }
218}
219
220pub struct LocalityProviderExecutor<S: StateStore> {
238 upstream: Executor,
240
241 #[expect(dead_code)]
243 locality_columns: Vec<usize>,
244
245 state_table: StateTable<S>,
247
248 progress_table: StateTable<S>,
250
251 sort_buffer_table: Option<StateTable<S>>,
254
255 sort_buffer_settings: SortBufferSettings,
257
258 input_schema: Schema,
259
260 progress: CreateMviewProgressReporter,
262
263 fragment_id: FragmentId,
264
265 actor_id: ActorId,
266
267 metrics: Arc<StreamingMetrics>,
269
270 chunk_size: usize,
272
273 rate_limiter: MonitoredRateLimiter,
274}
275
276impl<S: StateStore> LocalityProviderExecutor<S> {
277 #[expect(clippy::too_many_arguments)]
278 pub fn new(
279 upstream: Executor,
280 locality_columns: Vec<usize>,
281 state_table: StateTable<S>,
282 progress_table: StateTable<S>,
283 sort_buffer_table: Option<StateTable<S>>,
284 sort_buffer_settings: SortBufferSettings,
285 input_schema: Schema,
286 progress: CreateMviewProgressReporter,
287 metrics: Arc<StreamingMetrics>,
288 chunk_size: usize,
289 fragment_id: FragmentId,
290 rate_limit: RateLimit,
291 ) -> Self {
292 let rate_limiter = RateLimiter::new(rate_limit).monitored(state_table.table_id());
293 Self {
294 upstream,
295 locality_columns,
296 state_table,
297 progress_table,
298 sort_buffer_table,
299 sort_buffer_settings,
300 input_schema,
301 actor_id: progress.actor_id(),
302 progress,
303 metrics,
304 chunk_size,
305 fragment_id,
306 rate_limiter,
307 }
308 }
309
310 fn apply_throttle(
312 rate_limiter: &MonitoredRateLimiter,
313 fragment_id: FragmentId,
314 barrier: &Barrier,
315 ) -> Option<RateLimit> {
316 let Some(Mutation::Throttle(fragment_to_apply)) = barrier.mutation.as_deref() else {
317 return None;
318 };
319 let entry = fragment_to_apply.get(&fragment_id)?;
320 if entry.throttle_type() != ThrottleType::Backfill {
321 return None;
322 }
323 let new_rate_limit = entry.rate_limit.into();
324 let old_rate_limit = rate_limiter.update(new_rate_limit);
325 (old_rate_limit != new_rate_limit).then(|| {
326 tracing::info!(
327 ?old_rate_limit,
328 ?new_rate_limit,
329 %fragment_id,
330 "locality backfill rate limit changed"
331 );
332 new_rate_limit
333 })
334 }
335
336 #[try_stream(ok = (VirtualNode, OwnedRow), error = StreamExecutorError)]
338 async fn make_snapshot_stream<'a>(
339 reader: FlushedStateTableReader<S>,
340 backfill_state: LocalityBackfillState,
341 rate_limiter: &'a MonitoredRateLimiter,
342 ) {
343 for vnode in reader.vnodes().iter_vnodes() {
345 let progress = backfill_state.get_progress(&vnode);
346
347 let current_pos = match progress {
348 LocalityBackfillProgress::NotStarted => None,
349 LocalityBackfillProgress::Completed { .. } => {
350 continue;
352 }
353 LocalityBackfillProgress::InProgress { current_pos, .. } => {
354 Some(current_pos.clone())
355 }
356 };
357
358 let range_bounds = if let Some(ref pos) = current_pos {
360 let start_bound = std::ops::Bound::Excluded(pos.as_inner());
361 (start_bound, std::ops::Bound::<&[Datum]>::Unbounded)
362 } else {
363 (
364 std::ops::Bound::<&[Datum]>::Unbounded,
365 std::ops::Bound::<&[Datum]>::Unbounded,
366 )
367 };
368
369 let iter = reader
371 .iter_with_vnode(
372 vnode,
373 &range_bounds,
374 PrefetchOptions::prefetch_for_small_range_scan(),
375 )
376 .await?;
377 pin_mut!(iter);
378
379 while let Some(row) = iter.try_next().await? {
380 rate_limiter.wait(1).await;
381 yield (vnode, row);
382 }
383 }
384 }
385
386 async fn persist_backfill_state(
388 progress_table: &mut StateTable<S>,
389 backfill_state: &LocalityBackfillState,
390 ) -> StreamExecutorResult<()> {
391 for (vnode, progress) in &backfill_state.per_vnode {
392 let (is_finished, current_pos, row_count) = match progress {
393 LocalityBackfillProgress::NotStarted => continue, LocalityBackfillProgress::InProgress {
395 current_pos,
396 processed_rows,
397 } => (false, current_pos.clone(), *processed_rows),
398 LocalityBackfillProgress::Completed {
399 final_pos,
400 total_rows,
401 } => (true, final_pos.clone(), *total_rows),
402 };
403
404 let mut row_data = vec![Some(vnode.to_scalar().into())];
406 row_data.extend(current_pos);
407 row_data.push(Some(risingwave_common::types::ScalarImpl::Bool(
408 is_finished,
409 )));
410 row_data.push(Some(risingwave_common::types::ScalarImpl::Int64(
411 row_count as i64,
412 )));
413
414 let new_row = OwnedRow::new(row_data);
415
416 let key_data = vec![Some(vnode.to_scalar().into())];
419 let key = OwnedRow::new(key_data);
420
421 if let Some(existing_row) = progress_table.get_row(&key).await? {
422 progress_table.update(existing_row, new_row);
424 } else {
425 progress_table.insert(new_row);
427 }
428 }
429 Ok(())
430 }
431
432 async fn load_backfill_state(
434 progress_table: &StateTable<S>,
435 ) -> StreamExecutorResult<LocalityBackfillState> {
436 let mut backfill_state = LocalityBackfillState::new(progress_table.vnodes().iter_vnodes());
437 let mut total_snapshot_rows = 0;
438
439 for vnode in progress_table.vnodes().iter_vnodes() {
441 let key_data = vec![Some(vnode.to_scalar().into())];
443
444 let key = OwnedRow::new(key_data);
445
446 if let Some(row) = progress_table.get_row(&key).await? {
447 let finished_col_idx = row.len() - 2;
449 let is_finished = row
450 .datum_at(finished_col_idx)
451 .map(|d| d.into_bool())
452 .unwrap_or(false);
453
454 let row_count = row
456 .datum_at(row.len() - 1)
457 .map(|d| d.into_int64() as u64)
458 .unwrap_or(0);
459
460 let current_pos_data: Vec<Datum> = (1..finished_col_idx)
461 .map(|i| row.datum_at(i).to_owned_datum())
462 .collect();
463 let current_pos = OwnedRow::new(current_pos_data);
464
465 let progress = if is_finished {
467 LocalityBackfillProgress::Completed {
468 final_pos: current_pos,
469 total_rows: row_count,
470 }
471 } else {
472 LocalityBackfillProgress::InProgress {
473 current_pos,
474 processed_rows: row_count,
475 }
476 };
477
478 backfill_state.per_vnode.insert(vnode, progress);
479 total_snapshot_rows += row_count;
480 }
481 }
483
484 backfill_state.total_snapshot_rows = total_snapshot_rows;
485 Ok(backfill_state)
486 }
487
488 fn mark_chunk(
490 chunk: StreamChunk,
491 backfill_state: &LocalityBackfillState,
492 state_table: &StateTable<S>,
493 ) -> StreamExecutorResult<StreamChunk> {
494 let chunk = chunk.compact_vis();
495 let (data, ops) = chunk.into_parts();
496 let mut new_visibility = risingwave_common::bitmap::BitmapBuilder::with_capacity(ops.len());
497
498 let pk_indices = state_table.pk_indices();
499 let pk_order = state_table.pk_serde().get_order_types();
500
501 for row in data.rows() {
502 let pk = row.project(pk_indices);
504 let vnode = state_table.compute_vnode_by_pk(pk);
505
506 let visible = match backfill_state.get_progress(&vnode) {
507 LocalityBackfillProgress::Completed { .. } => true,
508 LocalityBackfillProgress::NotStarted => false,
509 LocalityBackfillProgress::InProgress { current_pos, .. } => {
510 cmp_datum_iter(pk.iter(), current_pos.iter(), pk_order.iter().copied()).is_le()
512 }
513 };
514
515 new_visibility.append(visible);
516 }
517
518 let (columns, _) = data.into_parts();
519 let chunk = StreamChunk::with_visibility(ops, columns, new_visibility.finish());
520 Ok(chunk)
521 }
522
523 fn handle_snapshot_chunk(
524 data_chunk: DataChunk,
525 vnode: VirtualNode,
526 pk_indices: &[usize],
527 backfill_state: &mut LocalityBackfillState,
528 cur_barrier_snapshot_processed_rows: &mut u64,
529 ) -> StreamExecutorResult<StreamChunk> {
530 let chunk = StreamChunk::from_parts(vec![Op::Insert; data_chunk.cardinality()], data_chunk);
531 let chunk_cardinality = chunk.cardinality() as u64;
532
533 if let Some(last_row) = chunk.rows().last() {
536 let pk = last_row.1.project(pk_indices);
537 let pk_owned = pk.into_owned_row();
538 backfill_state.update_progress(vnode, pk_owned, chunk_cardinality);
539 }
540
541 *cur_barrier_snapshot_processed_rows += chunk_cardinality;
542 Ok(chunk)
543 }
544
545 fn write_chunk_to_sort_buffer(
550 sort_buffer_table: &mut StateTable<S>,
551 chunk: &StreamChunk,
552 generation: i64,
553 seq: &mut i64,
554 ) {
555 let cardinality = chunk.cardinality();
556 debug_assert_eq!(
557 cardinality,
558 chunk.capacity(),
559 "chunk must be compacted before buffering"
560 );
561
562 let mut columns = chunk.data_chunk().columns().to_vec();
563 columns.push(Arc::new(ArrayImpl::Int64(I64Array::from_iter(
564 std::iter::repeat_n(Some(generation), cardinality),
565 ))));
566 columns.push(Arc::new(ArrayImpl::Int16(I16Array::from_iter(
567 chunk.ops().iter().map(|op| Some(op_to_code(*op))),
568 ))));
569 columns.push(Arc::new(ArrayImpl::Int64(I64Array::from_iter(
570 (*seq..*seq + cardinality as i64).map(Some),
571 ))));
572 *seq += cardinality as i64;
573
574 let log_chunk = StreamChunk::new(vec![Op::Insert; cardinality], columns);
575 sort_buffer_table.write_chunk(log_chunk);
576 }
577
578 #[try_stream(ok = StreamChunk, error = StreamExecutorError)]
585 async fn make_sort_buffer_replay_stream(
586 reader: FlushedStateTableReader<S>,
587 generation: i64,
588 group_key_indices: Vec<usize>,
589 input_len: usize,
590 data_types: Vec<DataType>,
591 chunk_size: usize,
592 ) {
593 let range = (
595 Bound::Included(OwnedRow::new(vec![Some(ScalarImpl::Int64(generation))])),
596 Bound::Excluded(OwnedRow::new(vec![Some(ScalarImpl::Int64(generation + 1))])),
597 );
598
599 let mut chunk_builder = StreamChunkBuilder::new(chunk_size, data_types);
600 let mut cur_key: Option<OwnedRow> = None;
601 let mut slot: Option<Record<OwnedRow>> = None;
602
603 for vnode in reader.vnodes().iter_vnodes() {
606 let iter = reader
607 .iter_with_vnode(
608 vnode,
609 &range,
610 PrefetchOptions::prefetch_for_large_range_scan(),
611 )
612 .await?;
613 pin_mut!(iter);
614
615 while let Some(row) = iter.try_next().await? {
616 let is_insert = row
618 .datum_at(input_len + 1)
619 .expect("op column must not be null")
620 .into_int16()
621 == Op::Insert.to_i16();
622
623 let same_key = match &cur_key {
624 Some(key) => (&row).project(&group_key_indices).iter().eq(key.iter()),
625 None => false,
626 };
627 if !same_key {
628 if let Some(record) = take_compacted_record(&mut slot)
629 && let Some(chunk) = chunk_builder.append_record(record)
630 {
631 yield chunk;
632 }
633 cur_key = Some((&row).project(&group_key_indices).into_owned_row());
634 }
635
636 let input_row = OwnedRow::new(row.as_inner()[..input_len].to_vec());
637 apply_change_to_slot(&mut slot, is_insert, input_row);
638 }
639 }
640
641 if let Some(record) = take_compacted_record(&mut slot)
642 && let Some(chunk) = chunk_builder.append_record(record)
643 {
644 yield chunk;
645 }
646 if let Some(chunk) = chunk_builder.take() {
647 yield chunk;
648 }
649 }
650}
651
652impl<S: StateStore> Execute for LocalityProviderExecutor<S> {
653 fn execute(self: Box<Self>) -> BoxedMessageStream {
654 self.execute_inner().boxed()
655 }
656}
657
658impl<S: StateStore> LocalityProviderExecutor<S> {
659 #[try_stream(ok = Message, error = StreamExecutorError)]
660 async fn execute_inner(mut self) {
661 let mut upstream = self.upstream.execute();
662
663 let first_barrier = expect_first_barrier(&mut upstream).await?;
665 let first_epoch = first_barrier.epoch;
666
667 yield Message::Barrier(first_barrier);
669
670 let mut state_table = self.state_table;
671 let mut progress_table = self.progress_table;
672 let mut sort_buffer_table = self.sort_buffer_table;
673 let rate_limiter = self.rate_limiter;
674
675 state_table.init_epoch(first_epoch).await?;
677 progress_table.init_epoch(first_epoch).await?;
678 if let Some(sort_table) = &mut sort_buffer_table {
679 sort_table.init_epoch(first_epoch).await?;
680 }
681
682 let mut cur_gen: u64 = first_epoch.curr;
685
686 let mut backfill_state = Self::load_backfill_state(&progress_table).await?;
688
689 let pk_indices = state_table.pk_indices().iter().cloned().collect_vec();
691
692 let need_backfill = !backfill_state.is_completed();
693 let mut report_finished_on_first_barrier = !need_backfill;
694
695 let need_buffering = backfill_state
696 .per_vnode
697 .values()
698 .all(|progress| matches!(progress, LocalityBackfillProgress::NotStarted));
699 if need_buffering {
701 let mut start_backfill = false;
703
704 #[for_await]
705 for msg in upstream.by_ref() {
706 let msg = msg?;
707
708 match msg {
709 Message::Watermark(_) => {
710 }
712 Message::Chunk(chunk) => {
713 state_table.write_chunk(chunk);
714 state_table.try_flush().await?;
715 }
716 Message::Barrier(barrier) => {
717 let epoch = barrier.epoch;
718 Self::apply_throttle(&rate_limiter, self.fragment_id, &barrier);
719
720 if let Some(mutation) = barrier.mutation.as_deref() {
722 use crate::executor::Mutation;
723 if let Mutation::StartFragmentBackfill { fragment_ids } = mutation
724 && fragment_ids.contains(&self.fragment_id)
725 {
726 tracing::info!(
727 "Start backfill of locality provider with fragment id: {:?}",
728 &self.fragment_id
729 );
730 start_backfill = true;
731 }
732 }
733
734 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
736 state_table
737 .commit_assert_no_update_vnode_bitmap(epoch)
738 .await?;
739 progress_table
740 .commit_assert_no_update_vnode_bitmap(epoch)
741 .await?;
742 if let Some(sort_table) = &mut sort_buffer_table {
743 sort_table
744 .commit_assert_no_update_vnode_bitmap(epoch)
745 .await?;
746 }
747 cur_gen = epoch.curr;
748
749 yield Message::Barrier(barrier);
750
751 if start_backfill {
753 break;
754 }
755 }
756 }
757 }
758 }
759
760 if need_backfill {
787 let mut upstream_chunk_buffer: Vec<StreamChunk> = vec![];
788
789 let metrics = self
790 .metrics
791 .new_backfill_metrics(state_table.table_id(), self.actor_id);
792
793 let snapshot_data_types = self.input_schema.data_types();
795 let vnodes = state_table.vnodes().clone();
796 let new_builders = |rate_limit| -> Builders {
797 vnodes
798 .iter_vnodes()
799 .map(|vnode| {
800 let builder = create_builder(
801 rate_limit,
802 self.chunk_size,
803 snapshot_data_types.clone(),
804 );
805 (vnode, builder)
806 })
807 .collect()
808 };
809 let mut builders = new_builders(rate_limiter.rate_limit());
810
811 let snapshot_reader = state_table.flushed_snapshot_reader();
812 let snapshot_stream = Self::make_snapshot_stream(
813 snapshot_reader.clone(),
814 backfill_state.clone(),
815 &rate_limiter,
816 );
817 pin_mut!(snapshot_stream);
818
819 'backfill_loop: loop {
820 let mut cur_barrier_snapshot_processed_rows: u64 = 0;
821 let mut cur_barrier_upstream_processed_rows: u64 = 0;
822
823 let barrier = loop {
826 let upstream_next = upstream.next();
827 let mut snapshot_stream_ref = snapshot_stream.as_mut();
828 let snapshot_paused = rate_limiter.rate_limit().is_paused();
829 let snapshot_next = async move {
830 if snapshot_paused {
831 pending().await
832 } else {
833 snapshot_stream_ref.next().await
834 }
835 };
836 pin_mut!(upstream_next);
837 pin_mut!(snapshot_next);
838
839 match select(upstream_next, snapshot_next).await {
840 FutureEither::Left((msg, _)) => match msg.transpose()? {
841 Some(Message::Barrier(barrier)) => {
842 break barrier;
844 }
845 Some(Message::Chunk(chunk)) => {
846 upstream_chunk_buffer.push(chunk.compact_vis());
848 }
849 Some(Message::Watermark(_)) => {
850 }
852 None => {
853 return Err(anyhow::anyhow!(
854 "locality provider upstream ended unexpectedly during backfill"
855 )
856 .into());
857 }
858 },
859 FutureEither::Right((msg, _)) => match msg.transpose()? {
860 Some((vnode, row)) => {
861 let builder = builders.get_mut(&vnode).unwrap();
863 if let Some(data_chunk) = builder.append_one_row(row) {
864 let chunk = Self::handle_snapshot_chunk(
866 data_chunk,
867 vnode,
868 &pk_indices,
869 &mut backfill_state,
870 &mut cur_barrier_snapshot_processed_rows,
871 )?;
872 yield Message::Chunk(chunk);
873 }
874 }
877 None => {
878 for (vnode, builder) in &mut builders {
881 if let Some(data_chunk) = builder.consume_all() {
882 let chunk = Self::handle_snapshot_chunk(
883 data_chunk,
884 *vnode,
885 &pk_indices,
886 &mut backfill_state,
887 &mut cur_barrier_snapshot_processed_rows,
888 )?;
889 yield Message::Chunk(chunk);
890 }
891 }
892
893 for chunk in upstream_chunk_buffer.drain(..) {
895 let chunk_cardinality = chunk.cardinality() as u64;
896 cur_barrier_upstream_processed_rows += chunk_cardinality;
897 yield Message::Chunk(chunk);
898 }
899 metrics
900 .backfill_snapshot_read_row_count
901 .inc_by(cur_barrier_snapshot_processed_rows);
902 metrics
903 .backfill_upstream_output_row_count
904 .inc_by(cur_barrier_upstream_processed_rows);
905 break 'backfill_loop;
906 }
907 },
908 }
909 };
910
911 for (vnode, builder) in &mut builders {
913 if let Some(data_chunk) = builder.consume_all() {
914 let chunk = Self::handle_snapshot_chunk(
915 data_chunk,
916 *vnode,
917 &pk_indices,
918 &mut backfill_state,
919 &mut cur_barrier_snapshot_processed_rows,
920 )?;
921 yield Message::Chunk(chunk);
922 }
923 }
924
925 if let Some(new_rate_limit) =
926 Self::apply_throttle(&rate_limiter, self.fragment_id, &barrier)
927 {
928 builders = new_builders(new_rate_limit);
929 }
930
931 let should_refresh_snapshot = !upstream_chunk_buffer.is_empty();
933 for chunk in upstream_chunk_buffer.drain(..) {
934 cur_barrier_upstream_processed_rows += chunk.cardinality() as u64;
935
936 if backfill_state.has_progress() {
938 let marked_chunk =
939 Self::mark_chunk(chunk.clone(), &backfill_state, &state_table)?;
940 yield Message::Chunk(marked_chunk);
941 }
942
943 state_table.write_chunk(chunk);
946 }
947
948 let barrier_epoch = barrier.epoch;
949 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
950 state_table
951 .commit_assert_no_update_vnode_bitmap(barrier_epoch)
952 .await?;
953 if let Some(sort_table) = &mut sort_buffer_table {
954 sort_table
955 .commit_assert_no_update_vnode_bitmap(barrier_epoch)
956 .await?;
957 }
958 cur_gen = barrier_epoch.curr;
959
960 let total_snapshot_processed_rows: u64 = backfill_state
963 .vnodes()
964 .map(|(_, progress)| match *progress {
965 LocalityBackfillProgress::InProgress { processed_rows, .. } => {
966 processed_rows
967 }
968 LocalityBackfillProgress::Completed { total_rows, .. } => total_rows,
969 LocalityBackfillProgress::NotStarted => 0,
970 })
971 .sum();
972
973 self.progress.update_with_buffered_rows(
974 barrier.epoch,
975 barrier.epoch.curr, total_snapshot_processed_rows,
977 0,
978 );
979
980 Self::persist_backfill_state(&mut progress_table, &backfill_state).await?;
982 progress_table
983 .commit_assert_no_update_vnode_bitmap(barrier_epoch)
984 .await?;
985
986 metrics
987 .backfill_snapshot_read_row_count
988 .inc_by(cur_barrier_snapshot_processed_rows);
989 metrics
990 .backfill_upstream_output_row_count
991 .inc_by(cur_barrier_upstream_processed_rows);
992
993 yield Message::Barrier(barrier);
994
995 if should_refresh_snapshot {
996 snapshot_stream.set(Self::make_snapshot_stream(
997 snapshot_reader.clone(),
998 backfill_state.clone(),
999 &rate_limiter,
1000 ));
1001 }
1002 }
1003 }
1004
1005 tracing::debug!("Locality provider backfill finished, forwarding upstream directly");
1006
1007 if need_backfill && !backfill_state.is_completed() {
1010 while let Some(Ok(msg)) = upstream.next().await {
1011 match msg {
1012 Message::Barrier(barrier) => {
1013 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
1014
1015 state_table
1017 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
1018 .await?;
1019 if let Some(sort_table) = &mut sort_buffer_table {
1020 sort_table
1021 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
1022 .await?;
1023 }
1024 cur_gen = barrier.epoch.curr;
1025
1026 for vnode in state_table.vnodes().iter_vnodes() {
1028 backfill_state.finish_vnode(vnode, pk_indices.len());
1029 }
1030
1031 let total_snapshot_processed_rows: u64 = backfill_state
1033 .vnodes()
1034 .map(|(_, progress)| match *progress {
1035 LocalityBackfillProgress::Completed { total_rows, .. } => {
1036 total_rows
1037 }
1038 LocalityBackfillProgress::InProgress { processed_rows, .. } => {
1039 processed_rows
1040 }
1041 LocalityBackfillProgress::NotStarted => 0,
1042 })
1043 .sum();
1044
1045 self.progress.finish_with_buffered_rows(
1048 barrier.epoch,
1049 total_snapshot_processed_rows,
1050 total_snapshot_processed_rows,
1051 );
1052
1053 Self::persist_backfill_state(&mut progress_table, &backfill_state).await?;
1055 progress_table
1056 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
1057 .await?;
1058
1059 yield Message::Barrier(barrier);
1060 break; }
1062 Message::Chunk(chunk) => {
1063 yield Message::Chunk(chunk);
1065 }
1066 Message::Watermark(watermark) => {
1067 yield Message::Watermark(watermark);
1069 }
1070 }
1071 }
1072 }
1073
1074 let sort_buffer_active = sort_buffer_table.is_some() && self.sort_buffer_settings.enabled;
1081 let activate_threshold = self.sort_buffer_settings.activate_threshold;
1082
1083 let group_key_indices = state_table.pk_indices().to_vec();
1087 let input_data_types = self.input_schema.data_types();
1088 let input_len = self.input_schema.len();
1089
1090 let mut buffering = false;
1094 let mut rows_in_epoch: u64 = 0;
1095 let mut seq: i64 = 0;
1096 let mut needs_cleanup = true;
1100 let mut pending_watermarks: BTreeMap<usize, Watermark> = BTreeMap::new();
1103
1104 #[for_await]
1105 for msg in upstream {
1106 let msg = msg?;
1107
1108 match msg {
1109 Message::Chunk(chunk) => {
1110 if !buffering {
1111 rows_in_epoch += chunk.cardinality() as u64;
1112 if sort_buffer_active && rows_in_epoch > activate_threshold {
1113 tracing::info!(
1114 actor_id = %self.actor_id,
1115 fragment_id = %self.fragment_id,
1116 rows_in_epoch,
1117 activate_threshold,
1118 "locality provider sort buffer activated for this epoch"
1119 );
1120 buffering = true;
1121 }
1122 }
1123 if buffering {
1124 let chunk = chunk.compact_vis();
1125 let sort_table = sort_buffer_table.as_mut().unwrap();
1126 Self::write_chunk_to_sort_buffer(
1127 sort_table,
1128 &chunk,
1129 cur_gen as i64,
1130 &mut seq,
1131 );
1132 sort_table.try_flush().await?;
1133 } else {
1134 yield Message::Chunk(chunk);
1135 }
1136 }
1137 Message::Watermark(watermark) => {
1138 if buffering {
1139 pending_watermarks.insert(watermark.col_idx, watermark);
1140 } else {
1141 yield Message::Watermark(watermark);
1142 }
1143 }
1144 Message::Barrier(barrier) => {
1145 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
1146
1147 state_table
1149 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
1150 .await?;
1151 progress_table
1152 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
1153 .await?;
1154
1155 if let Some(sort_table) = &mut sort_buffer_table {
1156 debug_assert_eq!(cur_gen, barrier.epoch.prev);
1157
1158 if buffering || needs_cleanup {
1159 sort_table.update_watermark(ScalarImpl::Int64(cur_gen as i64));
1165 }
1166 sort_table
1169 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
1170 .await?;
1171
1172 if buffering {
1173 let replay_stream = Self::make_sort_buffer_replay_stream(
1174 sort_table.flushed_snapshot_reader(),
1175 cur_gen as i64,
1176 group_key_indices.clone(),
1177 input_len,
1178 input_data_types.clone(),
1179 self.chunk_size,
1180 );
1181 pin_mut!(replay_stream);
1182 while let Some(chunk) = replay_stream.try_next().await? {
1183 yield Message::Chunk(chunk);
1184 }
1185
1186 for (_, watermark) in std::mem::take(&mut pending_watermarks) {
1187 yield Message::Watermark(watermark);
1188 }
1189 }
1190 needs_cleanup = buffering;
1191 }
1192
1193 if report_finished_on_first_barrier {
1194 self.progress.finish_with_buffered_rows(
1196 barrier.epoch,
1197 backfill_state.total_snapshot_rows,
1198 backfill_state.total_snapshot_rows,
1199 );
1200 report_finished_on_first_barrier = false;
1201 }
1202
1203 cur_gen = barrier.epoch.curr;
1204 buffering = false;
1205 rows_in_epoch = 0;
1206 seq = 0;
1207
1208 yield Message::Barrier(barrier);
1209 }
1210 }
1211 }
1212 }
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217 use std::time::Duration;
1218
1219 use risingwave_common::array::StreamChunkTestExt;
1220 use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema, TableId};
1221 use risingwave_common::hash::VirtualNode;
1222 use risingwave_common::types::DataType;
1223 use risingwave_common::util::epoch::{EpochPair, test_epoch};
1224 use risingwave_common::util::sort_util::OrderType;
1225 use risingwave_storage::memory::MemoryStateStore;
1226 use tokio::time::timeout;
1227
1228 use super::*;
1229 use crate::common::table::test_utils::gen_pbtable;
1230 use crate::executor::test_utils::{MockSource, StreamExecutorTestExt};
1231 use crate::task::LocalBarrierManager;
1232
1233 fn row(values: impl IntoIterator<Item = i32>) -> OwnedRow {
1234 OwnedRow::new(
1235 values
1236 .into_iter()
1237 .map(|v| Some(ScalarImpl::Int32(v)))
1238 .collect(),
1239 )
1240 }
1241
1242 fn parts(record: Option<Record<OwnedRow>>) -> Option<(&'static str, Vec<OwnedRow>)> {
1244 record.map(|record| match record {
1245 Record::Insert { new_row } => ("insert", vec![new_row]),
1246 Record::Delete { old_row } => ("delete", vec![old_row]),
1247 Record::Update { old_row, new_row } => ("update", vec![old_row, new_row]),
1248 })
1249 }
1250
1251 #[test]
1252 fn test_per_key_compaction_slot() {
1253 let mut slot = None;
1255 apply_change_to_slot(&mut slot, true, row([1]));
1256 apply_change_to_slot(&mut slot, false, row([1]));
1257 assert_eq!(parts(take_compacted_record(&mut slot)), None);
1258
1259 let mut slot = None;
1261 apply_change_to_slot(&mut slot, false, row([1]));
1262 apply_change_to_slot(&mut slot, true, row([2]));
1263 assert_eq!(
1264 parts(take_compacted_record(&mut slot)),
1265 Some(("update", vec![row([1]), row([2])]))
1266 );
1267
1268 let mut slot = None;
1270 apply_change_to_slot(&mut slot, false, row([1]));
1271 apply_change_to_slot(&mut slot, true, row([2]));
1272 apply_change_to_slot(&mut slot, false, row([2]));
1273 assert_eq!(
1274 parts(take_compacted_record(&mut slot)),
1275 Some(("delete", vec![row([1])]))
1276 );
1277
1278 let mut slot = None;
1280 apply_change_to_slot(&mut slot, false, row([1]));
1281 apply_change_to_slot(&mut slot, true, row([1]));
1282 assert_eq!(parts(take_compacted_record(&mut slot)), None);
1283
1284 let mut slot = None;
1286 apply_change_to_slot(&mut slot, true, row([1]));
1287 assert_eq!(
1288 parts(take_compacted_record(&mut slot)),
1289 Some(("insert", vec![row([1])]))
1290 );
1291 }
1292
1293 #[tokio::test]
1297 async fn test_sort_buffer_write_and_replay() {
1298 let input_data_types = vec![DataType::Int32, DataType::Int32, DataType::Int32];
1300 let input_len = input_data_types.len();
1301 let group_key_indices = vec![0, 1];
1302
1303 let column_descs = vec![
1306 ColumnDesc::unnamed(ColumnId::new(0), DataType::Int32),
1307 ColumnDesc::unnamed(ColumnId::new(1), DataType::Int32),
1308 ColumnDesc::unnamed(ColumnId::new(2), DataType::Int32),
1309 ColumnDesc::unnamed(ColumnId::new(3), DataType::Int64),
1310 ColumnDesc::unnamed(ColumnId::new(4), DataType::Int16),
1311 ColumnDesc::unnamed(ColumnId::new(5), DataType::Int64),
1312 ];
1313 let pk_indices = vec![3, 0, 1, 5];
1314 let order_types = vec![OrderType::ascending(); 4];
1315
1316 let mut table = StateTable::from_table_catalog(
1317 &gen_pbtable(TableId::new(1), column_descs, order_types, pk_indices, 0),
1318 MemoryStateStore::new(),
1319 None,
1320 )
1321 .await;
1322
1323 let mut epoch = EpochPair::new_test_epoch(test_epoch(1));
1324 table.init_epoch(epoch).await.unwrap();
1325
1326 let generation = test_epoch(1) as i64;
1327 let mut seq = 0;
1328
1329 let chunk = StreamChunk::from_pretty(
1335 " i i i
1336 + 3 1 100
1337 + 1 1 10
1338 U- 2 1 20
1339 U+ 2 1 21
1340 + 1 2 11
1341 - 3 1 100",
1342 );
1343 LocalityProviderExecutor::<MemoryStateStore>::write_chunk_to_sort_buffer(
1344 &mut table, &chunk, generation, &mut seq,
1345 );
1346 assert_eq!(seq, 6);
1347
1348 let other_chunk = StreamChunk::from_pretty(
1350 " i i i
1351 + 9 9 99",
1352 );
1353 let mut other_seq = 0;
1354 LocalityProviderExecutor::<MemoryStateStore>::write_chunk_to_sort_buffer(
1355 &mut table,
1356 &other_chunk,
1357 test_epoch(2) as i64,
1358 &mut other_seq,
1359 );
1360
1361 epoch.inc_for_test();
1363 table.commit_for_test(epoch).await.unwrap();
1364
1365 let replay_stream =
1366 LocalityProviderExecutor::<MemoryStateStore>::make_sort_buffer_replay_stream(
1367 table.flushed_snapshot_reader(),
1368 generation,
1369 group_key_indices,
1370 input_len,
1371 input_data_types,
1372 2,
1373 );
1374 pin_mut!(replay_stream);
1375 let mut chunks = vec![];
1376 while let Some(chunk) = replay_stream.try_next().await.unwrap() {
1377 chunks.push(chunk);
1378 }
1379
1380 assert_eq!(chunks.len(), 2);
1382 assert_eq!(
1383 chunks[0],
1384 StreamChunk::from_pretty(
1385 " i i i
1386 + 1 1 10
1387 + 1 2 11",
1388 )
1389 );
1390 assert_eq!(
1391 chunks[1],
1392 StreamChunk::from_pretty(
1393 " i i i
1394 U- 2 1 20
1395 U+ 2 1 21",
1396 )
1397 );
1398 }
1399
1400 #[tokio::test]
1401 async fn test_sort_buffer_starts_after_backfill_completion_barrier() {
1402 let store = MemoryStateStore::new();
1403 let input_schema = Schema::new(vec![
1404 Field::unnamed(DataType::Int32),
1405 Field::unnamed(DataType::Int32),
1406 Field::unnamed(DataType::Int32),
1407 ]);
1408 let input_columns = vec![
1409 ColumnDesc::unnamed(ColumnId::new(0), DataType::Int32),
1410 ColumnDesc::unnamed(ColumnId::new(1), DataType::Int32),
1411 ColumnDesc::unnamed(ColumnId::new(2), DataType::Int32),
1412 ];
1413
1414 let state_table = StateTable::from_table_catalog(
1416 &gen_pbtable(
1417 TableId::new(1),
1418 input_columns.clone(),
1419 vec![OrderType::ascending(); 2],
1420 vec![0, 1],
1421 0,
1422 ),
1423 store.clone(),
1424 None,
1425 )
1426 .await;
1427
1428 let progress_catalog = gen_pbtable(
1430 TableId::new(2),
1431 vec![
1432 ColumnDesc::unnamed(ColumnId::new(0), VirtualNode::RW_TYPE),
1433 ColumnDesc::unnamed(ColumnId::new(1), DataType::Int32),
1434 ColumnDesc::unnamed(ColumnId::new(2), DataType::Int32),
1435 ColumnDesc::unnamed(ColumnId::new(3), DataType::Boolean),
1436 ColumnDesc::unnamed(ColumnId::new(4), DataType::Int64),
1437 ],
1438 vec![OrderType::ascending()],
1439 vec![0],
1440 1,
1441 );
1442 let progress_table =
1443 StateTable::from_table_catalog(&progress_catalog, store.clone(), None).await;
1444
1445 let mut sort_buffer_catalog = gen_pbtable(
1448 TableId::new(3),
1449 vec![
1450 input_columns[0].clone(),
1451 input_columns[1].clone(),
1452 input_columns[2].clone(),
1453 ColumnDesc::unnamed(ColumnId::new(3), DataType::Int64),
1454 ColumnDesc::unnamed(ColumnId::new(4), DataType::Int16),
1455 ColumnDesc::unnamed(ColumnId::new(5), DataType::Int64),
1456 ],
1457 vec![OrderType::ascending(); 4],
1458 vec![3, 0, 1, 5],
1459 0,
1460 );
1461 sort_buffer_catalog.clean_watermark_indices = vec![3];
1462 let sort_buffer_table =
1463 StateTable::from_table_catalog_inconsistent_op(&sort_buffer_catalog, store, None).await;
1464
1465 let fragment_id: FragmentId = 1.into();
1466 let progress = CreateMviewProgressReporter::for_test(LocalBarrierManager::for_test());
1467 let (mut tx, source) = MockSource::channel();
1468 let upstream = source.into_executor(input_schema.clone(), vec![1]);
1469 let mut executor = LocalityProviderExecutor::new(
1470 upstream,
1471 vec![0],
1472 state_table,
1473 progress_table,
1474 Some(sort_buffer_table),
1475 SortBufferSettings {
1476 enabled: true,
1477 activate_threshold: 0,
1478 },
1479 input_schema,
1480 progress,
1481 Arc::new(StreamingMetrics::unused()),
1482 1024,
1483 fragment_id,
1484 RateLimit::Disabled,
1485 )
1486 .boxed()
1487 .execute();
1488
1489 tx.push_barrier(test_epoch(1), false);
1490 assert_eq!(
1491 executor.expect_barrier().await.epoch,
1492 EpochPair::new_test_epoch(test_epoch(1))
1493 );
1494
1495 tx.send_barrier(Barrier::new_test_barrier(test_epoch(2)).with_mutation(
1496 Mutation::StartFragmentBackfill {
1497 fragment_ids: [fragment_id].into(),
1498 },
1499 ));
1500 assert_eq!(
1501 executor.expect_barrier().await.epoch,
1502 EpochPair::new_test_epoch(test_epoch(2))
1503 );
1504
1505 assert!(
1508 timeout(Duration::from_millis(200), executor.next())
1509 .await
1510 .is_err()
1511 );
1512 let transition_chunk = StreamChunk::from_pretty(
1513 " i i i
1514 + 2 1 20
1515 + 1 1 10",
1516 );
1517 tx.push_chunk(transition_chunk.clone());
1518 assert_eq!(executor.expect_chunk().await, transition_chunk);
1519
1520 tx.push_barrier(test_epoch(3), false);
1521 assert_eq!(
1522 executor.expect_barrier().await.epoch,
1523 EpochPair::new_test_epoch(test_epoch(3))
1524 );
1525
1526 tx.push_chunk(StreamChunk::from_pretty(
1529 " i i i
1530 + 4 1 40
1531 + 3 1 30",
1532 ));
1533 tx.push_barrier(test_epoch(4), false);
1534 assert_eq!(
1535 executor.expect_chunk().await,
1536 StreamChunk::from_pretty(
1537 " i i i
1538 + 3 1 30
1539 + 4 1 40",
1540 )
1541 );
1542 assert_eq!(
1543 executor.expect_barrier().await.epoch,
1544 EpochPair::new_test_epoch(test_epoch(4))
1545 );
1546 }
1547}