risingwave_stream/common/log_store_impl/
in_mem.rs1use anyhow::{Context, anyhow};
16use await_tree::InstrumentAwait;
17use futures::FutureExt;
18use futures::future::BoxFuture;
19use risingwave_common::array::StreamChunk;
20use risingwave_common::util::epoch::{EpochExt, EpochPair, INVALID_EPOCH};
21use risingwave_connector::sink::log_store::{
22 FlushCurrentEpochOptions, LogReader, LogStoreFactory, LogStoreReadItem, LogStoreResult,
23 LogWriter, LogWriterPostFlushCurrentEpoch, TruncateBarrierLogReader, TruncateOffset,
24};
25use tokio::sync::mpsc::{
26 Receiver, Sender, UnboundedReceiver, UnboundedSender, channel, unbounded_channel,
27};
28use tokio::sync::oneshot;
29
30use crate::common::log_store_impl::in_mem::LogReaderEpochProgress::{AwaitingTruncate, Consuming};
31use crate::executor::StreamExecutorResult;
32
33enum InMemLogStoreItem {
34 StreamChunk(StreamChunk),
35 Barrier {
36 next_epoch: u64,
37 options: FlushCurrentEpochOptions,
38 },
39}
40
41pub struct BoundedInMemLogStoreWriter {
47 curr_epoch: Option<u64>,
49
50 init_epoch_tx: Option<oneshot::Sender<u64>>,
52
53 item_tx: Sender<InMemLogStoreItem>,
55
56 truncated_epoch_rx: UnboundedReceiver<u64>,
58
59 wait_init_epoch: Option<WaitInitEpochFn>,
60}
61
62#[derive(Eq, PartialEq, Debug)]
63enum LogReaderEpochProgress {
64 Consuming(u64),
66 AwaitingTruncate { sealed_epoch: u64, next_epoch: u64 },
68}
69
70const UNINITIALIZED: LogReaderEpochProgress = LogReaderEpochProgress::Consuming(INVALID_EPOCH);
71
72pub struct BoundedInMemLogStoreReader {
73 epoch_progress: LogReaderEpochProgress,
76
77 init_epoch_rx: Option<oneshot::Receiver<u64>>,
79
80 item_rx: Receiver<InMemLogStoreItem>,
82
83 truncated_epoch_tx: UnboundedSender<u64>,
85
86 latest_offset: TruncateOffset,
88
89 truncate_offset: TruncateOffset,
91}
92
93type WaitInitEpochFn =
94 Box<dyn FnOnce(EpochPair) -> BoxFuture<'static, StreamExecutorResult<()>> + Send + 'static>;
95
96pub struct BoundedInMemLogStoreFactory {
97 bound: usize,
98 wait_init_epoch: WaitInitEpochFn,
99}
100
101impl BoundedInMemLogStoreFactory {
102 pub fn new(
103 bound: usize,
104 wait_init_epoch: impl FnOnce(EpochPair) -> BoxFuture<'static, StreamExecutorResult<()>>
105 + Send
106 + 'static,
107 ) -> Self {
108 Self {
109 bound,
110 wait_init_epoch: Box::new(wait_init_epoch),
111 }
112 }
113
114 #[cfg(test)]
115 pub fn for_test(bound: usize) -> Self {
116 Self {
117 bound,
118 wait_init_epoch: Box::new(|_x| std::future::ready(Ok(())).boxed()),
119 }
120 }
121}
122
123impl LogStoreFactory for BoundedInMemLogStoreFactory {
124 type Reader = TruncateBarrierLogReader<BoundedInMemLogStoreReader>;
125 type Writer = BoundedInMemLogStoreWriter;
126
127 const ALLOW_REWIND: bool = false;
128 const REBUILD_SINK_ON_UPDATE_VNODE_BITMAP: bool = false;
129
130 async fn build(self) -> (Self::Reader, Self::Writer) {
131 let (init_epoch_tx, init_epoch_rx) = oneshot::channel();
132 let (item_tx, item_rx) = channel(self.bound);
133 let (truncated_epoch_tx, truncated_epoch_rx) = unbounded_channel();
134 let reader = BoundedInMemLogStoreReader {
135 epoch_progress: UNINITIALIZED,
136 init_epoch_rx: Some(init_epoch_rx),
137 item_rx,
138 truncated_epoch_tx,
139 latest_offset: TruncateOffset::Barrier { epoch: 0 },
140 truncate_offset: TruncateOffset::Barrier { epoch: 0 },
141 };
142 let writer = BoundedInMemLogStoreWriter {
143 curr_epoch: None,
144 init_epoch_tx: Some(init_epoch_tx),
145 item_tx,
146 truncated_epoch_rx,
147 wait_init_epoch: Some(self.wait_init_epoch),
148 };
149 (TruncateBarrierLogReader::new(reader), writer)
150 }
151}
152
153impl LogReader for BoundedInMemLogStoreReader {
154 async fn init(&mut self) -> LogStoreResult<()> {
155 let init_epoch_rx = self
156 .init_epoch_rx
157 .take()
158 .expect("should not init for twice");
159 let epoch = init_epoch_rx.await.context("unable to get init epoch")?;
160 assert_eq!(self.epoch_progress, UNINITIALIZED);
161 self.epoch_progress = LogReaderEpochProgress::Consuming(epoch);
162 self.latest_offset = TruncateOffset::Barrier {
163 epoch: epoch.prev_epoch(),
164 };
165 self.truncate_offset = TruncateOffset::Barrier {
166 epoch: epoch.prev_epoch(),
167 };
168 Ok(())
169 }
170
171 async fn next_item(&mut self) -> LogStoreResult<(u64, LogStoreReadItem)> {
172 match self.epoch_progress {
173 Consuming(current_epoch) => match self.item_rx.recv().await {
174 Some(item) => match item {
175 InMemLogStoreItem::StreamChunk(chunk) => {
176 let chunk_id = match self.latest_offset {
177 TruncateOffset::Chunk { epoch, chunk_id } => {
178 assert_eq!(epoch, current_epoch);
179 chunk_id + 1
180 }
181 TruncateOffset::Barrier { epoch } => {
182 assert!(
183 epoch < current_epoch,
184 "prev offset at barrier {} but current epoch {}",
185 epoch,
186 current_epoch
187 );
188 0
189 }
190 };
191 self.latest_offset = TruncateOffset::Chunk {
192 epoch: current_epoch,
193 chunk_id,
194 };
195 Ok((
196 current_epoch,
197 LogStoreReadItem::StreamChunk { chunk, chunk_id },
198 ))
199 }
200 InMemLogStoreItem::Barrier {
201 next_epoch,
202 options,
203 } => {
204 if options.is_checkpoint {
205 self.epoch_progress = AwaitingTruncate {
206 next_epoch,
207 sealed_epoch: current_epoch,
208 };
209 } else {
210 self.epoch_progress = Consuming(next_epoch);
211 }
212 self.latest_offset = TruncateOffset::Barrier {
213 epoch: current_epoch,
214 };
215 Ok((
216 current_epoch,
217 LogStoreReadItem::Barrier {
218 is_checkpoint: options.is_checkpoint,
219 new_vnode_bitmap: options.new_vnode_bitmap,
220 is_stop: options.is_stop,
221 schema_change: options.schema_change,
222 },
223 ))
224 }
225 },
226 None => Err(anyhow!("end of log stream")),
227 },
228 AwaitingTruncate { .. } => std::future::pending().await,
229 }
230 }
231
232 fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()> {
233 if self.truncate_offset >= offset {
235 return Err(anyhow!(
236 "truncate offset {:?} but prev truncate offset is {:?}",
237 offset,
238 self.truncate_offset
239 ));
240 }
241
242 if offset > self.latest_offset {
244 return Err(anyhow!(
245 "truncate at {:?} but latest offset is {:?}",
246 offset,
247 self.latest_offset
248 ));
249 }
250
251 if let AwaitingTruncate {
252 sealed_epoch,
253 next_epoch,
254 } = &self.epoch_progress
255 && let TruncateOffset::Barrier { epoch } = offset
256 && epoch == *sealed_epoch
257 {
258 let sealed_epoch = *sealed_epoch;
259 self.epoch_progress = Consuming(*next_epoch);
260 self.truncated_epoch_tx
261 .send(sealed_epoch)
262 .map_err(|_| anyhow!("unable to send sealed epoch"))?;
263 }
264 self.truncate_offset = offset;
265 Ok(())
266 }
267
268 async fn rewind(&mut self) -> LogStoreResult<()> {
269 Err(anyhow!("should not call rewind on it"))
270 }
271
272 async fn start_from(&mut self, _start_offset: Option<u64>) -> LogStoreResult<()> {
273 Ok(())
274 }
275}
276
277impl LogWriter for BoundedInMemLogStoreWriter {
278 async fn init(
279 &mut self,
280 epoch: EpochPair,
281 _pause_read_on_bootstrap: bool,
282 ) -> LogStoreResult<()> {
283 let init_epoch_tx = self.init_epoch_tx.take().expect("cannot be init for twice");
284 self.wait_init_epoch
285 .take()
286 .expect("cannot be init for in-mem log store")(epoch)
287 .await?;
288 init_epoch_tx
289 .send(epoch.curr)
290 .map_err(|_| anyhow!("unable to send init epoch"))?;
291 self.curr_epoch = Some(epoch.curr);
292 Ok(())
293 }
294
295 async fn write_chunk(&mut self, chunk: StreamChunk) -> LogStoreResult<()> {
296 self.item_tx
297 .send(InMemLogStoreItem::StreamChunk(chunk))
298 .instrument_await("in_mem_send_item_chunk")
299 .await
300 .map_err(|_| anyhow!("unable to send stream chunk"))?;
301 Ok(())
302 }
303
304 async fn flush_current_epoch(
305 &mut self,
306 next_epoch: u64,
307 options: FlushCurrentEpochOptions,
308 ) -> LogStoreResult<LogWriterPostFlushCurrentEpoch<'_>> {
309 let is_checkpoint = options.is_checkpoint;
310 self.item_tx
311 .send(InMemLogStoreItem::Barrier {
312 next_epoch,
313 options,
314 })
315 .instrument_await("in_mem_send_item_barrier")
316 .await
317 .map_err(|_| anyhow!("unable to send barrier"))?;
318
319 let prev_epoch = self
320 .curr_epoch
321 .replace(next_epoch)
322 .expect("should have epoch");
323
324 if is_checkpoint {
325 let truncated_epoch = self
326 .truncated_epoch_rx
327 .recv()
328 .instrument_await("in_mem_recv_truncated_epoch")
329 .await
330 .ok_or_else(|| anyhow!("cannot get truncated epoch"))?;
331 assert_eq!(truncated_epoch, prev_epoch);
332 }
333
334 Ok(LogWriterPostFlushCurrentEpoch::new(move || {
335 async move { Ok(()) }.boxed()
336 }))
337 }
338
339 fn pause(&mut self) -> LogStoreResult<()> {
340 Ok(())
342 }
343
344 fn resume(&mut self) -> LogStoreResult<()> {
345 Ok(())
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use std::future::poll_fn;
353 use std::task::Poll;
354
355 use futures::FutureExt;
356 use risingwave_common::array::{Op, StreamChunkBuilder};
357 use risingwave_common::types::{DataType, ScalarImpl};
358 use risingwave_common::util::epoch::{EpochPair, test_epoch};
359 use risingwave_connector::sink::log_store::{
360 LogReader, LogStoreFactory, LogStoreReadItem, LogWriter, TruncateOffset,
361 };
362
363 use crate::common::log_store_impl::in_mem::BoundedInMemLogStoreFactory;
364 use crate::common::log_store_impl::kv_log_store::test_utils::LogWriterTestExt;
365
366 #[tokio::test]
367 async fn test_in_memory_log_store() {
368 let factory = BoundedInMemLogStoreFactory::for_test(4);
369 let (mut reader, mut writer) = factory.build().await;
370
371 let init_epoch = test_epoch(1);
372 let epoch1 = test_epoch(2);
373 let epoch2 = test_epoch(3);
374
375 let ops = vec![Op::Insert, Op::Delete, Op::UpdateInsert, Op::UpdateDelete];
376 let mut builder =
377 StreamChunkBuilder::unlimited(vec![DataType::Int64, DataType::Varchar], None);
378 for (i, op) in ops.into_iter().enumerate() {
379 assert!(
380 builder
381 .append_row(
382 op,
383 [
384 Some(ScalarImpl::Int64(i as i64)),
385 Some(ScalarImpl::Utf8(format!("name_{}", i).into_boxed_str()))
386 ]
387 )
388 .is_none()
389 );
390 }
391 let stream_chunk = builder.take().unwrap();
392 let stream_chunk_clone = stream_chunk.clone();
393
394 let mut join_handle = tokio::spawn(async move {
395 writer
396 .init(EpochPair::new_test_epoch(init_epoch), false)
397 .await
398 .unwrap();
399 writer
400 .write_chunk(stream_chunk_clone.clone())
401 .await
402 .unwrap();
403 writer
404 .write_chunk(stream_chunk_clone.clone())
405 .await
406 .unwrap();
407 writer
408 .flush_current_epoch_for_test(epoch1, false)
409 .await
410 .unwrap();
411 writer.write_chunk(stream_chunk_clone).await.unwrap();
412 writer
413 .flush_current_epoch_for_test(epoch2, true)
414 .await
415 .unwrap();
416 });
417
418 reader.init().await.unwrap();
419 let _chunk_id1_1 = match reader.next_item().await.unwrap() {
420 (epoch, LogStoreReadItem::StreamChunk { chunk, chunk_id }) => {
421 assert_eq!(epoch, init_epoch);
422 assert_eq!(&chunk, &stream_chunk);
423 chunk_id
424 }
425 _ => unreachable!(),
426 };
427
428 let chunk_id1_2 = match reader.next_item().await.unwrap() {
429 (epoch, LogStoreReadItem::StreamChunk { chunk, chunk_id }) => {
430 assert_eq!(epoch, init_epoch);
431 assert_eq!(&chunk, &stream_chunk);
432 chunk_id
433 }
434 _ => unreachable!(),
435 };
436
437 match reader.next_item().await.unwrap() {
438 (epoch, LogStoreReadItem::Barrier { is_checkpoint, .. }) => {
439 assert!(!is_checkpoint);
440 assert_eq!(epoch, init_epoch);
441 }
442 _ => unreachable!(),
443 }
444
445 let chunk_id2_1 = match reader.next_item().await.unwrap() {
446 (epoch, LogStoreReadItem::StreamChunk { chunk, chunk_id }) => {
447 assert_eq!(&chunk, &stream_chunk);
448 assert_eq!(epoch, epoch1);
449 chunk_id
450 }
451 _ => unreachable!(),
452 };
453
454 match reader.next_item().await.unwrap() {
455 (epoch, LogStoreReadItem::Barrier { is_checkpoint, .. }) => {
456 assert!(is_checkpoint);
457 assert_eq!(epoch, epoch1);
458 }
459 _ => unreachable!(),
460 }
461
462 reader
463 .truncate(TruncateOffset::Chunk {
464 epoch: init_epoch,
465 chunk_id: chunk_id1_2,
466 })
467 .unwrap();
468 assert!(
469 poll_fn(|cx| Poll::Ready(join_handle.poll_unpin(cx)))
470 .await
471 .is_pending()
472 );
473 reader
474 .truncate(TruncateOffset::Chunk {
475 epoch: epoch1,
476 chunk_id: chunk_id2_1,
477 })
478 .unwrap();
479 assert!(
480 poll_fn(|cx| Poll::Ready(join_handle.poll_unpin(cx)))
481 .await
482 .is_pending()
483 );
484 reader
485 .truncate(TruncateOffset::Barrier { epoch: epoch1 })
486 .unwrap();
487 join_handle.await.unwrap();
488 }
489}