1use std::collections::HashMap;
16use std::sync::Arc;
17
18use futures::future::{Either as FutureEither, select};
19use futures::{StreamExt, TryStreamExt, pin_mut};
20use futures_async_stream::try_stream;
21use itertools::Itertools;
22use risingwave_common::array::{DataChunk, Op, StreamChunk};
23use risingwave_common::catalog::Schema;
24use risingwave_common::hash::{VirtualNode, VnodeBitmapExt};
25use risingwave_common::row::{OwnedRow, Row, RowExt};
26use risingwave_common::types::{Datum, ToOwnedDatum};
27use risingwave_common::util::chunk_coalesce::DataChunkBuilder;
28use risingwave_common::util::sort_util::cmp_datum_iter;
29use risingwave_common_rate_limit::RateLimit;
30use risingwave_storage::StateStore;
31use risingwave_storage::store::PrefetchOptions;
32
33use crate::common::table::state_table::{FlushedStateTableReader, StateTable};
34use crate::executor::backfill::utils::create_builder;
35use crate::executor::prelude::*;
36use crate::task::{CreateMviewProgressReporter, FragmentId};
37
38type Builders = HashMap<VirtualNode, DataChunkBuilder>;
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42enum LocalityBackfillProgress {
43 NotStarted,
45 InProgress {
47 current_pos: OwnedRow,
49 processed_rows: u64,
51 },
52 Completed {
54 final_pos: OwnedRow,
56 total_rows: u64,
58 },
59}
60
61#[derive(Clone, Debug)]
63struct LocalityBackfillState {
64 per_vnode: HashMap<VirtualNode, LocalityBackfillProgress>,
66 total_snapshot_rows: u64,
68}
69
70impl LocalityBackfillState {
71 fn new(vnodes: impl Iterator<Item = VirtualNode>) -> Self {
72 let per_vnode = vnodes
73 .map(|vnode| (vnode, LocalityBackfillProgress::NotStarted))
74 .collect();
75 Self {
76 per_vnode,
77 total_snapshot_rows: 0,
78 }
79 }
80
81 fn is_completed(&self) -> bool {
82 self.per_vnode
83 .values()
84 .all(|progress| matches!(progress, LocalityBackfillProgress::Completed { .. }))
85 }
86
87 fn vnodes(&self) -> impl Iterator<Item = (VirtualNode, &LocalityBackfillProgress)> {
88 self.per_vnode
89 .iter()
90 .map(|(&vnode, progress)| (vnode, progress))
91 }
92
93 fn has_progress(&self) -> bool {
94 self.per_vnode
95 .values()
96 .any(|progress| matches!(progress, LocalityBackfillProgress::InProgress { .. }))
97 }
98
99 fn update_progress(&mut self, vnode: VirtualNode, new_pos: OwnedRow, row_count_delta: u64) {
100 let progress = self.per_vnode.get_mut(&vnode).unwrap();
101 match progress {
102 LocalityBackfillProgress::NotStarted => {
103 *progress = LocalityBackfillProgress::InProgress {
104 current_pos: new_pos,
105 processed_rows: row_count_delta,
106 };
107 }
108 LocalityBackfillProgress::InProgress { processed_rows, .. } => {
109 *progress = LocalityBackfillProgress::InProgress {
110 current_pos: new_pos,
111 processed_rows: *processed_rows + row_count_delta,
112 };
113 }
114 LocalityBackfillProgress::Completed { .. } => {
115 }
117 }
118 self.total_snapshot_rows += row_count_delta;
119 }
120
121 fn finish_vnode(&mut self, vnode: VirtualNode, pk_len: usize) {
122 let progress = self.per_vnode.get_mut(&vnode).unwrap();
123 match progress {
124 LocalityBackfillProgress::NotStarted => {
125 let final_pos = OwnedRow::new(vec![None; pk_len]);
127 *progress = LocalityBackfillProgress::Completed {
128 final_pos,
129 total_rows: 0,
130 };
131 }
132 LocalityBackfillProgress::InProgress {
133 current_pos,
134 processed_rows,
135 } => {
136 *progress = LocalityBackfillProgress::Completed {
137 final_pos: current_pos.clone(),
138 total_rows: *processed_rows,
139 };
140 }
141 LocalityBackfillProgress::Completed { .. } => {
142 }
144 }
145 }
146
147 fn get_progress(&self, vnode: &VirtualNode) -> &LocalityBackfillProgress {
148 self.per_vnode.get(vnode).unwrap()
149 }
150}
151
152pub struct LocalityProviderExecutor<S: StateStore> {
164 upstream: Executor,
166
167 #[expect(dead_code)]
169 locality_columns: Vec<usize>,
170
171 state_table: StateTable<S>,
173
174 progress_table: StateTable<S>,
176
177 input_schema: Schema,
178
179 progress: CreateMviewProgressReporter,
181
182 fragment_id: FragmentId,
183
184 actor_id: ActorId,
185
186 metrics: Arc<StreamingMetrics>,
188
189 chunk_size: usize,
191}
192
193impl<S: StateStore> LocalityProviderExecutor<S> {
194 #[expect(clippy::too_many_arguments)]
195 pub fn new(
196 upstream: Executor,
197 locality_columns: Vec<usize>,
198 state_table: StateTable<S>,
199 progress_table: StateTable<S>,
200 input_schema: Schema,
201 progress: CreateMviewProgressReporter,
202 metrics: Arc<StreamingMetrics>,
203 chunk_size: usize,
204 fragment_id: FragmentId,
205 ) -> Self {
206 Self {
207 upstream,
208 locality_columns,
209 state_table,
210 progress_table,
211 input_schema,
212 actor_id: progress.actor_id(),
213 progress,
214 metrics,
215 chunk_size,
216 fragment_id,
217 }
218 }
219
220 #[try_stream(ok = (VirtualNode, OwnedRow), error = StreamExecutorError)]
222 async fn make_snapshot_stream(
223 reader: FlushedStateTableReader<S>,
224 backfill_state: LocalityBackfillState,
225 ) {
226 for vnode in reader.vnodes().iter_vnodes() {
228 let progress = backfill_state.get_progress(&vnode);
229
230 let current_pos = match progress {
231 LocalityBackfillProgress::NotStarted => None,
232 LocalityBackfillProgress::Completed { .. } => {
233 continue;
235 }
236 LocalityBackfillProgress::InProgress { current_pos, .. } => {
237 Some(current_pos.clone())
238 }
239 };
240
241 let range_bounds = if let Some(ref pos) = current_pos {
243 let start_bound = std::ops::Bound::Excluded(pos.as_inner());
244 (start_bound, std::ops::Bound::<&[Datum]>::Unbounded)
245 } else {
246 (
247 std::ops::Bound::<&[Datum]>::Unbounded,
248 std::ops::Bound::<&[Datum]>::Unbounded,
249 )
250 };
251
252 let iter = reader
254 .iter_with_vnode(
255 vnode,
256 &range_bounds,
257 PrefetchOptions::prefetch_for_small_range_scan(),
258 )
259 .await?;
260 pin_mut!(iter);
261
262 while let Some(row) = iter.try_next().await? {
263 yield (vnode, row);
264 }
265 }
266 }
267
268 async fn persist_backfill_state(
270 progress_table: &mut StateTable<S>,
271 backfill_state: &LocalityBackfillState,
272 ) -> StreamExecutorResult<()> {
273 for (vnode, progress) in &backfill_state.per_vnode {
274 let (is_finished, current_pos, row_count) = match progress {
275 LocalityBackfillProgress::NotStarted => continue, LocalityBackfillProgress::InProgress {
277 current_pos,
278 processed_rows,
279 } => (false, current_pos.clone(), *processed_rows),
280 LocalityBackfillProgress::Completed {
281 final_pos,
282 total_rows,
283 } => (true, final_pos.clone(), *total_rows),
284 };
285
286 let mut row_data = vec![Some(vnode.to_scalar().into())];
288 row_data.extend(current_pos);
289 row_data.push(Some(risingwave_common::types::ScalarImpl::Bool(
290 is_finished,
291 )));
292 row_data.push(Some(risingwave_common::types::ScalarImpl::Int64(
293 row_count as i64,
294 )));
295
296 let new_row = OwnedRow::new(row_data);
297
298 let key_data = vec![Some(vnode.to_scalar().into())];
301 let key = OwnedRow::new(key_data);
302
303 if let Some(existing_row) = progress_table.get_row(&key).await? {
304 progress_table.update(existing_row, new_row);
306 } else {
307 progress_table.insert(new_row);
309 }
310 }
311 Ok(())
312 }
313
314 async fn load_backfill_state(
316 progress_table: &StateTable<S>,
317 ) -> StreamExecutorResult<LocalityBackfillState> {
318 let mut backfill_state = LocalityBackfillState::new(progress_table.vnodes().iter_vnodes());
319 let mut total_snapshot_rows = 0;
320
321 for vnode in progress_table.vnodes().iter_vnodes() {
323 let key_data = vec![Some(vnode.to_scalar().into())];
325
326 let key = OwnedRow::new(key_data);
327
328 if let Some(row) = progress_table.get_row(&key).await? {
329 let finished_col_idx = row.len() - 2;
331 let is_finished = row
332 .datum_at(finished_col_idx)
333 .map(|d| d.into_bool())
334 .unwrap_or(false);
335
336 let row_count = row
338 .datum_at(row.len() - 1)
339 .map(|d| d.into_int64() as u64)
340 .unwrap_or(0);
341
342 let current_pos_data: Vec<Datum> = (1..finished_col_idx)
343 .map(|i| row.datum_at(i).to_owned_datum())
344 .collect();
345 let current_pos = OwnedRow::new(current_pos_data);
346
347 let progress = if is_finished {
349 LocalityBackfillProgress::Completed {
350 final_pos: current_pos,
351 total_rows: row_count,
352 }
353 } else {
354 LocalityBackfillProgress::InProgress {
355 current_pos,
356 processed_rows: row_count,
357 }
358 };
359
360 backfill_state.per_vnode.insert(vnode, progress);
361 total_snapshot_rows += row_count;
362 }
363 }
365
366 backfill_state.total_snapshot_rows = total_snapshot_rows;
367 Ok(backfill_state)
368 }
369
370 fn mark_chunk(
372 chunk: StreamChunk,
373 backfill_state: &LocalityBackfillState,
374 state_table: &StateTable<S>,
375 ) -> StreamExecutorResult<StreamChunk> {
376 let chunk = chunk.compact_vis();
377 let (data, ops) = chunk.into_parts();
378 let mut new_visibility = risingwave_common::bitmap::BitmapBuilder::with_capacity(ops.len());
379
380 let pk_indices = state_table.pk_indices();
381 let pk_order = state_table.pk_serde().get_order_types();
382
383 for row in data.rows() {
384 let pk = row.project(pk_indices);
386 let vnode = state_table.compute_vnode_by_pk(pk);
387
388 let visible = match backfill_state.get_progress(&vnode) {
389 LocalityBackfillProgress::Completed { .. } => true,
390 LocalityBackfillProgress::NotStarted => false,
391 LocalityBackfillProgress::InProgress { current_pos, .. } => {
392 cmp_datum_iter(pk.iter(), current_pos.iter(), pk_order.iter().copied()).is_le()
394 }
395 };
396
397 new_visibility.append(visible);
398 }
399
400 let (columns, _) = data.into_parts();
401 let chunk = StreamChunk::with_visibility(ops, columns, new_visibility.finish());
402 Ok(chunk)
403 }
404
405 fn handle_snapshot_chunk(
406 data_chunk: DataChunk,
407 vnode: VirtualNode,
408 pk_indices: &[usize],
409 backfill_state: &mut LocalityBackfillState,
410 cur_barrier_snapshot_processed_rows: &mut u64,
411 ) -> StreamExecutorResult<StreamChunk> {
412 let chunk = StreamChunk::from_parts(vec![Op::Insert; data_chunk.cardinality()], data_chunk);
413 let chunk_cardinality = chunk.cardinality() as u64;
414
415 if let Some(last_row) = chunk.rows().last() {
418 let pk = last_row.1.project(pk_indices);
419 let pk_owned = pk.into_owned_row();
420 backfill_state.update_progress(vnode, pk_owned, chunk_cardinality);
421 }
422
423 *cur_barrier_snapshot_processed_rows += chunk_cardinality;
424 Ok(chunk)
425 }
426}
427
428impl<S: StateStore> Execute for LocalityProviderExecutor<S> {
429 fn execute(self: Box<Self>) -> BoxedMessageStream {
430 self.execute_inner().boxed()
431 }
432}
433
434impl<S: StateStore> LocalityProviderExecutor<S> {
435 #[try_stream(ok = Message, error = StreamExecutorError)]
436 async fn execute_inner(mut self) {
437 let mut upstream = self.upstream.execute();
438
439 let first_barrier = expect_first_barrier(&mut upstream).await?;
441 let first_epoch = first_barrier.epoch;
442
443 yield Message::Barrier(first_barrier);
445
446 let mut state_table = self.state_table;
447 let mut progress_table = self.progress_table;
448
449 state_table.init_epoch(first_epoch).await?;
451 progress_table.init_epoch(first_epoch).await?;
452
453 let mut backfill_state = Self::load_backfill_state(&progress_table).await?;
455
456 let pk_indices = state_table.pk_indices().iter().cloned().collect_vec();
458
459 let need_backfill = !backfill_state.is_completed();
460 let mut report_finished_on_first_barrier = !need_backfill;
461
462 let need_buffering = backfill_state
463 .per_vnode
464 .values()
465 .all(|progress| matches!(progress, LocalityBackfillProgress::NotStarted));
466 if need_buffering {
468 let mut start_backfill = false;
470
471 #[for_await]
472 for msg in upstream.by_ref() {
473 let msg = msg?;
474
475 match msg {
476 Message::Watermark(_) => {
477 }
479 Message::Chunk(chunk) => {
480 state_table.write_chunk(chunk);
481 state_table.try_flush().await?;
482 }
483 Message::Barrier(barrier) => {
484 let epoch = barrier.epoch;
485
486 if let Some(mutation) = barrier.mutation.as_deref() {
488 use crate::executor::Mutation;
489 if let Mutation::StartFragmentBackfill { fragment_ids } = mutation
490 && fragment_ids.contains(&self.fragment_id)
491 {
492 tracing::info!(
493 "Start backfill of locality provider with fragment id: {:?}",
494 &self.fragment_id
495 );
496 start_backfill = true;
497 }
498 }
499
500 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
502 state_table
503 .commit_assert_no_update_vnode_bitmap(epoch)
504 .await?;
505 progress_table
506 .commit_assert_no_update_vnode_bitmap(epoch)
507 .await?;
508
509 yield Message::Barrier(barrier);
510
511 if start_backfill {
513 break;
514 }
515 }
516 }
517 }
518 }
519
520 if need_backfill {
547 let mut upstream_chunk_buffer: Vec<StreamChunk> = vec![];
548
549 let metrics = self
550 .metrics
551 .new_backfill_metrics(state_table.table_id(), self.actor_id);
552
553 let snapshot_data_types = self.input_schema.data_types();
555 let mut builders: Builders = state_table
556 .vnodes()
557 .iter_vnodes()
558 .map(|vnode| {
559 let builder = create_builder(
560 RateLimit::Disabled,
561 self.chunk_size,
562 snapshot_data_types.clone(),
563 );
564 (vnode, builder)
565 })
566 .collect();
567
568 let snapshot_reader = state_table.flushed_snapshot_reader();
569 let snapshot_stream =
570 Self::make_snapshot_stream(snapshot_reader.clone(), backfill_state.clone());
571 pin_mut!(snapshot_stream);
572
573 'backfill_loop: loop {
574 let mut cur_barrier_snapshot_processed_rows: u64 = 0;
575 let mut cur_barrier_upstream_processed_rows: u64 = 0;
576
577 let barrier = loop {
580 let upstream_next = upstream.next();
581 let mut snapshot_stream_ref = snapshot_stream.as_mut();
582 let snapshot_next = snapshot_stream_ref.next();
583 pin_mut!(upstream_next);
584 pin_mut!(snapshot_next);
585
586 match select(upstream_next, snapshot_next).await {
587 FutureEither::Left((msg, _)) => match msg.transpose()? {
588 Some(Message::Barrier(barrier)) => {
589 break barrier;
591 }
592 Some(Message::Chunk(chunk)) => {
593 upstream_chunk_buffer.push(chunk.compact_vis());
595 }
596 Some(Message::Watermark(_)) => {
597 }
599 None => {
600 return Err(anyhow::anyhow!(
601 "locality provider upstream ended unexpectedly during backfill"
602 )
603 .into());
604 }
605 },
606 FutureEither::Right((msg, _)) => match msg.transpose()? {
607 Some((vnode, row)) => {
608 let builder = builders.get_mut(&vnode).unwrap();
610 if let Some(data_chunk) = builder.append_one_row(row) {
611 let chunk = Self::handle_snapshot_chunk(
613 data_chunk,
614 vnode,
615 &pk_indices,
616 &mut backfill_state,
617 &mut cur_barrier_snapshot_processed_rows,
618 )?;
619 yield Message::Chunk(chunk);
620 }
621 }
624 None => {
625 for (vnode, builder) in &mut builders {
628 if let Some(data_chunk) = builder.consume_all() {
629 let chunk = Self::handle_snapshot_chunk(
630 data_chunk,
631 *vnode,
632 &pk_indices,
633 &mut backfill_state,
634 &mut cur_barrier_snapshot_processed_rows,
635 )?;
636 yield Message::Chunk(chunk);
637 }
638 }
639
640 for chunk in upstream_chunk_buffer.drain(..) {
642 let chunk_cardinality = chunk.cardinality() as u64;
643 cur_barrier_upstream_processed_rows += chunk_cardinality;
644 yield Message::Chunk(chunk);
645 }
646 metrics
647 .backfill_snapshot_read_row_count
648 .inc_by(cur_barrier_snapshot_processed_rows);
649 metrics
650 .backfill_upstream_output_row_count
651 .inc_by(cur_barrier_upstream_processed_rows);
652 break 'backfill_loop;
653 }
654 },
655 }
656 };
657
658 for (vnode, builder) in &mut builders {
660 if let Some(data_chunk) = builder.consume_all() {
661 let chunk = Self::handle_snapshot_chunk(
662 data_chunk,
663 *vnode,
664 &pk_indices,
665 &mut backfill_state,
666 &mut cur_barrier_snapshot_processed_rows,
667 )?;
668 yield Message::Chunk(chunk);
669 }
670 }
671
672 let should_refresh_snapshot = !upstream_chunk_buffer.is_empty();
674 for chunk in upstream_chunk_buffer.drain(..) {
675 cur_barrier_upstream_processed_rows += chunk.cardinality() as u64;
676
677 if backfill_state.has_progress() {
679 let marked_chunk =
680 Self::mark_chunk(chunk.clone(), &backfill_state, &state_table)?;
681 yield Message::Chunk(marked_chunk);
682 }
683
684 state_table.write_chunk(chunk);
687 }
688
689 let barrier_epoch = barrier.epoch;
690 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
691 state_table
692 .commit_assert_no_update_vnode_bitmap(barrier_epoch)
693 .await?;
694
695 let total_snapshot_processed_rows: u64 = backfill_state
698 .vnodes()
699 .map(|(_, progress)| match *progress {
700 LocalityBackfillProgress::InProgress { processed_rows, .. } => {
701 processed_rows
702 }
703 LocalityBackfillProgress::Completed { total_rows, .. } => total_rows,
704 LocalityBackfillProgress::NotStarted => 0,
705 })
706 .sum();
707
708 self.progress.update_with_buffered_rows(
709 barrier.epoch,
710 barrier.epoch.curr, total_snapshot_processed_rows,
712 0,
713 );
714
715 Self::persist_backfill_state(&mut progress_table, &backfill_state).await?;
717 progress_table
718 .commit_assert_no_update_vnode_bitmap(barrier_epoch)
719 .await?;
720
721 metrics
722 .backfill_snapshot_read_row_count
723 .inc_by(cur_barrier_snapshot_processed_rows);
724 metrics
725 .backfill_upstream_output_row_count
726 .inc_by(cur_barrier_upstream_processed_rows);
727
728 yield Message::Barrier(barrier);
729
730 if should_refresh_snapshot {
731 snapshot_stream.set(Self::make_snapshot_stream(
732 snapshot_reader.clone(),
733 backfill_state.clone(),
734 ));
735 }
736 }
737 }
738
739 tracing::debug!("Locality provider backfill finished, forwarding upstream directly");
740
741 if need_backfill && !backfill_state.is_completed() {
743 while let Some(Ok(msg)) = upstream.next().await {
744 match msg {
745 Message::Barrier(barrier) => {
746 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
747
748 state_table
750 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
751 .await?;
752
753 for vnode in state_table.vnodes().iter_vnodes() {
755 backfill_state.finish_vnode(vnode, pk_indices.len());
756 }
757
758 let total_snapshot_processed_rows: u64 = backfill_state
760 .vnodes()
761 .map(|(_, progress)| match *progress {
762 LocalityBackfillProgress::Completed { total_rows, .. } => {
763 total_rows
764 }
765 LocalityBackfillProgress::InProgress { processed_rows, .. } => {
766 processed_rows
767 }
768 LocalityBackfillProgress::NotStarted => 0,
769 })
770 .sum();
771
772 self.progress.finish_with_buffered_rows(
775 barrier.epoch,
776 total_snapshot_processed_rows,
777 total_snapshot_processed_rows,
778 );
779
780 Self::persist_backfill_state(&mut progress_table, &backfill_state).await?;
782 progress_table
783 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
784 .await?;
785
786 yield Message::Barrier(barrier);
787 break; }
789 Message::Chunk(chunk) => {
790 yield Message::Chunk(chunk);
792 }
793 Message::Watermark(watermark) => {
794 yield Message::Watermark(watermark);
796 }
797 }
798 }
799 }
800
801 #[for_await]
803 for msg in upstream {
804 let msg = msg?;
805
806 match msg {
807 Message::Barrier(barrier) => {
808 barrier.assume_no_update_vnode_bitmap(self.actor_id)?;
809
810 state_table
812 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
813 .await?;
814 progress_table
815 .commit_assert_no_update_vnode_bitmap(barrier.epoch)
816 .await?;
817 if report_finished_on_first_barrier {
818 self.progress.finish_with_buffered_rows(
820 barrier.epoch,
821 backfill_state.total_snapshot_rows,
822 backfill_state.total_snapshot_rows,
823 );
824 report_finished_on_first_barrier = false;
825 }
826 yield Message::Barrier(barrier);
827 }
828 _ => {
829 yield msg;
831 }
832 }
833 }
834 }
835}