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