1use std::collections::VecDeque;
16use std::future::{Future, pending};
17use std::pin::Pin;
18use std::time::Duration;
19
20use anyhow::anyhow;
21use futures::future::{Either, select};
22use pin_project::pin_project;
23use risingwave_common::bitmap::Bitmap;
24use risingwave_common::must_match;
25use risingwave_pb::stream_plan;
26use risingwave_storage::StateStore;
27use risingwave_storage::store::StateStoreRead;
28use rw_futures_util::drop_either_future;
29use tokio::sync::mpsc::UnboundedReceiver;
30
31use super::{DispatchExecutor, DispatchExecutorInner, dispatch_message_batch};
32use crate::common::log_store_impl::kv_log_store::reader::LogStoreReadStateStreamRangeStart;
33use crate::common::log_store_impl::kv_log_store::serde::LogStoreRowSerde;
34use crate::common::log_store_impl::kv_log_store::state::LogStoreReadState;
35use crate::common::log_store_impl::kv_log_store::{
36 FIRST_SEQ_ID, KV_LOG_STORE_V2_INFO, LogStoreVnodeProgress,
37};
38use crate::executor::prelude::*;
39use crate::executor::sync_kv_log_store::{
40 ReadFuture, SyncKvLogStoreContext, SyncedKvLogStoreExecutor, SyncedLogStoreBuffer, WriteFuture,
41 WriteFutureEvent,
42};
43use crate::executor::{MessageBatch, StreamConsumer, SyncedKvLogStoreMetrics};
44use crate::task::NewOutputRequest;
45
46pub struct SyncLogStoreDispatchExecutor<S: StateStore> {
52 pub(super) input: Executor,
53 pub(super) inner: DispatchExecutorInner,
54 pub(super) log_store_context: SyncKvLogStoreContext<S>,
55}
56
57impl<S: StateStore> SyncLogStoreDispatchExecutor<S> {
58 pub(crate) async fn new(
59 input: Executor,
60 new_output_request_rx: UnboundedReceiver<(ActorId, NewOutputRequest)>,
61 dispatchers: Vec<stream_plan::Dispatcher>,
62 actor_context: &ActorContextRef,
63 sync: &stream_plan::SyncLogStoreNode,
64 vnode_bitmap: Bitmap,
65 state_store: S,
66 ) -> StreamResult<Self> {
67 let chunk_size = actor_context.config.developer.chunk_size;
68 let fragment_id = actor_context.fragment_id;
69 let log_store_metrics = SyncedKvLogStoreMetrics::new(
70 &actor_context.streaming_metrics,
71 actor_context.id,
72 fragment_id,
73 "sync_log_store_dispatch",
74 "sync_log_store_dispatch",
75 );
76
77 let table = sync
78 .log_store_table
79 .as_ref()
80 .ok_or_else(|| anyhow!("missing log_store_table in SyncLogStoreNode"))?;
81
82 let pause_duration_ms = actor_context
83 .config
84 .developer
85 .sync_log_store_pause_duration_ms;
86 let max_buffer_size = actor_context.config.developer.sync_log_store_buffer_size;
87
88 let serde = LogStoreRowSerde::new(table, vnode_bitmap.into(), &KV_LOG_STORE_V2_INFO);
89 let log_store_context = SyncKvLogStoreContext {
90 table_id: table.id,
91 fragment_id,
92 serde,
93 state_store,
94 max_buffer_size,
95 pause_duration_ms: Duration::from_millis(pause_duration_ms as _),
96 aligned: sync.aligned,
97 chunk_size,
98 metrics: log_store_metrics,
99 };
100
101 Self::new_with_log_store_context(
102 input,
103 new_output_request_rx,
104 dispatchers,
105 actor_context,
106 log_store_context,
107 )
108 .await
109 }
110
111 async fn new_with_log_store_context(
112 input: Executor,
113 new_output_request_rx: UnboundedReceiver<(ActorId, NewOutputRequest)>,
114 dispatchers: Vec<stream_plan::Dispatcher>,
115 actor_context: &ActorContextRef,
116 log_store_context: SyncKvLogStoreContext<S>,
117 ) -> StreamResult<Self> {
118 let DispatchExecutor { input, inner } =
119 DispatchExecutor::new(input, new_output_request_rx, dispatchers, actor_context).await?;
120
121 tracing::info!(
122 actor_id = %actor_context.id,
123 "synclogstore dispatch executor info"
124 );
125
126 Ok(Self {
127 input,
128 inner,
129 log_store_context,
130 })
131 }
132}
133
134type DispatchingFuture =
135 impl Future<Output = (DispatchExecutorInner, StreamResult<Option<Barrier>>)> + 'static;
136
137#[define_opaque(DispatchingFuture)]
138fn dispatching_future(mut inner: DispatchExecutorInner, message: Message) -> DispatchingFuture {
139 async move {
140 let batch: MessageBatch = message.into();
141 let r = dispatch_message_batch(&mut inner, batch)
142 .await
143 .map(|barrier_batch| {
144 barrier_batch.map(|mut barrier_batch| {
145 debug_assert_eq!(barrier_batch.len(), 1);
146 barrier_batch
147 .pop()
148 .expect("barrier batch should contain one barrier")
149 })
150 });
151 (inner, r)
152 }
153}
154
155#[pin_project(project = ConsumerFutureProj, project_replace = ConsumerFutureProjReplace)]
158enum ConsumerFuture {
159 ReadingChunk { inner: DispatchExecutorInner },
162 Dispatching {
165 #[pin]
166 future: DispatchingFuture,
167 barrier_queue: VecDeque<Message>,
168 },
169 PlaceHolder,
171}
172
173enum ConsumerFutureEvent {
174 BarrierDispatched(Barrier),
175 CleanStateReached,
176}
177
178impl ConsumerFuture {
179 fn dispatch(inner: DispatchExecutorInner, message: Message) -> Self {
180 tracing::trace!("consumer_future: dispatching future created");
181 Self::Dispatching {
182 future: dispatching_future(inner, message),
183 barrier_queue: VecDeque::new(),
184 }
185 }
186
187 fn read_chunk(inner: DispatchExecutorInner) -> Self {
188 tracing::trace!("consumer_future: reading chunk future created");
189 Self::ReadingChunk { inner }
190 }
191
192 fn push_barrier(mut self: Pin<&mut Self>, barrier: Barrier) {
193 let message = Message::Barrier(barrier);
194 match self.as_mut().project() {
195 ConsumerFutureProj::ReadingChunk { .. } => {
196 let inner = must_match!(
197 self.as_mut().project_replace(ConsumerFuture::PlaceHolder),
198 ConsumerFutureProjReplace::ReadingChunk { inner } => inner
199 );
200 self.set(Self::dispatch(inner, message));
201 }
202 ConsumerFutureProj::Dispatching { barrier_queue, .. } => {
203 barrier_queue.push_front(message);
204 }
205 ConsumerFutureProj::PlaceHolder => {
206 unreachable!("ConsumerFuture::PlaceHolder should be handled!")
207 }
208 }
209 }
210
211 #[expect(clippy::too_many_arguments)]
212 async fn next_event<S: StateStoreRead>(
213 mut self: Pin<&mut Self>,
214 read_future: &mut ReadFuture<S>,
215 read_paused: bool,
216 clean_state: &mut bool,
217 progress: &mut LogStoreVnodeProgress,
218 read_state: &LogStoreReadState<S>,
219 buffer: &mut SyncedLogStoreBuffer,
220 metrics: &SyncedKvLogStoreMetrics,
221 ) -> StreamResult<ConsumerFutureEvent> {
222 loop {
223 match self.as_mut().project() {
224 ConsumerFutureProj::ReadingChunk { .. } => {
225 if read_paused {
226 pending().await
227 }
228
229 let message = read_future
230 .next_message(progress, read_state, buffer, metrics)
231 .await?;
232 if let Message::Chunk(chunk) = &message {
233 metrics.total_read_count.inc_by(chunk.cardinality() as _);
234 }
235
236 let clean_state_reached =
237 read_future.mark_clean_state(clean_state, buffer, metrics);
238 let inner = must_match!(
239 self.as_mut().project_replace(ConsumerFuture::PlaceHolder),
240 ConsumerFutureProjReplace::ReadingChunk { inner } => inner
241 );
242 self.set(Self::dispatch(inner, message));
243
244 if clean_state_reached {
245 return Ok(ConsumerFutureEvent::CleanStateReached);
246 }
247 continue;
248 }
249 ConsumerFutureProj::Dispatching {
250 future,
251 barrier_queue,
252 } => {
253 let (inner, result) = future.await;
254 let barrier = result?;
255
256 if let Some(next_barrier) = barrier_queue.pop_back() {
257 tracing::trace!("consumer_future: dispatching future created");
258 let ConsumerFutureProj::Dispatching { mut future, .. } =
259 self.as_mut().project()
260 else {
261 unreachable!("ConsumerFuture::ReadingChunk should be handled!")
262 };
263 future.set(dispatching_future(inner, next_barrier));
264 } else {
265 self.set(Self::read_chunk(inner));
266 }
267
268 if let Some(barrier) = barrier {
269 return Ok(ConsumerFutureEvent::BarrierDispatched(barrier));
270 }
271 }
272 ConsumerFutureProj::PlaceHolder => {
273 unreachable!("ConsumerFuture::PlaceHolder should be handled!")
274 }
275 }
276 }
277 }
278}
279
280impl<S: StateStore> StreamConsumer for SyncLogStoreDispatchExecutor<S> {
281 type BarrierStream = impl Stream<Item = StreamResult<Barrier>> + Send;
282
283 fn execute(mut self: Box<Self>) -> Self::BarrierStream {
284 #[try_stream]
285 async move {
286 let actor_id = self.inner.actor_id;
287 let log_store_config = self.log_store_context;
288
289 let mut input = self.input.execute();
290
291 let first_barrier = expect_first_barrier(&mut input).await?;
292 let first_write_epoch = first_barrier.epoch;
293
294 let first_barrier_batch = dispatch_message_batch(
296 &mut self.inner,
297 Message::Barrier(first_barrier.clone()).into(),
298 )
299 .await?;
300 debug_assert_eq!(
301 first_barrier_batch
302 .as_ref()
303 .map(|barrier_batch| barrier_batch.len()),
304 Some(1)
305 );
306 yield first_barrier.clone();
307
308 let (read_state, initial_write_state) =
309 SyncedKvLogStoreExecutor::<S>::init_local_log_store_state(
310 &log_store_config,
311 first_write_epoch,
312 )
313 .await?;
314
315 let initial_write_epoch = first_write_epoch;
316 let mut pause_stream = first_barrier.is_pause_on_startup();
317
318 if log_store_config.aligned {
319 let aligned_stream = SyncedKvLogStoreExecutor::<S>::aligned_message_stream(
320 actor_id,
321 input,
322 read_state,
323 initial_write_state,
324 log_store_config.metrics.clone(),
325 initial_write_epoch,
326 );
327
328 #[for_await]
329 for message in aligned_stream {
330 if let Some(barrier_batch) =
331 dispatch_message_batch(&mut self.inner, message?.into()).await?
332 {
333 debug_assert_eq!(barrier_batch.len(), 1);
335 for barrier in barrier_batch {
336 yield barrier;
337 }
338 }
339 }
340 return Ok(());
341 }
342
343 let mut seq_id = FIRST_SEQ_ID;
344 let mut buffer = SyncedLogStoreBuffer::new(
345 log_store_config.max_buffer_size,
346 log_store_config.chunk_size,
347 &log_store_config.metrics,
348 );
349
350 let log_store_stream = read_state
351 .read_persisted_log_store(
352 log_store_config.metrics.persistent_log_read_metrics.clone(),
353 initial_write_epoch.curr,
354 LogStoreReadStateStreamRangeStart::Unbounded,
355 )
356 .await?;
357
358 let mut log_store_stream = tokio_stream::StreamExt::peekable(log_store_stream);
359 let mut clean_state = log_store_stream.peek().await.is_none();
360 tracing::trace!(?clean_state);
361
362 let mut progress = LogStoreVnodeProgress::None;
363 let mut read_future_state = ReadFuture::ReadingPersistedStream(log_store_stream);
364 let consumer_future_state = ConsumerFuture::ReadingChunk { inner: self.inner };
365 pin_mut!(consumer_future_state);
366
367 let mut write_future_state =
368 WriteFuture::receive_from_upstream(input, initial_write_state);
369 let mut end_of_stream = false;
370
371 loop {
372 let select_result = {
373 let consumer_future = async {
374 consumer_future_state
375 .as_mut()
376 .next_event(
377 &mut read_future_state,
378 pause_stream,
379 &mut clean_state,
380 &mut progress,
381 &read_state,
382 &mut buffer,
383 &log_store_config.metrics,
384 )
385 .await
386 };
387 pin_mut!(consumer_future);
388 let write_future = async {
389 if end_of_stream {
390 pending().await
391 } else {
392 write_future_state
393 .next_event(&log_store_config.metrics)
394 .await
395 }
396 };
397 pin_mut!(write_future);
398 let output = select(write_future, consumer_future).await;
399 drop_either_future(output)
400 };
401
402 match select_result {
403 Either::Left(_write_result) => {
404 drop(write_future_state);
405 let (stream, mut write_state, either) = _write_result?;
406 match either {
407 WriteFutureEvent::UpstreamMessageReceived(msg) => match msg {
408 Message::Chunk(chunk) => {
409 let (new_seq_id, next_write_future) =
410 SyncedKvLogStoreExecutor::<S>::process_upstream_chunk(
411 seq_id,
412 stream,
413 write_state,
414 chunk,
415 &mut buffer,
416 );
417 seq_id = new_seq_id;
418 write_future_state = next_write_future;
419 }
420 Message::Barrier(barrier) => {
421 if clean_state
422 && barrier.kind.is_checkpoint()
423 && !buffer.is_empty()
424 {
425 write_future_state = WriteFuture::paused(
426 log_store_config.pause_duration_ms,
427 barrier,
428 stream,
429 write_state,
430 );
431 clean_state = false;
432 log_store_config.metrics.unclean_state.inc();
433 } else {
434 SyncedKvLogStoreExecutor::<S>::apply_pause_resume_mutation(
435 &barrier,
436 &mut pause_stream,
437 );
438 let write_state_post_write_barrier =
439 SyncedKvLogStoreExecutor::<S>::write_barrier(
440 actor_id,
441 &mut write_state,
442 barrier.clone(),
443 &log_store_config.metrics,
444 progress.take(),
445 &mut buffer,
446 )
447 .await?;
448 seq_id = FIRST_SEQ_ID;
449 barrier.assume_no_update_vnode_bitmap(actor_id)?;
450
451 write_state_post_write_barrier
452 .post_yield_barrier(None)
453 .await?;
454
455 let is_stop_barrier = barrier.is_stop(actor_id);
456 if is_stop_barrier {
457 end_of_stream = true;
460 write_future_state = WriteFuture::Empty;
461 } else {
462 write_future_state = WriteFuture::receive_from_upstream(
463 stream,
464 write_state,
465 );
466 }
467 consumer_future_state.as_mut().push_barrier(barrier);
468 }
469 }
470 Message::Watermark(watermark) => {
471 buffer.add_watermark(write_state.epoch().curr, watermark);
472 write_future_state =
473 WriteFuture::receive_from_upstream(stream, write_state);
474 }
475 },
476 WriteFutureEvent::ChunkFlushed(info) => {
477 write_future_state =
478 SyncedKvLogStoreExecutor::<S>::process_flushed_chunk(
479 stream,
480 write_state,
481 info,
482 &mut buffer,
483 &log_store_config.metrics,
484 );
485 }
486 }
487 }
488 Either::Right(consumer_result) => {
489 let event = consumer_result?;
490 match event {
491 ConsumerFutureEvent::CleanStateReached => {
492 if let WriteFuture::Paused { sleep_future, .. } =
493 &mut write_future_state
494 {
495 assert!(buffer.has_available_capacity());
496 *sleep_future = None;
497 }
498 }
499 ConsumerFutureEvent::BarrierDispatched(barrier) => {
500 yield barrier;
501 }
502 }
503 }
504 }
505 }
506 }
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use std::sync::Arc;
513
514 use futures::StreamExt;
515 use risingwave_common::array::{StreamChunk, StreamChunkTestExt};
516 use risingwave_common::bitmap::Bitmap;
517 use risingwave_common::catalog::{Field, Schema};
518 use risingwave_common::hash::VirtualNode;
519 use risingwave_common::types::DataType;
520 use risingwave_common::util::epoch::test_epoch;
521 use risingwave_pb::stream_plan::{DispatcherType, PbDispatchOutputMapping};
522 use risingwave_storage::memory::MemoryStateStore;
523 use tokio::sync::mpsc::unbounded_channel;
524 use tokio::time::{Duration, timeout};
525
526 use super::*;
527 use crate::assert_stream_chunk_eq;
528 use crate::common::log_store_impl::kv_log_store::KV_LOG_STORE_V2_INFO;
529 use crate::common::log_store_impl::kv_log_store::serde::LogStoreRowSerde;
530 use crate::common::log_store_impl::kv_log_store::test_utils::{
531 check_stream_chunk_eq, gen_test_log_store_table,
532 };
533 use crate::executor::exchange::permit::channel_for_test;
534 use crate::executor::test_utils::MockSource;
535 use crate::executor::{ActorContext, BarrierInner as Barrier};
536 use crate::task::ActorId;
537
538 const ACTOR_ID: u32 = 4242;
539 const DOWNSTREAM_ACTOR_ID: u32 = 5252;
540
541 fn init_logger() {
542 let _ = tracing_subscriber::fmt()
543 .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
544 .with_ansi(false)
545 .try_init();
546 }
547
548 async fn run_barrier_chunk_ordering_test(aligned: bool) {
549 init_logger();
550
551 let actor_id = ActorId::new(ACTOR_ID);
552 let downstream_actor = ActorId::new(DOWNSTREAM_ACTOR_ID);
553 let (new_output_request_tx, new_output_request_rx) = unbounded_channel();
554 let (down_tx, mut down_rx) = channel_for_test();
555 new_output_request_tx
556 .send((downstream_actor, NewOutputRequest::Local(down_tx)))
557 .unwrap();
558
559 let barrier1 = Barrier::new_test_barrier(test_epoch(1));
560 let barrier2 = Barrier::new_test_barrier(test_epoch(2));
561 let chunk_1 = StreamChunk::from_pretty(
562 " I T
563 + 5 10
564 + 6 10
565 + 8 10
566 + 9 10
567 + 10 11",
568 );
569 let chunk_2 = StreamChunk::from_pretty(
570 " I T
571 - 5 10
572 - 6 10
573 - 8 10
574 U- 10 11
575 U+ 10 10",
576 );
577 let dispatcher = stream_plan::Dispatcher {
578 r#type: DispatcherType::Simple as _,
579 dispatcher_id: 7.into(),
580 downstream_actor_id: vec![DOWNSTREAM_ACTOR_ID.into()],
581 output_mapping: PbDispatchOutputMapping::identical(2).into(),
582 ..Default::default()
583 };
584 let pk_info = &KV_LOG_STORE_V2_INFO;
585 let table = gen_test_log_store_table(pk_info);
586 let vnodes = Arc::new(Bitmap::ones(VirtualNode::COUNT_FOR_TEST));
587 let serde = LogStoreRowSerde::new(&table, vnodes, pk_info);
588 let log_store_config = SyncKvLogStoreContext {
589 table_id: table.id,
590 fragment_id: 0.into(),
591 serde,
592 state_store: MemoryStateStore::new(),
593 max_buffer_size: 1024,
594 pause_duration_ms: Duration::from_millis(10),
595 aligned,
596 chunk_size: 1024,
597 metrics: SyncedKvLogStoreMetrics::for_test(),
598 };
599 let (mut input_tx, source) = MockSource::channel();
600 let input = source.into_executor(
601 Schema {
602 fields: vec![
603 Field::unnamed(DataType::Int64),
604 Field::unnamed(DataType::Varchar),
605 ],
606 },
607 vec![0],
608 );
609
610 let executor = SyncLogStoreDispatchExecutor::new_with_log_store_context(
611 input,
612 new_output_request_rx,
613 vec![dispatcher],
614 &ActorContext::for_test(actor_id),
615 log_store_config,
616 )
617 .await
618 .unwrap();
619
620 let (barrier_out_tx, mut barrier_out_rx) = unbounded_channel();
621 let barrier_driver = tokio::spawn(async move {
622 let barrier_stream = Box::new(executor).execute();
623 futures::pin_mut!(barrier_stream);
624 while let Some(item) = barrier_stream.next().await {
625 barrier_out_tx.send(item).ok();
626 }
627 });
628
629 input_tx.send_barrier(barrier1.clone());
630 let observed1 = timeout(Duration::from_secs(1), barrier_out_rx.recv())
631 .await
632 .unwrap()
633 .unwrap()
634 .unwrap();
635 assert_eq!(observed1.epoch.curr, test_epoch(1));
636
637 let msg = timeout(Duration::from_secs(1), down_rx.recv())
638 .await
639 .unwrap()
640 .expect("downstream should receive barrier(1)");
641 let barriers = msg.as_barrier_batch().unwrap();
642 assert_eq!(barriers.len(), 1);
643 assert_eq!(barriers[0].epoch.curr, test_epoch(1));
644
645 input_tx.push_chunk(chunk_1.clone());
646 input_tx.push_int64_watermark(0, 7);
647 input_tx.push_chunk(chunk_2.clone());
648 let msg = timeout(Duration::from_secs(1), down_rx.recv())
649 .await
650 .unwrap()
651 .expect("downstream should receive chunk(1)");
652 assert_stream_chunk_eq!(msg.as_chunk().unwrap(), chunk_1);
653
654 let msg = timeout(Duration::from_secs(1), down_rx.recv())
655 .await
656 .unwrap()
657 .expect("downstream should receive watermark");
658 assert_eq!(
659 msg.as_watermark().unwrap(),
660 &Watermark::new(0, DataType::Int64, 7_i64.into())
661 );
662
663 let msg = timeout(Duration::from_secs(1), down_rx.recv())
664 .await
665 .unwrap()
666 .expect("downstream should receive chunk(2)");
667 assert_stream_chunk_eq!(msg.as_chunk().unwrap(), chunk_2);
668
669 input_tx.send_barrier(barrier2.clone());
670 let msg = timeout(Duration::from_secs(1), down_rx.recv())
671 .await
672 .unwrap()
673 .expect("downstream should receive barrier(2)");
674 let barriers = msg.as_barrier_batch().unwrap();
675 assert_eq!(barriers.len(), 1);
676 assert_eq!(barriers[0].epoch.curr, test_epoch(2));
677
678 let observed2 = timeout(Duration::from_secs(1), barrier_out_rx.recv())
679 .await
680 .unwrap()
681 .unwrap()
682 .unwrap();
683 assert_eq!(observed2.epoch.curr, test_epoch(2));
684
685 barrier_driver.abort();
686 }
687
688 #[tokio::test]
691 async fn test_barrier_chunk_ordering_in_dispatch() {
692 run_barrier_chunk_ordering_test(false).await;
693 }
694
695 #[tokio::test]
696 async fn test_aligned_barrier_chunk_ordering_in_dispatch() {
697 run_barrier_chunk_ordering_test(true).await;
698 }
699}