Skip to main content

risingwave_stream/executor/
sync_kv_log_store.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This contains the synced kv log store implementation.
16//! It's meant to buffer a large number of records emitted from upstream,
17//! to avoid overwhelming the downstream executor.
18//!
19//! The synced kv log store polls two futures:
20//!
21//! 1. Upstream: upstream message source
22//!
23//!   It will write stream messages to the log store buffer. e.g. `Message::Barrier`, `Message::Chunk`, ...
24//!   When writing a stream chunk, if the log store buffer is full, it will:
25//!     a. Flush the buffer to the log store.
26//!     b. Convert the stream chunk into a reference (`LogStoreBufferItem::Flushed`)
27//!       which can read the corresponding chunks in the log store.
28//!       We will compact adjacent references,
29//!       so it can read multiple chunks if there's a build up.
30//!
31//!   On receiving barriers, it will:
32//!     a. Apply truncation to historical data in the logstore.
33//!     b. Flush and checkpoint the logstore data.
34//!
35//! 2. State store + buffer + recently flushed chunks: the storage components of the logstore.
36//!
37//!   It will read all historical data from the logstore first. This can be done just by
38//!   constructing a state store stream, which will read all data until the latest epoch.
39//!   This is a static snapshot of data.
40//!   For any subsequently flushed chunks, we will read them via
41//!   `flushed_chunk_future`. See the next paragraph below.
42//!
43//!   We will next read `flushed_chunk_future` (if there's one pre-existing one), see below for how
44//!   it's constructed, what it is.
45//!
46//!   Finally we will pop the earliest item in the buffer.
47//!   - If it's a chunk yield it.
48//!   - If it's a watermark yield it.
49//!   - If it's a flushed chunk reference (`LogStoreBufferItem::Flushed`),
50//!     we will read the corresponding chunks in the log store.
51//!     This is done by constructing a `flushed_chunk_future` which will read the log store
52//!     using the `seq_id`.
53//!   - Barrier,
54//!     because they are directly propagated from the upstream when polling it.
55//!
56//! TODO(kwannoel):
57//! - [] Add dedicated metrics for sync log store, namespace according to the upstream.
58//! - [] Add tests
59//! - [] Handle paused stream
60
61use std::collections::VecDeque;
62use std::future::pending;
63use std::mem::replace;
64use std::pin::Pin;
65
66use anyhow::anyhow;
67use futures::future::{BoxFuture, Either, select};
68use futures::stream::StreamFuture;
69use futures::{FutureExt, StreamExt, TryStreamExt};
70use futures_async_stream::try_stream;
71use risingwave_common::array::StreamChunk;
72use risingwave_common::bitmap::Bitmap;
73use risingwave_common::catalog::{TableId, TableOption};
74use risingwave_common::must_match;
75use risingwave_common::util::epoch::EpochPair;
76use risingwave_common_estimate_size::EstimateSize;
77use risingwave_connector::sink::log_store::{ChunkId, LogStoreResult};
78use risingwave_pb::id::FragmentId;
79use risingwave_storage::StateStore;
80use risingwave_storage::store::timeout_auto_rebuild::TimeoutAutoRebuildIter;
81use risingwave_storage::store::{
82    LocalStateStore, NewLocalOptions, OpConsistencyLevel, StateStoreRead,
83};
84use rw_futures_util::drop_either_future;
85use tokio::time::{Duration, Instant, Sleep, sleep_until};
86use tokio_stream::adapters::Peekable;
87
88use crate::common::log_store_impl::kv_log_store::buffer::LogStoreBufferItem;
89use crate::common::log_store_impl::kv_log_store::reader::LogStoreReadStateStreamRangeStart;
90use crate::common::log_store_impl::kv_log_store::serde::{
91    KvLogStoreItem, LogStoreItemMergeStream, LogStoreRowSerde,
92};
93use crate::common::log_store_impl::kv_log_store::state::{
94    LogStorePostSealCurrentEpoch, LogStoreReadState, LogStoreStateWriteChunkFuture,
95    LogStoreWriteState, new_log_store_state,
96};
97use crate::common::log_store_impl::kv_log_store::{
98    Epoch, FIRST_SEQ_ID, FlushInfo, LogStoreVnodeProgress, SeqId,
99};
100use crate::executor::prelude::*;
101use crate::executor::sync_kv_log_store::metrics::SyncedKvLogStoreMetrics;
102use crate::executor::{
103    Barrier, BoxedMessageStream, Message, StreamExecutorError, StreamExecutorResult, Watermark,
104};
105
106pub mod metrics {
107    use risingwave_common::id::FragmentId;
108    #[cfg(test)]
109    use risingwave_common::metrics::GaugeAggregation;
110    use risingwave_common::metrics::{LabelGuardedIntCounter, RelabeledAggregatedIntGauge};
111
112    use crate::common::log_store_impl::kv_log_store::KvLogStoreReadMetrics;
113    use crate::executor::monitor::StreamingMetrics;
114    use crate::task::ActorId;
115
116    #[derive(Clone)]
117    pub struct SyncedKvLogStoreMetrics {
118        // state of the log store
119        pub unclean_state: LabelGuardedIntCounter,
120        pub clean_state: LabelGuardedIntCounter,
121        pub wait_next_poll_ns: LabelGuardedIntCounter,
122
123        // Write metrics
124        pub storage_write_count: LabelGuardedIntCounter,
125        pub storage_write_size: LabelGuardedIntCounter,
126        pub pause_duration_ns: LabelGuardedIntCounter,
127
128        // Buffer metrics
129        pub buffer_unconsumed_item_count: RelabeledAggregatedIntGauge,
130        pub buffer_unconsumed_row_count: RelabeledAggregatedIntGauge,
131        pub buffer_unconsumed_epoch_count: RelabeledAggregatedIntGauge,
132        pub buffer_unconsumed_min_epoch: RelabeledAggregatedIntGauge,
133        pub buffer_memory_bytes: RelabeledAggregatedIntGauge,
134        pub buffer_read_count: LabelGuardedIntCounter,
135        pub buffer_read_size: LabelGuardedIntCounter,
136
137        // Read metrics
138        pub total_read_count: LabelGuardedIntCounter,
139        pub total_read_size: LabelGuardedIntCounter,
140        pub persistent_log_read_metrics: KvLogStoreReadMetrics,
141        pub flushed_buffer_read_metrics: KvLogStoreReadMetrics,
142    }
143
144    impl SyncedKvLogStoreMetrics {
145        /// `id`: refers to a unique way to identify the logstore. This can be the sink id,
146        ///       or for joins, it can be the `fragment_id`.
147        /// `name`: refers to the MV / Sink that the log store is associated with.
148        /// `target`: refers to the target of the log store,
149        ///           for instance `MySql` Sink, PG sink, etc...
150        ///           or unaligned join.
151        pub(crate) fn new(
152            metrics: &StreamingMetrics,
153            actor_id: ActorId,
154            fragment_id: FragmentId,
155            name: &str,
156            target: &'static str,
157        ) -> Self {
158            let actor_id_str = actor_id.to_string();
159            let fragment_id_str = fragment_id.to_string();
160            let labels = &[&actor_id_str, target, &fragment_id_str, name];
161
162            let unclean_state = metrics.sync_kv_log_store_state.with_guarded_label_values(&[
163                &actor_id_str,
164                "dirty",
165                target,
166                &fragment_id_str,
167                name,
168            ]);
169            let clean_state = metrics.sync_kv_log_store_state.with_guarded_label_values(&[
170                &actor_id_str,
171                "clean",
172                target,
173                &fragment_id_str,
174                name,
175            ]);
176            let wait_next_poll_ns = metrics
177                .sync_kv_log_store_wait_next_poll_ns
178                .with_guarded_label_values(labels);
179
180            let storage_write_size = metrics
181                .sync_kv_log_store_storage_write_size
182                .with_guarded_label_values(labels);
183            let storage_write_count = metrics
184                .sync_kv_log_store_storage_write_count
185                .with_guarded_label_values(labels);
186            let storage_pause_duration_ns = metrics
187                .sync_kv_log_store_write_pause_duration_ns
188                .with_guarded_label_values(labels);
189
190            let buffer_unconsumed_item_count = metrics
191                .sync_kv_log_store_buffer_unconsumed_item_count
192                .with_guarded_label_values(labels);
193            let buffer_unconsumed_row_count = metrics
194                .sync_kv_log_store_buffer_unconsumed_row_count
195                .with_guarded_label_values(labels);
196            let buffer_unconsumed_epoch_count = metrics
197                .sync_kv_log_store_buffer_unconsumed_epoch_count
198                .with_guarded_label_values(labels);
199            let buffer_unconsumed_min_epoch = metrics
200                .sync_kv_log_store_buffer_unconsumed_min_epoch
201                .with_guarded_label_values(labels);
202            let buffer_memory_bytes = metrics
203                .sync_kv_log_store_buffer_memory_bytes
204                .with_guarded_label_values(labels);
205            let buffer_read_count = metrics
206                .sync_kv_log_store_read_count
207                .with_guarded_label_values(&[
208                    &actor_id_str,
209                    "buffer",
210                    target,
211                    &fragment_id_str,
212                    name,
213                ]);
214
215            let buffer_read_size = metrics
216                .sync_kv_log_store_read_size
217                .with_guarded_label_values(&[
218                    &actor_id_str,
219                    "buffer",
220                    target,
221                    &fragment_id_str,
222                    name,
223                ]);
224
225            let total_read_count = metrics
226                .sync_kv_log_store_read_count
227                .with_guarded_label_values(&[
228                    &actor_id_str,
229                    "total",
230                    target,
231                    &fragment_id_str,
232                    name,
233                ]);
234
235            let total_read_size = metrics
236                .sync_kv_log_store_read_size
237                .with_guarded_label_values(&[
238                    &actor_id_str,
239                    "total",
240                    target,
241                    &fragment_id_str,
242                    name,
243                ]);
244
245            const READ_PERSISTENT_LOG: &str = "persistent_log";
246            const READ_FLUSHED_BUFFER: &str = "flushed_buffer";
247
248            let persistent_log_read_size = metrics
249                .sync_kv_log_store_read_size
250                .with_guarded_label_values(&[
251                    &actor_id_str,
252                    READ_PERSISTENT_LOG,
253                    target,
254                    &fragment_id_str,
255                    name,
256                ]);
257
258            let persistent_log_read_count = metrics
259                .sync_kv_log_store_read_count
260                .with_guarded_label_values(&[
261                    &actor_id_str,
262                    READ_PERSISTENT_LOG,
263                    target,
264                    &fragment_id_str,
265                    name,
266                ]);
267
268            let flushed_buffer_read_size = metrics
269                .sync_kv_log_store_read_size
270                .with_guarded_label_values(&[
271                    &actor_id_str,
272                    READ_FLUSHED_BUFFER,
273                    target,
274                    &fragment_id_str,
275                    name,
276                ]);
277
278            let flushed_buffer_read_count = metrics
279                .sync_kv_log_store_read_count
280                .with_guarded_label_values(&[
281                    &actor_id_str,
282                    READ_FLUSHED_BUFFER,
283                    target,
284                    &fragment_id_str,
285                    name,
286                ]);
287
288            Self {
289                unclean_state,
290                clean_state,
291                wait_next_poll_ns,
292                storage_write_size,
293                storage_write_count,
294                pause_duration_ns: storage_pause_duration_ns,
295                buffer_unconsumed_item_count,
296                buffer_unconsumed_row_count,
297                buffer_unconsumed_epoch_count,
298                buffer_unconsumed_min_epoch,
299                buffer_memory_bytes,
300                buffer_read_count,
301                buffer_read_size,
302                total_read_count,
303                total_read_size,
304                persistent_log_read_metrics: KvLogStoreReadMetrics {
305                    storage_read_size: persistent_log_read_size,
306                    storage_read_count: persistent_log_read_count,
307                },
308                flushed_buffer_read_metrics: KvLogStoreReadMetrics {
309                    storage_read_count: flushed_buffer_read_count,
310                    storage_read_size: flushed_buffer_read_size,
311                },
312            }
313        }
314
315        #[cfg(test)]
316        pub(crate) fn for_test() -> Self {
317            SyncedKvLogStoreMetrics {
318                unclean_state: LabelGuardedIntCounter::test_int_counter::<5>(),
319                clean_state: LabelGuardedIntCounter::test_int_counter::<5>(),
320                wait_next_poll_ns: LabelGuardedIntCounter::test_int_counter::<4>(),
321                storage_write_count: LabelGuardedIntCounter::test_int_counter::<4>(),
322                storage_write_size: LabelGuardedIntCounter::test_int_counter::<4>(),
323                pause_duration_ns: LabelGuardedIntCounter::test_int_counter::<4>(),
324                buffer_unconsumed_item_count: RelabeledAggregatedIntGauge::test_int_gauge::<4>(
325                    GaugeAggregation::Sum,
326                ),
327                buffer_unconsumed_row_count: RelabeledAggregatedIntGauge::test_int_gauge::<4>(
328                    GaugeAggregation::Sum,
329                ),
330                buffer_unconsumed_epoch_count: RelabeledAggregatedIntGauge::test_int_gauge::<4>(
331                    GaugeAggregation::Sum,
332                ),
333                buffer_unconsumed_min_epoch: RelabeledAggregatedIntGauge::test_int_gauge::<4>(
334                    GaugeAggregation::Min,
335                ),
336                buffer_memory_bytes: RelabeledAggregatedIntGauge::test_int_gauge::<4>(
337                    GaugeAggregation::Sum,
338                ),
339                buffer_read_count: LabelGuardedIntCounter::test_int_counter::<5>(),
340                buffer_read_size: LabelGuardedIntCounter::test_int_counter::<5>(),
341                total_read_count: LabelGuardedIntCounter::test_int_counter::<5>(),
342                total_read_size: LabelGuardedIntCounter::test_int_counter::<5>(),
343                persistent_log_read_metrics: KvLogStoreReadMetrics::for_test(),
344                flushed_buffer_read_metrics: KvLogStoreReadMetrics::for_test(),
345            }
346        }
347    }
348}
349
350pub(crate) type ReadFlushedChunkFuture =
351    BoxFuture<'static, LogStoreResult<(ChunkId, StreamChunk, Epoch)>>;
352
353pub struct SyncKvLogStoreContext<S: StateStore> {
354    pub table_id: TableId,
355    pub fragment_id: FragmentId,
356    pub metrics: SyncedKvLogStoreMetrics,
357    pub serde: LogStoreRowSerde,
358    pub state_store: S,
359    pub max_buffer_size: usize,
360    pub chunk_size: usize,
361    pub pause_duration_ms: Duration,
362    pub aligned: bool,
363}
364
365pub struct SyncedKvLogStoreExecutor<S: StateStore> {
366    actor_context: ActorContextRef,
367    upstream: Executor,
368    logstore_context: SyncKvLogStoreContext<S>,
369}
370
371// Stream interface
372impl<S: StateStore> SyncedKvLogStoreExecutor<S> {
373    #[expect(clippy::too_many_arguments)]
374    pub(crate) fn new(
375        actor_context: ActorContextRef,
376        table_id: TableId,
377        metrics: SyncedKvLogStoreMetrics,
378        serde: LogStoreRowSerde,
379        state_store: S,
380        buffer_size: usize,
381        chunk_size: usize,
382        upstream: Executor,
383        pause_duration_ms: Duration,
384        aligned: bool,
385    ) -> Self {
386        let logstore_context = SyncKvLogStoreContext {
387            table_id,
388            fragment_id: actor_context.fragment_id,
389            metrics,
390            serde,
391            state_store,
392            max_buffer_size: buffer_size,
393            chunk_size,
394            pause_duration_ms,
395            aligned,
396        };
397        Self {
398            actor_context,
399            upstream,
400            logstore_context,
401        }
402    }
403}
404
405pub(crate) struct FlushedChunkInfo {
406    epoch: u64,
407    start_seq_id: SeqId,
408    end_seq_id: SeqId,
409    flush_info: FlushInfo,
410    vnode_bitmap: Bitmap,
411}
412
413pub(crate) enum WriteFuture<S: LocalStateStore> {
414    /// We trigger a brief pause to let the `ReadFuture` be polled in the following scenarios:
415    /// - When seeing an upstream data chunk, when the buffer becomes full, and the state is clean.
416    /// - When seeing a checkpoint barrier, when the buffer is not empty, and the state is clean.
417    ///
418    /// On pausing, we will transition to a dirty state.
419    ///
420    /// We trigger resume to let the `ReadFuture` to be polled in the following scenarios:
421    /// - After the pause duration.
422    /// - After the read future consumes a chunk.
423    Paused {
424        start_instant: Instant,
425        sleep_future: Option<Pin<Box<Sleep>>>,
426        barrier: Barrier,
427        stream: BoxedMessageStream,
428        write_state: LogStoreWriteState<S>, // Just used to hold the state
429    },
430    ReceiveFromUpstream {
431        future: StreamFuture<BoxedMessageStream>,
432        write_state: LogStoreWriteState<S>,
433    },
434    FlushingChunk {
435        epoch: u64,
436        start_seq_id: SeqId,
437        end_seq_id: SeqId,
438        future: Pin<Box<LogStoreStateWriteChunkFuture<S>>>,
439        stream: BoxedMessageStream,
440    },
441    Empty,
442}
443
444pub(crate) enum WriteFutureEvent {
445    UpstreamMessageReceived(Message),
446    ChunkFlushed(FlushedChunkInfo),
447}
448
449impl<S: LocalStateStore> WriteFuture<S> {
450    fn flush_chunk(
451        stream: BoxedMessageStream,
452        write_state: LogStoreWriteState<S>,
453        chunk: StreamChunk,
454        epoch: u64,
455        start_seq_id: SeqId,
456        end_seq_id: SeqId,
457    ) -> Self {
458        tracing::trace!(
459            start_seq_id,
460            end_seq_id,
461            epoch,
462            cardinality = chunk.cardinality(),
463            "write_future: flushing chunk"
464        );
465        Self::FlushingChunk {
466            epoch,
467            start_seq_id,
468            end_seq_id,
469            future: Box::pin(write_state.into_write_chunk_future(
470                chunk,
471                epoch,
472                start_seq_id,
473                end_seq_id,
474            )),
475            stream,
476        }
477    }
478
479    pub(crate) fn receive_from_upstream(
480        stream: BoxedMessageStream,
481        write_state: LogStoreWriteState<S>,
482    ) -> Self {
483        Self::ReceiveFromUpstream {
484            future: stream.into_future(),
485            write_state,
486        }
487    }
488
489    pub(crate) fn paused(
490        duration: Duration,
491        barrier: Barrier,
492        stream: BoxedMessageStream,
493        write_state: LogStoreWriteState<S>,
494    ) -> Self {
495        let now = Instant::now();
496        tracing::trace!(?now, ?duration, "write_future_pause");
497        Self::Paused {
498            start_instant: now,
499            sleep_future: Some(Box::pin(sleep_until(now + duration))),
500            barrier,
501            stream,
502            write_state,
503        }
504    }
505
506    pub(crate) async fn next_event(
507        &mut self,
508        metrics: &SyncedKvLogStoreMetrics,
509    ) -> StreamExecutorResult<(BoxedMessageStream, LogStoreWriteState<S>, WriteFutureEvent)> {
510        match self {
511            WriteFuture::Paused {
512                start_instant,
513                sleep_future,
514                ..
515            } => {
516                if let Some(sleep_future) = sleep_future {
517                    sleep_future.await;
518                    metrics
519                        .pause_duration_ns
520                        .inc_by(start_instant.elapsed().as_nanos() as _);
521                    tracing::trace!("resuming write future");
522                }
523                must_match!(replace(self, WriteFuture::Empty), WriteFuture::Paused { stream, write_state, barrier, .. } => {
524                    Ok((stream, write_state, WriteFutureEvent::UpstreamMessageReceived(Message::Barrier(barrier))))
525                })
526            }
527            WriteFuture::ReceiveFromUpstream { future, .. } => {
528                let (opt, stream) = future.await;
529                must_match!(replace(self, WriteFuture::Empty), WriteFuture::ReceiveFromUpstream { write_state, .. } => {
530                    opt
531                    .ok_or_else(|| anyhow!("end of upstream input").into())
532                    .and_then(|result| result.map(|item| {
533                        (stream, write_state, WriteFutureEvent::UpstreamMessageReceived(item))
534                    }))
535                })
536            }
537            WriteFuture::FlushingChunk { future, .. } => {
538                let (write_state, result) = future.await;
539                let result = must_match!(replace(self, WriteFuture::Empty), WriteFuture::FlushingChunk { epoch, start_seq_id, end_seq_id, stream, ..  } => {
540                    result.map(|(flush_info, vnode_bitmap)| {
541                        (stream, write_state, WriteFutureEvent::ChunkFlushed(FlushedChunkInfo {
542                            epoch,
543                            start_seq_id,
544                            end_seq_id,
545                            flush_info,
546                            vnode_bitmap,
547                        }))
548                    })
549                });
550                result.map_err(Into::into)
551            }
552            WriteFuture::Empty => {
553                unreachable!("should not be polled after ready")
554            }
555        }
556    }
557}
558
559pub(crate) type LocalLogStoreReadState<S> =
560    LogStoreReadState<<<S as StateStore>::Local as LocalStateStore>::FlushedSnapshotReader>;
561pub(crate) type LocalLogStoreWriteState<S> = LogStoreWriteState<<S as StateStore>::Local>;
562
563// Stream interface
564impl<S: StateStore> SyncedKvLogStoreExecutor<S> {
565    pub(crate) async fn init_local_log_store_state(
566        context: &SyncKvLogStoreContext<S>,
567        first_write_epoch: EpochPair,
568    ) -> StreamExecutorResult<(LocalLogStoreReadState<S>, LocalLogStoreWriteState<S>)> {
569        let local_state_store = context
570            .state_store
571            .new_local(NewLocalOptions {
572                table_id: context.table_id,
573                fragment_id: context.fragment_id,
574                op_consistency_level: OpConsistencyLevel::Inconsistent,
575                table_option: TableOption {
576                    retention_seconds: None,
577                },
578                is_replicated: false,
579                vnodes: context.serde.vnodes().clone(),
580                upload_on_flush: false,
581            })
582            .await;
583
584        let (read_state, mut initial_write_state) = new_log_store_state(
585            context.table_id,
586            local_state_store,
587            context.serde.clone(),
588            context.chunk_size,
589        );
590        initial_write_state.init(first_write_epoch).await?;
591        Ok((read_state, initial_write_state))
592    }
593
594    #[try_stream(ok = Message, error = StreamExecutorError)]
595    pub(crate) async fn aligned_message_stream(
596        actor_id: ActorId,
597        input: BoxedMessageStream,
598        read_state: LocalLogStoreReadState<S>,
599        mut initial_write_state: LocalLogStoreWriteState<S>,
600        metrics: SyncedKvLogStoreMetrics,
601        initial_write_epoch: EpochPair,
602    ) {
603        tracing::info!("aligned mode");
604        // We want to realign the buffer and the stream.
605        // We just block the upstream input stream,
606        // and wait until the persisted logstore is empty.
607        // Then after that we can consume the input stream.
608        let log_store_stream = read_state
609            .read_persisted_log_store(
610                metrics.persistent_log_read_metrics.clone(),
611                initial_write_epoch.curr,
612                LogStoreReadStateStreamRangeStart::Unbounded,
613            )
614            .await?;
615
616        #[for_await]
617        for message in log_store_stream {
618            let (_epoch, message) = message?;
619            match message {
620                KvLogStoreItem::Barrier { .. } => {
621                    continue;
622                }
623                KvLogStoreItem::StreamChunk { chunk, .. } => {
624                    yield Message::Chunk(chunk);
625                }
626            }
627        }
628
629        let mut realigned_logstore = false;
630
631        #[for_await]
632        for message in input {
633            match message? {
634                Message::Barrier(barrier) => {
635                    let is_checkpoint = barrier.is_checkpoint();
636                    let mut progress = LogStoreVnodeProgress::None;
637                    progress.apply_aligned(read_state.vnodes().clone(), barrier.epoch.prev, None);
638                    // Truncate the logstore.
639                    let post_seal =
640                        initial_write_state.seal_current_epoch(barrier.epoch.curr, progress.take());
641                    barrier.assume_no_update_vnode_bitmap(actor_id)?;
642                    yield Message::Barrier(barrier);
643                    post_seal.post_yield_barrier(None).await?;
644                    if !realigned_logstore && is_checkpoint {
645                        realigned_logstore = true;
646                        tracing::info!("realigned logstore");
647                    }
648                }
649                Message::Chunk(chunk) => {
650                    yield Message::Chunk(chunk);
651                }
652                Message::Watermark(watermark) => {
653                    yield Message::Watermark(watermark);
654                }
655            }
656        }
657    }
658
659    pub(crate) fn apply_pause_resume_mutation(barrier: &Barrier, pause_stream: &mut bool) {
660        if let Some(mutation) = barrier.mutation.as_deref() {
661            match mutation {
662                Mutation::Pause => {
663                    *pause_stream = true;
664                }
665                Mutation::Resume => {
666                    *pause_stream = false;
667                }
668                _ => {}
669            }
670        }
671    }
672
673    pub(crate) fn process_upstream_chunk(
674        seq_id: SeqId,
675        stream: BoxedMessageStream,
676        write_state: LogStoreWriteState<S::Local>,
677        chunk: StreamChunk,
678        buffer: &mut SyncedLogStoreBuffer,
679    ) -> (SeqId, WriteFuture<S::Local>) {
680        let cardinality = chunk.cardinality();
681        if cardinality == 0 {
682            tracing::warn!(
683                epoch = write_state.epoch().curr,
684                "received empty chunk (cardinality=0), skipping"
685            );
686            return (
687                seq_id,
688                WriteFuture::receive_from_upstream(stream, write_state),
689            );
690        }
691
692        let start_seq_id = seq_id;
693        let new_seq_id = seq_id + cardinality as SeqId;
694        let end_seq_id = new_seq_id - 1;
695        let epoch = write_state.epoch().curr;
696        tracing::trace!(
697            start_seq_id,
698            end_seq_id,
699            new_seq_id,
700            epoch,
701            cardinality,
702            "received chunk"
703        );
704        let next_write_future = if let Some(chunk_to_flush) =
705            buffer.add_or_flush_chunk(start_seq_id, end_seq_id, chunk, epoch)
706        {
707            WriteFuture::flush_chunk(
708                stream,
709                write_state,
710                chunk_to_flush,
711                epoch,
712                start_seq_id,
713                end_seq_id,
714            )
715        } else {
716            WriteFuture::receive_from_upstream(stream, write_state)
717        };
718        (new_seq_id, next_write_future)
719    }
720
721    pub(crate) fn process_flushed_chunk(
722        stream: BoxedMessageStream,
723        write_state: LogStoreWriteState<S::Local>,
724        info: FlushedChunkInfo,
725        buffer: &mut SyncedLogStoreBuffer,
726        metrics: &SyncedKvLogStoreMetrics,
727    ) -> WriteFuture<S::Local> {
728        buffer.add_flushed_item_to_buffer(
729            info.start_seq_id,
730            info.end_seq_id,
731            info.vnode_bitmap,
732            info.epoch,
733        );
734        metrics
735            .storage_write_count
736            .inc_by(info.flush_info.flush_count as _);
737        metrics
738            .storage_write_size
739            .inc_by(info.flush_info.flush_size as _);
740        WriteFuture::receive_from_upstream(stream, write_state)
741    }
742
743    #[try_stream(ok= Message, error = StreamExecutorError)]
744    pub async fn execute_monitored(self) {
745        let wait_next_poll_ns = self.logstore_context.metrics.wait_next_poll_ns.clone();
746        #[for_await]
747        for message in self.execute_inner() {
748            let current_time = Instant::now();
749            yield message?;
750            wait_next_poll_ns.inc_by(current_time.elapsed().as_nanos() as _);
751        }
752    }
753
754    #[try_stream(ok = Message, error = StreamExecutorError)]
755    async fn execute_inner(self) {
756        let mut input = self.upstream.execute();
757
758        // init first epoch + local state store
759        let first_barrier = expect_first_barrier(&mut input).await?;
760        let first_write_epoch = first_barrier.epoch;
761        yield Message::Barrier(first_barrier.clone());
762
763        let (read_state, initial_write_state) =
764            Self::init_local_log_store_state(&self.logstore_context, first_write_epoch).await?;
765
766        let mut pause_stream = first_barrier.is_pause_on_startup();
767        let initial_write_epoch = first_write_epoch;
768
769        if self.logstore_context.aligned {
770            let aligned_stream = Self::aligned_message_stream(
771                self.actor_context.id,
772                input,
773                read_state,
774                initial_write_state,
775                self.logstore_context.metrics.clone(),
776                initial_write_epoch,
777            );
778            #[for_await]
779            for message in aligned_stream {
780                yield message?;
781            }
782            return Ok(());
783        }
784
785        let mut seq_id = FIRST_SEQ_ID;
786        let mut buffer = SyncedLogStoreBuffer::new(
787            self.logstore_context.max_buffer_size,
788            self.logstore_context.chunk_size,
789            &self.logstore_context.metrics,
790        );
791
792        let log_store_stream = read_state
793            .read_persisted_log_store(
794                self.logstore_context
795                    .metrics
796                    .persistent_log_read_metrics
797                    .clone(),
798                initial_write_epoch.curr,
799                LogStoreReadStateStreamRangeStart::Unbounded,
800            )
801            .await?;
802
803        let mut log_store_stream = tokio_stream::StreamExt::peekable(log_store_stream);
804        let mut clean_state = log_store_stream.peek().await.is_none();
805        tracing::trace!(?clean_state);
806
807        let mut read_future_state = ReadFuture::ReadingPersistedStream(log_store_stream);
808
809        let mut write_future_state = WriteFuture::receive_from_upstream(input, initial_write_state);
810
811        let mut progress = LogStoreVnodeProgress::None;
812
813        loop {
814            let select_result = {
815                let read_future = async {
816                    if pause_stream {
817                        pending().await
818                    } else {
819                        read_future_state
820                            .next_message(
821                                &mut progress,
822                                &read_state,
823                                &mut buffer,
824                                &self.logstore_context.metrics,
825                            )
826                            .await
827                    }
828                };
829                pin_mut!(read_future);
830                let write_future = write_future_state.next_event(&self.logstore_context.metrics);
831                pin_mut!(write_future);
832                let output = select(write_future, read_future).await;
833                drop_either_future(output)
834            };
835            match select_result {
836                Either::Left(result) => {
837                    // drop the future to ensure that the future must be reset later
838                    drop(write_future_state);
839                    let (stream, mut write_state, either) = result?;
840                    match either {
841                        WriteFutureEvent::UpstreamMessageReceived(msg) => match msg {
842                            Message::Barrier(barrier) => {
843                                if clean_state && barrier.kind.is_checkpoint() && !buffer.is_empty()
844                                {
845                                    write_future_state = WriteFuture::paused(
846                                        self.logstore_context.pause_duration_ms,
847                                        barrier,
848                                        stream,
849                                        write_state,
850                                    );
851                                    clean_state = false;
852                                    self.logstore_context.metrics.unclean_state.inc();
853                                } else {
854                                    Self::apply_pause_resume_mutation(&barrier, &mut pause_stream);
855                                    let write_state_post_write_barrier = Self::write_barrier(
856                                        self.actor_context.id,
857                                        &mut write_state,
858                                        barrier.clone(),
859                                        &self.logstore_context.metrics,
860                                        progress.take(),
861                                        &mut buffer,
862                                    )
863                                    .await?;
864                                    seq_id = FIRST_SEQ_ID;
865                                    barrier.assume_no_update_vnode_bitmap(self.actor_context.id)?;
866
867                                    yield Message::Barrier(barrier);
868
869                                    write_state_post_write_barrier
870                                        .post_yield_barrier(None)
871                                        .await?;
872
873                                    write_future_state =
874                                        WriteFuture::receive_from_upstream(stream, write_state);
875                                }
876                            }
877                            Message::Chunk(chunk) => {
878                                let (new_seq_id, next_write_future) = Self::process_upstream_chunk(
879                                    seq_id,
880                                    stream,
881                                    write_state,
882                                    chunk,
883                                    &mut buffer,
884                                );
885                                seq_id = new_seq_id;
886                                write_future_state = next_write_future;
887                            }
888                            Message::Watermark(watermark) => {
889                                buffer.add_watermark(write_state.epoch().curr, watermark);
890                                write_future_state =
891                                    WriteFuture::receive_from_upstream(stream, write_state);
892                            }
893                        },
894                        WriteFutureEvent::ChunkFlushed(info) => {
895                            write_future_state = Self::process_flushed_chunk(
896                                stream,
897                                write_state,
898                                info,
899                                &mut buffer,
900                                &self.logstore_context.metrics,
901                            );
902                        }
903                    }
904                }
905                Either::Right(result) => {
906                    let clean_state_reached = read_future_state.mark_clean_state(
907                        &mut clean_state,
908                        &buffer,
909                        &self.logstore_context.metrics,
910                    );
911
912                    if clean_state_reached {
913                        // Let write future resume immediately
914                        if let WriteFuture::Paused { sleep_future, .. } = &mut write_future_state {
915                            tracing::trace!("resuming paused future");
916                            assert!(buffer.has_available_capacity());
917                            *sleep_future = None;
918                        }
919                    }
920                    let message = result?;
921                    if let Message::Chunk(chunk) = &message {
922                        self.logstore_context
923                            .metrics
924                            .total_read_count
925                            .inc_by(chunk.cardinality() as _);
926                    }
927
928                    yield message;
929                }
930            }
931        }
932    }
933}
934
935pub(crate) type PersistedStream<S> =
936    Peekable<Pin<Box<LogStoreItemMergeStream<TimeoutAutoRebuildIter<S>>>>>;
937
938pub(crate) enum ReadFuture<S: StateStoreRead> {
939    ReadingPersistedStream(PersistedStream<S>),
940    ReadingFlushedChunk {
941        future: ReadFlushedChunkFuture,
942        end_seq_id: SeqId,
943    },
944    Idle,
945}
946
947// Read methods
948impl<S: StateStoreRead> ReadFuture<S> {
949    pub(crate) fn mark_clean_state(
950        &self,
951        clean_state: &mut bool,
952        buffer: &SyncedLogStoreBuffer,
953        metrics: &SyncedKvLogStoreMetrics,
954    ) -> bool {
955        if !*clean_state && matches!(self, ReadFuture::Idle) && buffer.is_empty() {
956            *clean_state = true;
957            metrics.clean_state.inc();
958            true
959        } else {
960            false
961        }
962    }
963
964    pub(crate) async fn next_message(
965        &mut self,
966        progress: &mut LogStoreVnodeProgress,
967        read_state: &LogStoreReadState<S>,
968        buffer: &mut SyncedLogStoreBuffer,
969        metrics: &SyncedKvLogStoreMetrics,
970    ) -> StreamExecutorResult<Message> {
971        match self {
972            ReadFuture::ReadingPersistedStream(stream) => {
973                while let Some((epoch, item)) = stream.try_next().await? {
974                    match item {
975                        KvLogStoreItem::Barrier { vnodes, .. } => {
976                            tracing::trace!(epoch, "read logstore barrier");
977                            // update the progress
978                            progress.apply_aligned(vnodes, epoch, None);
979                            continue;
980                        }
981                        KvLogStoreItem::StreamChunk {
982                            chunk,
983                            progress: chunk_progress,
984                        } => {
985                            tracing::trace!("read logstore chunk of size: {}", chunk.cardinality());
986                            progress.apply_per_vnode(epoch, chunk_progress);
987                            return Ok(Message::Chunk(chunk));
988                        }
989                    }
990                }
991                *self = ReadFuture::Idle;
992            }
993            ReadFuture::ReadingFlushedChunk { .. } | ReadFuture::Idle => {}
994        }
995        match self {
996            ReadFuture::ReadingPersistedStream(_) => {
997                unreachable!("must have finished read persisted stream when reaching here")
998            }
999            ReadFuture::ReadingFlushedChunk { .. } => {}
1000            ReadFuture::Idle => loop {
1001                let Some((item_epoch, item)) = buffer.pop_front() else {
1002                    return pending().await;
1003                };
1004                match item {
1005                    SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::StreamChunk {
1006                        chunk,
1007                        start_seq_id,
1008                        end_seq_id,
1009                        flushed,
1010                        ..
1011                    }) => {
1012                        metrics.buffer_read_count.inc_by(chunk.cardinality() as _);
1013                        tracing::trace!(
1014                            start_seq_id,
1015                            end_seq_id,
1016                            flushed,
1017                            cardinality = chunk.cardinality(),
1018                            "read buffered chunk of size"
1019                        );
1020                        progress.apply_aligned(
1021                            read_state.vnodes().clone(),
1022                            item_epoch,
1023                            Some(end_seq_id),
1024                        );
1025                        return Ok(Message::Chunk(chunk));
1026                    }
1027                    SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Flushed {
1028                        vnode_bitmap,
1029                        start_seq_id,
1030                        end_seq_id,
1031                        chunk_id,
1032                    }) => {
1033                        tracing::trace!(start_seq_id, end_seq_id, chunk_id, "read flushed chunk");
1034                        let read_metrics = metrics.flushed_buffer_read_metrics.clone();
1035                        let future = read_state
1036                            .read_flushed_chunk(
1037                                vnode_bitmap,
1038                                chunk_id,
1039                                start_seq_id,
1040                                end_seq_id,
1041                                item_epoch,
1042                                read_metrics,
1043                            )
1044                            .boxed();
1045                        *self = ReadFuture::ReadingFlushedChunk { future, end_seq_id };
1046                        break;
1047                    }
1048                    SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Barrier { .. }) => {
1049                        tracing::trace!(item_epoch, "read buffer barrier");
1050                        progress.apply_aligned(read_state.vnodes().clone(), item_epoch, None);
1051                        continue;
1052                    }
1053                    SyncedLogStoreBufferItem::Watermark(watermark) => {
1054                        return Ok(Message::Watermark(watermark));
1055                    }
1056                }
1057            },
1058        }
1059
1060        let (future, end_seq_id) = match self {
1061            ReadFuture::ReadingPersistedStream(_) | ReadFuture::Idle => {
1062                unreachable!("should be at ReadingFlushedChunk")
1063            }
1064            ReadFuture::ReadingFlushedChunk { future, end_seq_id } => (future, *end_seq_id),
1065        };
1066
1067        let (_, chunk, epoch) = future.await?;
1068        progress.apply_aligned(read_state.vnodes().clone(), epoch, Some(end_seq_id));
1069        tracing::trace!(
1070            end_seq_id,
1071            "read flushed chunk of size: {}",
1072            chunk.cardinality()
1073        );
1074        *self = ReadFuture::Idle;
1075        Ok(Message::Chunk(chunk))
1076    }
1077}
1078
1079// Write methods
1080impl<S: StateStore> SyncedKvLogStoreExecutor<S> {
1081    pub(crate) async fn write_barrier<'a>(
1082        actor_id: ActorId,
1083        write_state: &'a mut LogStoreWriteState<S::Local>,
1084        barrier: Barrier,
1085        metrics: &SyncedKvLogStoreMetrics,
1086        progress: LogStoreVnodeProgress,
1087        buffer: &mut SyncedLogStoreBuffer,
1088    ) -> StreamExecutorResult<LogStorePostSealCurrentEpoch<'a, S::Local>> {
1089        tracing::trace!(%actor_id, ?progress, "applying truncation");
1090        // TODO(kwannoel): As an optimization we can also change flushed chunks to be flushed items
1091        // to reduce memory consumption of logstore.
1092
1093        let epoch = barrier.epoch.prev;
1094        let mut writer = write_state.start_writer(false);
1095        writer.write_barrier(epoch, barrier.is_checkpoint())?;
1096
1097        if barrier.is_checkpoint() {
1098            for (epoch, item) in buffer.buffer.iter_mut().rev() {
1099                match item {
1100                    SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::StreamChunk {
1101                        chunk,
1102                        start_seq_id,
1103                        end_seq_id,
1104                        flushed,
1105                        ..
1106                    }) => {
1107                        if !*flushed {
1108                            writer.write_chunk(chunk, *epoch, *start_seq_id, *end_seq_id)?;
1109                            *flushed = true;
1110                        } else {
1111                            break;
1112                        }
1113                    }
1114                    SyncedLogStoreBufferItem::LogStore(
1115                        LogStoreBufferItem::Flushed { .. } | LogStoreBufferItem::Barrier { .. },
1116                    )
1117                    | SyncedLogStoreBufferItem::Watermark(_) => {}
1118                }
1119            }
1120        }
1121
1122        // Apply truncation
1123        let (flush_info, _) = writer.finish().await?;
1124        metrics
1125            .storage_write_count
1126            .inc_by(flush_info.flush_count as _);
1127        metrics
1128            .storage_write_size
1129            .inc_by(flush_info.flush_size as _);
1130        let post_seal = write_state.seal_current_epoch(barrier.epoch.curr, progress);
1131
1132        // Add to buffer
1133        buffer.buffer.push_back((
1134            epoch,
1135            SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Barrier {
1136                is_checkpoint: barrier.is_checkpoint(),
1137                next_epoch: barrier.epoch.curr,
1138                schema_change: None,
1139                is_stop: false,
1140            }),
1141        ));
1142        buffer.next_chunk_id = 0;
1143        buffer.update_buffer_metrics();
1144
1145        Ok(post_seal)
1146    }
1147}
1148
1149#[derive(EstimateSize)]
1150enum SyncedLogStoreBufferItem {
1151    LogStore(LogStoreBufferItem),
1152    Watermark(Watermark),
1153}
1154
1155pub(crate) struct SyncedLogStoreBuffer {
1156    buffer: VecDeque<(u64, SyncedLogStoreBufferItem)>,
1157    current_size: usize,
1158    max_size: usize,
1159    max_chunk_size: usize,
1160    next_chunk_id: ChunkId,
1161    metrics: SyncedKvLogStoreMetrics,
1162    flushed_count: usize,
1163}
1164
1165impl SyncedLogStoreBuffer {
1166    pub fn new(
1167        max_buffer_size: usize,
1168        chunk_size: usize,
1169        metrics: &SyncedKvLogStoreMetrics,
1170    ) -> Self {
1171        SyncedLogStoreBuffer {
1172            buffer: VecDeque::new(),
1173            current_size: 0,
1174            max_size: max_buffer_size,
1175            max_chunk_size: chunk_size,
1176            next_chunk_id: 0,
1177            metrics: metrics.clone(),
1178            flushed_count: 0,
1179        }
1180    }
1181
1182    pub fn is_empty(&self) -> bool {
1183        self.current_size == 0
1184    }
1185
1186    pub fn has_available_capacity(&self) -> bool {
1187        self.current_size < self.max_size
1188    }
1189
1190    fn add_or_flush_chunk(
1191        &mut self,
1192        start_seq_id: SeqId,
1193        end_seq_id: SeqId,
1194        chunk: StreamChunk,
1195        epoch: u64,
1196    ) -> Option<StreamChunk> {
1197        let current_size = self.current_size;
1198        let chunk_size = chunk.cardinality();
1199
1200        tracing::trace!(
1201            current_size,
1202            chunk_size,
1203            max_size = self.max_size,
1204            "checking chunk size"
1205        );
1206        let should_flush_chunk = current_size + chunk_size > self.max_size;
1207        if should_flush_chunk {
1208            tracing::trace!(start_seq_id, end_seq_id, epoch, "flushing chunk",);
1209            Some(chunk)
1210        } else {
1211            tracing::trace!(start_seq_id, end_seq_id, epoch, "buffering chunk",);
1212            self.add_chunk_to_buffer(chunk, start_seq_id, end_seq_id, epoch);
1213            None
1214        }
1215    }
1216
1217    /// After flushing a chunk, we will preserve a `FlushedItem` inside the buffer.
1218    /// This doesn't contain any data, but it contains the metadata to read the flushed chunk.
1219    fn add_flushed_item_to_buffer(
1220        &mut self,
1221        start_seq_id: SeqId,
1222        end_seq_id: SeqId,
1223        new_vnode_bitmap: Bitmap,
1224        epoch: u64,
1225    ) {
1226        let new_chunk_size = (end_seq_id - start_seq_id + 1) as usize;
1227
1228        if let Some((
1229            item_epoch,
1230            SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Flushed {
1231                start_seq_id: prev_start_seq_id,
1232                end_seq_id: prev_end_seq_id,
1233                vnode_bitmap,
1234                ..
1235            }),
1236        )) = self.buffer.back_mut()
1237            && let flushed_chunk_size = (*prev_end_seq_id - *prev_start_seq_id + 1) as usize
1238            && let projected_flushed_chunk_size = flushed_chunk_size + new_chunk_size
1239            && projected_flushed_chunk_size <= self.max_chunk_size
1240        {
1241            assert!(
1242                *prev_end_seq_id < start_seq_id,
1243                "prev end_seq_id {} should be smaller than current start_seq_id {}",
1244                end_seq_id,
1245                start_seq_id
1246            );
1247            assert_eq!(
1248                epoch, *item_epoch,
1249                "epoch of newly added flushed item must be the same as the last flushed item"
1250            );
1251            *prev_end_seq_id = end_seq_id;
1252            *vnode_bitmap |= new_vnode_bitmap;
1253        } else {
1254            let chunk_id = self.next_chunk_id;
1255            self.next_chunk_id += 1;
1256            self.buffer.push_back((
1257                epoch,
1258                SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Flushed {
1259                    start_seq_id,
1260                    end_seq_id,
1261                    vnode_bitmap: new_vnode_bitmap,
1262                    chunk_id,
1263                }),
1264            ));
1265            self.flushed_count += 1;
1266            tracing::trace!(
1267                "adding flushed item to buffer: start_seq_id: {start_seq_id}, end_seq_id: {end_seq_id}, chunk_id: {chunk_id}"
1268            );
1269        }
1270        // FIXME(kwannoel): Seems these metrics are updated _after_ the flush info is reported.
1271        self.update_buffer_metrics();
1272    }
1273
1274    fn add_chunk_to_buffer(
1275        &mut self,
1276        chunk: StreamChunk,
1277        start_seq_id: SeqId,
1278        end_seq_id: SeqId,
1279        epoch: u64,
1280    ) {
1281        let chunk_id = self.next_chunk_id;
1282        self.next_chunk_id += 1;
1283        self.current_size += chunk.cardinality();
1284        self.buffer.push_back((
1285            epoch,
1286            SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::StreamChunk {
1287                chunk,
1288                start_seq_id,
1289                end_seq_id,
1290                flushed: false,
1291                chunk_id,
1292            }),
1293        ));
1294        self.update_buffer_metrics();
1295    }
1296
1297    pub(crate) fn add_watermark(&mut self, epoch: u64, watermark: Watermark) {
1298        self.buffer
1299            .push_back((epoch, SyncedLogStoreBufferItem::Watermark(watermark)));
1300        self.update_buffer_metrics();
1301    }
1302
1303    fn pop_front(&mut self) -> Option<(u64, SyncedLogStoreBufferItem)> {
1304        let item = self.buffer.pop_front();
1305        match &item {
1306            Some((_, SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Flushed { .. }))) => {
1307                self.flushed_count -= 1;
1308            }
1309            Some((
1310                _,
1311                SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::StreamChunk {
1312                    chunk, ..
1313                }),
1314            )) => {
1315                self.current_size -= chunk.cardinality();
1316            }
1317            _ => {}
1318        }
1319        self.update_buffer_metrics();
1320        item
1321    }
1322
1323    fn update_buffer_metrics(&self) {
1324        let mut epoch_count = 0;
1325        let mut row_count = 0;
1326        let mut memory_bytes = 0;
1327        for (_, item) in &self.buffer {
1328            match item {
1329                SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::StreamChunk {
1330                    chunk,
1331                    ..
1332                }) => {
1333                    row_count += chunk.cardinality();
1334                }
1335                SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Flushed {
1336                    start_seq_id,
1337                    end_seq_id,
1338                    ..
1339                }) => {
1340                    row_count += (end_seq_id - start_seq_id) as usize;
1341                }
1342                SyncedLogStoreBufferItem::LogStore(LogStoreBufferItem::Barrier { .. }) => {
1343                    epoch_count += 1;
1344                }
1345                SyncedLogStoreBufferItem::Watermark(_) => {}
1346            }
1347            memory_bytes += item.estimated_size();
1348        }
1349        self.metrics.buffer_unconsumed_epoch_count.set(epoch_count);
1350        self.metrics.buffer_unconsumed_row_count.set(row_count as _);
1351        self.metrics
1352            .buffer_unconsumed_item_count
1353            .set(self.buffer.len() as _);
1354        self.metrics
1355            .buffer_unconsumed_min_epoch
1356            .set_optional(self.buffer.front().map(|(epoch, _)| *epoch as _));
1357        self.metrics.buffer_memory_bytes.set(memory_bytes as _);
1358    }
1359}
1360
1361impl<S> Execute for SyncedKvLogStoreExecutor<S>
1362where
1363    S: StateStore,
1364{
1365    fn execute(self: Box<Self>) -> BoxedMessageStream {
1366        self.execute_monitored().boxed()
1367    }
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372    use itertools::Itertools;
1373    use pretty_assertions::assert_eq;
1374    use risingwave_common::catalog::Field;
1375    use risingwave_common::hash::VirtualNode;
1376    use risingwave_common::test_prelude::*;
1377    use risingwave_common::util::epoch::test_epoch;
1378    use risingwave_storage::memory::MemoryStateStore;
1379
1380    use super::*;
1381    use crate::assert_stream_chunk_eq;
1382    use crate::common::log_store_impl::kv_log_store::KV_LOG_STORE_V2_INFO;
1383    use crate::common::log_store_impl::kv_log_store::test_utils::{
1384        check_stream_chunk_eq, gen_test_log_store_table, test_payload_schema,
1385    };
1386    use crate::executor::sync_kv_log_store::metrics::SyncedKvLogStoreMetrics;
1387    use crate::executor::test_utils::MockSource;
1388
1389    fn init_logger() {
1390        let _ = tracing_subscriber::fmt()
1391            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
1392            .with_ansi(false)
1393            .try_init();
1394    }
1395
1396    // test read/write buffer
1397    #[tokio::test]
1398    async fn test_read_write_buffer() {
1399        init_logger();
1400
1401        let pk_info = &KV_LOG_STORE_V2_INFO;
1402        let column_descs = test_payload_schema(pk_info);
1403        let fields = column_descs
1404            .into_iter()
1405            .map(|desc| Field::new(desc.name.clone(), desc.data_type))
1406            .collect_vec();
1407        let schema = Schema { fields };
1408        let stream_key = vec![0];
1409        let (mut tx, source) = MockSource::channel();
1410        let source = source.into_executor(schema.clone(), stream_key.clone());
1411
1412        let vnodes = Arc::new(Bitmap::ones(VirtualNode::COUNT_FOR_TEST));
1413
1414        let table = gen_test_log_store_table(pk_info);
1415
1416        let log_store_executor = SyncedKvLogStoreExecutor::new(
1417            ActorContext::for_test(123),
1418            table.id,
1419            SyncedKvLogStoreMetrics::for_test(),
1420            LogStoreRowSerde::new(&table, vnodes, pk_info),
1421            MemoryStateStore::new(),
1422            10,
1423            256,
1424            source,
1425            Duration::from_millis(256),
1426            false,
1427        )
1428        .boxed();
1429
1430        // Init
1431        tx.push_barrier(test_epoch(1), false);
1432
1433        let chunk_1 = StreamChunk::from_pretty(
1434            "  I   T
1435            +  5  10
1436            +  6  10
1437            +  8  10
1438            +  9  10
1439            +  10 11",
1440        );
1441
1442        let chunk_2 = StreamChunk::from_pretty(
1443            "   I   T
1444            -   5  10
1445            -   6  10
1446            -   8  10
1447            U-  9  10
1448            U+ 10  11",
1449        );
1450
1451        tx.push_chunk(chunk_1.clone());
1452        tx.push_int64_watermark(0, 7);
1453        tx.push_chunk(chunk_2.clone());
1454
1455        let mut stream = log_store_executor.execute();
1456
1457        match stream.next().await {
1458            Some(Ok(Message::Barrier(barrier))) => {
1459                assert_eq!(barrier.epoch.curr, test_epoch(1));
1460            }
1461            other => panic!("Expected a barrier message, got {:?}", other),
1462        }
1463
1464        match stream.next().await {
1465            Some(Ok(Message::Chunk(chunk))) => {
1466                assert_stream_chunk_eq!(chunk, chunk_1);
1467            }
1468            other => panic!("Expected a chunk message, got {:?}", other),
1469        }
1470
1471        match stream.next().await {
1472            Some(Ok(Message::Watermark(watermark))) => {
1473                assert_eq!(watermark, Watermark::new(0, DataType::Int64, 7_i64.into()));
1474            }
1475            other => panic!("Expected a watermark message, got {:?}", other),
1476        }
1477
1478        match stream.next().await {
1479            Some(Ok(Message::Chunk(chunk))) => {
1480                assert_stream_chunk_eq!(chunk, chunk_2);
1481            }
1482            other => panic!("Expected a chunk message, got {:?}", other),
1483        }
1484
1485        tx.push_barrier(test_epoch(2), false);
1486
1487        match stream.next().await {
1488            Some(Ok(Message::Barrier(barrier))) => {
1489                assert_eq!(barrier.epoch.curr, test_epoch(2));
1490            }
1491            other => panic!("Expected a barrier message, got {:?}", other),
1492        }
1493    }
1494
1495    // test barrier persisted read
1496    //
1497    // sequence of events (earliest -> latest):
1498    // barrier(1) -> chunk(1) -> watermark -> chunk(2) -> poll(4) items -> barrier(2)
1499    // * poll just means we read from the executor stream.
1500    #[tokio::test]
1501    async fn test_barrier_persisted_read() {
1502        init_logger();
1503
1504        let pk_info = &KV_LOG_STORE_V2_INFO;
1505        let column_descs = test_payload_schema(pk_info);
1506        let fields = column_descs
1507            .into_iter()
1508            .map(|desc| Field::new(desc.name.clone(), desc.data_type))
1509            .collect_vec();
1510        let schema = Schema { fields };
1511        let stream_key = vec![0];
1512        let (mut tx, source) = MockSource::channel();
1513        let source = source.into_executor(schema.clone(), stream_key.clone());
1514
1515        let vnodes = Arc::new(Bitmap::ones(VirtualNode::COUNT_FOR_TEST));
1516
1517        let table = gen_test_log_store_table(pk_info);
1518
1519        let log_store_executor = SyncedKvLogStoreExecutor::new(
1520            ActorContext::for_test(123),
1521            table.id,
1522            SyncedKvLogStoreMetrics::for_test(),
1523            LogStoreRowSerde::new(&table, vnodes, pk_info),
1524            MemoryStateStore::new(),
1525            10,
1526            256,
1527            source,
1528            Duration::from_millis(256),
1529            false,
1530        )
1531        .boxed();
1532
1533        // Init
1534        tx.push_barrier(test_epoch(1), false);
1535
1536        let chunk_1 = StreamChunk::from_pretty(
1537            "  I   T
1538            +  5  10
1539            +  6  10
1540            +  8  10
1541            +  9  10
1542            +  10 11",
1543        );
1544
1545        let chunk_2 = StreamChunk::from_pretty(
1546            "   I   T
1547            -   5  10
1548            -   6  10
1549            -   8  10
1550            U- 10  11
1551            U+ 10  10",
1552        );
1553
1554        tx.push_chunk(chunk_1.clone());
1555        tx.push_int64_watermark(0, 7);
1556        tx.push_chunk(chunk_2.clone());
1557
1558        tx.push_barrier(test_epoch(2), false);
1559
1560        let mut stream = log_store_executor.execute();
1561
1562        match stream.next().await {
1563            Some(Ok(Message::Barrier(barrier))) => {
1564                assert_eq!(barrier.epoch.curr, test_epoch(1));
1565            }
1566            other => panic!("Expected a barrier message, got {:?}", other),
1567        }
1568
1569        match stream.next().await {
1570            Some(Ok(Message::Chunk(chunk))) => {
1571                assert_stream_chunk_eq!(chunk, chunk_1);
1572            }
1573            other => panic!("Expected a chunk message, got {:?}", other),
1574        }
1575
1576        match stream.next().await {
1577            Some(Ok(Message::Watermark(watermark))) => {
1578                assert_eq!(watermark, Watermark::new(0, DataType::Int64, 7_i64.into()));
1579            }
1580            other => panic!("Expected a watermark message, got {:?}", other),
1581        }
1582
1583        match stream.next().await {
1584            Some(Ok(Message::Chunk(chunk))) => {
1585                assert_stream_chunk_eq!(chunk, chunk_2);
1586            }
1587            other => panic!("Expected a chunk message, got {:?}", other),
1588        }
1589
1590        match stream.next().await {
1591            Some(Ok(Message::Barrier(barrier))) => {
1592                assert_eq!(barrier.epoch.curr, test_epoch(2));
1593            }
1594            other => panic!("Expected a barrier message, got {:?}", other),
1595        }
1596    }
1597
1598    // When we hit buffer max_chunk, we only store placeholder `FlushedItem`.
1599    // So we just let capacity = 0, and we will always flush incoming chunks to state store.
1600    #[tokio::test]
1601    async fn test_max_chunk_persisted_read() {
1602        init_logger();
1603
1604        let pk_info = &KV_LOG_STORE_V2_INFO;
1605        let column_descs = test_payload_schema(pk_info);
1606        let fields = column_descs
1607            .into_iter()
1608            .map(|desc| Field::new(desc.name.clone(), desc.data_type))
1609            .collect_vec();
1610        let schema = Schema { fields };
1611        let stream_key = vec![0];
1612        let (mut tx, source) = MockSource::channel();
1613        let source = source.into_executor(schema.clone(), stream_key.clone());
1614
1615        let vnodes = Arc::new(Bitmap::ones(VirtualNode::COUNT_FOR_TEST));
1616
1617        let table = gen_test_log_store_table(pk_info);
1618
1619        let log_store_executor = SyncedKvLogStoreExecutor::new(
1620            ActorContext::for_test(123),
1621            table.id,
1622            SyncedKvLogStoreMetrics::for_test(),
1623            LogStoreRowSerde::new(&table, vnodes, pk_info),
1624            MemoryStateStore::new(),
1625            0,
1626            256,
1627            source,
1628            Duration::from_millis(256),
1629            false,
1630        )
1631        .boxed();
1632
1633        // Init
1634        tx.push_barrier(test_epoch(1), false);
1635
1636        let chunk_1 = StreamChunk::from_pretty(
1637            "  I   T
1638            +  5  10
1639            +  6  10
1640            +  8  10
1641            +  9  10
1642            +  10 11",
1643        );
1644
1645        let chunk_2 = StreamChunk::from_pretty(
1646            "   I   T
1647            -   5  10
1648            -   6  10
1649            -   8  10
1650            U- 10  11
1651            U+ 10  10",
1652        );
1653
1654        let chunk_3 = StreamChunk::from_pretty(
1655            "   I   T
1656            +  11  12",
1657        );
1658
1659        tx.push_chunk(chunk_1.clone());
1660        tx.push_chunk(chunk_2.clone());
1661        tx.push_int64_watermark(0, 7);
1662        tx.push_chunk(chunk_3.clone());
1663
1664        tx.push_barrier(test_epoch(2), false);
1665
1666        let mut stream = log_store_executor.execute();
1667
1668        for i in 1..=2 {
1669            match stream.next().await {
1670                Some(Ok(Message::Barrier(barrier))) => {
1671                    assert_eq!(barrier.epoch.curr, test_epoch(i));
1672                }
1673                other => panic!("Expected a barrier message, got {:?}", other),
1674            }
1675        }
1676
1677        match stream.next().await {
1678            Some(Ok(Message::Chunk(actual))) => {
1679                let expected = StreamChunk::from_pretty(
1680                    "   I   T
1681                    +   5  10
1682                    +   6  10
1683                    +   8  10
1684                    +   9  10
1685                    +  10  11
1686                    -   5  10
1687                    -   6  10
1688                    -   8  10
1689                    U- 10  11
1690                    U+ 10  10",
1691                );
1692                assert_stream_chunk_eq!(actual, expected);
1693            }
1694            other => panic!("Expected a chunk message, got {:?}", other),
1695        }
1696
1697        match stream.next().await {
1698            Some(Ok(Message::Watermark(watermark))) => {
1699                assert_eq!(watermark, Watermark::new(0, DataType::Int64, 7_i64.into()));
1700            }
1701            other => panic!("Expected a watermark message, got {:?}", other),
1702        }
1703
1704        match stream.next().await {
1705            Some(Ok(Message::Chunk(actual))) => {
1706                assert_stream_chunk_eq!(actual, chunk_3);
1707            }
1708            other => panic!("Expected a chunk message, got {:?}", other),
1709        }
1710    }
1711}