1use std::collections::BTreeMap;
16use std::future::Future;
17use std::mem;
18
19use either::Either;
20use futures::future::{Either as FutureEither, select};
21use futures::stream::FuturesOrdered;
22use futures::{StreamExt, TryStreamExt};
23use risingwave_common::catalog::{ColumnDesc, TableId, TableVersionId};
24use risingwave_common::transaction::transaction_id::TxnId;
25use risingwave_common::transaction::transaction_message::TxnMsg;
26use risingwave_common_rate_limit::{MonitoredRateLimiter, RateLimit, RateLimiter};
27use risingwave_dml::dml_manager::DmlManagerRef;
28use risingwave_expr::codegen::BoxStream;
29use risingwave_hummock_sdk::HummockReadEpoch;
30use risingwave_pb::common::ThrottleType;
31use risingwave_storage::StateStore;
32use risingwave_storage::store::TryWaitEpochOptions;
33use tokio::sync::oneshot;
34
35use crate::common::rate_limit::rate_limited_pieces;
36use crate::executor::prelude::*;
37use crate::executor::stream_reader::StreamReaderWithPause;
38
39pub struct DmlExecutor<S: StateStore> {
42 actor_ctx: ActorContextRef,
43
44 upstream: Executor,
45
46 dml_manager: DmlManagerRef,
48
49 table_id: TableId,
51
52 table_version_id: TableVersionId,
54
55 column_descs: Vec<ColumnDesc>,
57
58 chunk_size: usize,
59
60 rate_limiter: Arc<MonitoredRateLimiter>,
61
62 state_store: S,
63}
64
65const MAX_CHUNK_FOR_ATOMICITY: usize = 32;
73
74#[derive(Debug, Default)]
75struct TxnBuffer {
76 vec: Vec<StreamChunk>,
77 overflow: bool,
79}
80
81impl<S: StateStore> DmlExecutor<S> {
82 #[expect(clippy::too_many_arguments)]
83 pub fn new(
84 actor_ctx: ActorContextRef,
85 upstream: Executor,
86 dml_manager: DmlManagerRef,
87 table_id: TableId,
88 table_version_id: TableVersionId,
89 column_descs: Vec<ColumnDesc>,
90 chunk_size: usize,
91 rate_limit: RateLimit,
92 state_store: S,
93 ) -> Self {
94 let rate_limiter = Arc::new(RateLimiter::new(rate_limit).monitored(table_id));
95 Self {
96 actor_ctx,
97 upstream,
98 dml_manager,
99 table_id,
100 table_version_id,
101 column_descs,
102 chunk_size,
103 rate_limiter,
104 state_store,
105 }
106 }
107
108 #[try_stream(ok = Message, error = StreamExecutorError)]
109 async fn execute_inner(self: Box<Self>) {
110 let mut upstream = self.upstream.execute();
111
112 let actor_id = self.actor_ctx.id;
113
114 let barrier = expect_first_barrier(&mut upstream).await?;
116
117 let handle = self.dml_manager.register_reader(
125 self.table_id,
126 self.table_version_id,
127 &self.column_descs,
128 )?;
129 let reader = apply_dml_rate_limit(
130 handle.stream_reader().into_stream(),
131 self.rate_limiter.clone(),
132 )
133 .boxed()
134 .map_err(StreamExecutorError::from);
135
136 let mut stream = StreamReaderWithPause::<false, TxnMsg>::new(upstream, reader);
140
141 if barrier.is_pause_on_startup() {
143 stream.pause_stream();
144 }
145
146 yield Message::Barrier(barrier);
147
148 let mut active_txn_map: BTreeMap<TxnId, TxnBuffer> = Default::default();
150 let mut batch_group: Vec<StreamChunk> = vec![];
152
153 let mut builder = StreamChunkBuilder::new(
154 self.chunk_size,
155 self.column_descs
156 .iter()
157 .map(|c| c.data_type.clone())
158 .collect(),
159 );
160
161 let mut pending_persistence_notifiers: Vec<oneshot::Sender<()>> = Vec::new();
164
165 let mut persistence_futures = FuturesOrdered::new();
168
169 while let Some(input_msg) =
170 next_input_driving_persistence(&mut stream, &mut persistence_futures).await?
171 {
172 match input_msg {
173 Either::Left(msg) => {
174 if let Message::Barrier(barrier) = &msg {
176 if !pending_persistence_notifiers.is_empty() {
178 let notifiers = mem::take(&mut pending_persistence_notifiers);
179 let closing_epoch = barrier.epoch.prev;
180 let store = self.state_store.clone();
181 let table_id = self.table_id;
182 persistence_futures.push_back(async move {
183 store
184 .try_wait_epoch(
185 HummockReadEpoch::Committed(closing_epoch),
186 TryWaitEpochOptions { table_id },
187 )
188 .await
189 .map_err(StreamExecutorError::from)?;
190 for tx in notifiers {
191 let _ = tx.send(());
192 }
193 Ok(())
194 });
195 }
196
197 if let Some(mutation) = barrier.mutation.as_deref() {
200 match mutation {
201 Mutation::Pause => stream.pause_stream(),
202 Mutation::Resume => stream.resume_stream(),
203 Mutation::Throttle(fragment_to_apply) => {
204 if let Some(entry) =
205 fragment_to_apply.get(&self.actor_ctx.fragment_id)
206 && entry.throttle_type() == ThrottleType::Dml
207 {
208 let new_rate_limit = entry.rate_limit.into();
209 let old_rate_limit =
210 self.rate_limiter.update(new_rate_limit);
211
212 if old_rate_limit != new_rate_limit {
213 tracing::info!(
214 old_rate_limit = ?old_rate_limit,
215 new_rate_limit = ?new_rate_limit,
216 %actor_id,
217 "dml rate limit changed",
218 );
219 }
220 }
221 }
222 _ => {}
223 }
224 }
225
226 if !batch_group.is_empty() {
228 let vec = mem::take(&mut batch_group);
229 for chunk in vec {
230 for (op, row) in chunk.rows() {
231 if let Some(chunk) = builder.append_row(op, row) {
232 yield Message::Chunk(chunk);
233 }
234 }
235 }
236 if let Some(chunk) = builder.take() {
237 yield Message::Chunk(chunk);
238 }
239 }
240 }
241 yield msg;
242 }
243 Either::Right(txn_msg) => {
244 match txn_msg {
246 TxnMsg::Begin(txn_id) => {
247 active_txn_map
248 .try_insert(txn_id, TxnBuffer::default())
249 .unwrap_or_else(|_| {
250 panic!("Transaction id collision txn_id = {}.", txn_id)
251 });
252 }
253 TxnMsg::End(txn_id, persistence_notifier) => {
254 if let Some(tx) = persistence_notifier {
255 pending_persistence_notifiers.push(tx);
256 }
257 let mut txn_buffer = active_txn_map.remove(&txn_id)
258 .unwrap_or_else(|| panic!("Receive an unexpected transaction end message. Active transaction map doesn't contain this transaction txn_id = {}.", txn_id));
259
260 let txn_buffer_cardinality = txn_buffer
261 .vec
262 .iter()
263 .map(|c| c.cardinality())
264 .sum::<usize>();
265 let batch_group_cardinality =
266 batch_group.iter().map(|c| c.cardinality()).sum::<usize>();
267
268 if txn_buffer_cardinality >= self.chunk_size {
269 if !batch_group.is_empty() {
271 let vec = mem::take(&mut batch_group);
272 for chunk in vec {
273 for (op, row) in chunk.rows() {
274 if let Some(chunk) = builder.append_row(op, row) {
275 yield Message::Chunk(chunk);
276 }
277 }
278 }
279 if let Some(chunk) = builder.take() {
280 yield Message::Chunk(chunk);
281 }
282 }
283
284 for chunk in txn_buffer.vec {
286 yield Message::Chunk(chunk);
287 }
288 } else if txn_buffer_cardinality + batch_group_cardinality
289 <= self.chunk_size
290 {
291 batch_group.extend(txn_buffer.vec);
293 } else {
294 if !batch_group.is_empty() {
296 let vec = mem::take(&mut batch_group);
297 for chunk in vec {
298 for (op, row) in chunk.rows() {
299 if let Some(chunk) = builder.append_row(op, row) {
300 yield Message::Chunk(chunk);
301 }
302 }
303 }
304 if let Some(chunk) = builder.take() {
305 yield Message::Chunk(chunk);
306 }
307 }
308
309 mem::swap(&mut txn_buffer.vec, &mut batch_group);
311 }
312 }
313 TxnMsg::Rollback(txn_id) => {
314 let txn_buffer = active_txn_map.remove(&txn_id)
315 .unwrap_or_else(|| panic!("Receive an unexpected transaction rollback message. Active transaction map doesn't contain this transaction txn_id = {}.", txn_id));
316 if txn_buffer.overflow {
317 tracing::warn!(
318 "txn_id={} large transaction tries to rollback, but part of its data has already been sent to the downstream.",
319 txn_id
320 );
321 }
322 }
323 TxnMsg::Data(txn_id, chunk) => {
324 match active_txn_map.get_mut(&txn_id) {
325 Some(txn_buffer) => {
326 if txn_buffer.overflow {
329 yield Message::Chunk(chunk);
330 continue;
331 }
332 txn_buffer.vec.push(chunk);
333 if txn_buffer.vec.len() > MAX_CHUNK_FOR_ATOMICITY {
334 tracing::warn!(
336 "txn_id={} Too many chunks for atomicity. Sent them to the downstream anyway.",
337 txn_id
338 );
339 for chunk in txn_buffer.vec.drain(..) {
340 yield Message::Chunk(chunk);
341 }
342 txn_buffer.overflow = true;
343 }
344 }
345 None => panic!(
346 "Receive an unexpected transaction data message. Active transaction map doesn't contain this transaction txn_id = {}.",
347 txn_id
348 ),
349 };
350 }
351 }
352 }
353 }
354 }
355 }
356}
357
358impl<S: StateStore> Execute for DmlExecutor<S> {
359 fn execute(self: Box<Self>) -> BoxedMessageStream {
360 self.execute_inner().boxed()
361 }
362}
363
364async fn next_input_driving_persistence(
368 stream: &mut StreamReaderWithPause<false, TxnMsg>,
369 persistence_futures: &mut FuturesOrdered<impl Future<Output = StreamExecutorResult<()>>>,
370) -> StreamExecutorResult<Option<Either<Message, TxnMsg>>> {
371 loop {
372 if persistence_futures.is_empty() {
373 return stream.next().await.transpose();
374 }
375
376 match select(persistence_futures.next(), stream.next()).await {
377 FutureEither::Left((Some(Ok(())), _)) => continue,
378 FutureEither::Left((Some(Err(err)), _)) => return Err(err),
379 FutureEither::Left((None, _)) => {
380 unreachable!("persistence_futures is known to be non-empty")
381 }
382 FutureEither::Right((stream_item, _)) => return stream_item.transpose(),
383 }
384 }
385}
386
387type BoxTxnMessageStream = BoxStream<'static, risingwave_dml::error::Result<TxnMsg>>;
388#[try_stream(ok = TxnMsg, error = risingwave_dml::error::DmlError)]
389async fn apply_dml_rate_limit(
390 stream: BoxTxnMessageStream,
391 rate_limiter: Arc<MonitoredRateLimiter>,
392) {
393 #[for_await]
394 for txn_msg in stream {
395 match txn_msg? {
396 TxnMsg::Begin(txn_id) => {
397 yield TxnMsg::Begin(txn_id);
398 }
399 TxnMsg::End(txn_id, persistence_notifier) => {
400 yield TxnMsg::End(txn_id, persistence_notifier);
401 }
402 TxnMsg::Rollback(txn_id) => {
403 yield TxnMsg::Rollback(txn_id);
404 }
405 TxnMsg::Data(txn_id, chunk) =>
406 {
407 #[for_await]
408 for chunk in rate_limited_pieces(&rate_limiter, chunk) {
409 yield TxnMsg::Data(txn_id, chunk);
410 }
411 }
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use std::sync::{Arc, Mutex};
419
420 use futures::FutureExt;
421 use risingwave_common::catalog::{ColumnId, Field, INITIAL_TABLE_VERSION_ID};
422 use risingwave_common::test_prelude::StreamChunkTestExt;
423 use risingwave_common::util::epoch::test_epoch;
424 use risingwave_dml::dml_manager::DmlManager;
425 use risingwave_hummock_sdk::key::TableKeyRange;
426 use risingwave_storage::error::StorageResult;
427 use risingwave_storage::memory::MemoryStateStore;
428 use risingwave_storage::panic_store::{PanicStateStore, PanicStateStoreIter};
429 use risingwave_storage::store::*;
430
431 use super::*;
432 use crate::executor::test_utils::MockSource;
433
434 const TEST_TRANSACTION_ID: TxnId = 0;
435 const TEST_SESSION_ID: u32 = 0;
436
437 type WaitEpochCallSender = oneshot::Sender<(HummockReadEpoch, TryWaitEpochOptions)>;
438
439 #[derive(Clone)]
440 struct MockWaitEpochStateStore {
441 wait_epoch_called_tx: Arc<Mutex<Option<WaitEpochCallSender>>>,
442 wait_epoch_release_rx: Arc<tokio::sync::Mutex<Option<oneshot::Receiver<()>>>>,
443 }
444
445 impl StateStoreReadLog for MockWaitEpochStateStore {
446 type ChangeLogIter = PanicStateStoreIter<StateStoreReadLogItem>;
447
448 async fn next_epoch(&self, _epoch: u64, _options: NextEpochOptions) -> StorageResult<u64> {
449 panic!("should not read changelog from MockWaitEpochStateStore")
450 }
451
452 async fn iter_log(
453 &self,
454 _epoch_range: (u64, u64),
455 _key_range: TableKeyRange,
456 _options: ReadLogOptions,
457 ) -> StorageResult<Self::ChangeLogIter> {
458 panic!("should not read changelog from MockWaitEpochStateStore")
459 }
460 }
461
462 impl StateStore for MockWaitEpochStateStore {
463 type Local = PanicStateStore;
464 type ReadSnapshot = PanicStateStore;
465 type VectorWriter = PanicStateStore;
466
467 async fn try_wait_epoch(
468 &self,
469 epoch: HummockReadEpoch,
470 options: TryWaitEpochOptions,
471 ) -> StorageResult<()> {
472 if let Some(tx) = self.wait_epoch_called_tx.lock().unwrap().take() {
473 assert!(tx.send((epoch, options)).is_ok());
474 }
475 let rx = self.wait_epoch_release_rx.lock().await.take().unwrap();
476 rx.await.unwrap();
477 Ok(())
478 }
479
480 async fn new_local(&self, _option: NewLocalOptions) -> Self::Local {
481 panic!("should not create local state from MockWaitEpochStateStore")
482 }
483
484 async fn new_read_snapshot(
485 &self,
486 _epoch: HummockReadEpoch,
487 _options: NewReadSnapshotOptions,
488 ) -> StorageResult<Self::ReadSnapshot> {
489 panic!("should not read snapshot from MockWaitEpochStateStore")
490 }
491
492 async fn new_vector_writer(&self, _options: NewVectorWriterOptions) -> Self::VectorWriter {
493 panic!("should not create vector writer from MockWaitEpochStateStore")
494 }
495 }
496
497 #[tokio::test]
498 async fn test_dml_executor() {
499 let table_id = TableId::default();
500 let schema = Schema::new(vec![
501 Field::unnamed(DataType::Int64),
502 Field::unnamed(DataType::Int64),
503 ]);
504 let column_descs = vec![
505 ColumnDesc::unnamed(ColumnId::new(0), DataType::Int64),
506 ColumnDesc::unnamed(ColumnId::new(1), DataType::Int64),
507 ];
508 let stream_key = vec![0];
509 let dml_manager = Arc::new(DmlManager::for_test());
510
511 let (mut tx, source) = MockSource::channel();
512 let source = source.into_executor(schema, stream_key);
513
514 let dml_executor = DmlExecutor::new(
515 ActorContext::for_test(0),
516 source,
517 dml_manager.clone(),
518 table_id,
519 INITIAL_TABLE_VERSION_ID,
520 column_descs,
521 1024,
522 RateLimit::Disabled,
523 MemoryStateStore::new(),
524 );
525 let mut dml_executor = dml_executor.boxed().execute();
526
527 let stream_chunk1 = StreamChunk::from_pretty(
528 " I I
529 + 1 1
530 + 2 2
531 + 3 6",
532 );
533 let stream_chunk2 = StreamChunk::from_pretty(
534 " I I
535 + 88 43",
536 );
537 let stream_chunk3 = StreamChunk::from_pretty(
538 " I I
539 + 199 40
540 + 978 72
541 + 134 41
542 + 398 98",
543 );
544 let batch_chunk = StreamChunk::from_pretty(
545 " I I
546 U+ 1 11
547 U+ 2 22",
548 );
549
550 tx.push_barrier(test_epoch(1), false);
552 let msg = dml_executor.next().await.unwrap().unwrap();
553 assert!(matches!(msg, Message::Barrier(_)));
554
555 tx.push_chunk(stream_chunk1);
557 tx.push_chunk(stream_chunk2);
558 tx.push_chunk(stream_chunk3);
559
560 let table_dml_handle = dml_manager
561 .table_dml_handle(table_id, INITIAL_TABLE_VERSION_ID)
562 .unwrap();
563 let mut write_handle = table_dml_handle
564 .write_handle(TEST_SESSION_ID, TEST_TRANSACTION_ID)
565 .unwrap();
566
567 write_handle.begin().unwrap();
569 write_handle.write_chunk(batch_chunk).await.unwrap();
570 tokio::spawn(async move {
573 write_handle.end().await.unwrap();
574 tx.push_barrier(test_epoch(2), false);
576 });
577
578 let msg = dml_executor.next().await.unwrap().unwrap();
580 assert_eq!(
581 msg.into_chunk().unwrap(),
582 StreamChunk::from_pretty(
583 " I I
584 + 1 1
585 + 2 2
586 + 3 6",
587 )
588 );
589
590 let msg = dml_executor.next().await.unwrap().unwrap();
597 assert_eq!(
598 msg.into_chunk().unwrap(),
599 StreamChunk::from_pretty(
600 " I I
601 + 88 43",
602 )
603 );
604
605 let msg = dml_executor.next().await.unwrap().unwrap();
609 assert_eq!(
610 msg.into_chunk().unwrap(),
611 StreamChunk::from_pretty(
612 " I I
613 + 199 40
614 + 978 72
615 + 134 41
616 + 398 98",
617 )
618 );
619
620 let msg = dml_executor.next().await.unwrap().unwrap();
622 assert_eq!(
623 msg.into_chunk().unwrap(),
624 StreamChunk::from_pretty(
625 " I I
626 U+ 1 11
627 U+ 2 22",
628 )
629 );
630
631 let msg = dml_executor.next().await.unwrap().unwrap();
632 assert!(matches!(msg, Message::Barrier(_)));
633 }
634
635 #[tokio::test]
636 async fn test_dml_executor_waits_for_barrier_prev_epoch_persistence() {
637 let table_id = TableId::new(233);
638 let schema = Schema::new(vec![Field::unnamed(DataType::Int64)]);
639 let column_descs = vec![ColumnDesc::unnamed(ColumnId::new(0), DataType::Int64)];
640 let stream_key = vec![0];
641 let dml_manager = Arc::new(DmlManager::for_test());
642 let (wait_epoch_called_tx, wait_epoch_called_rx) = oneshot::channel();
643 let (wait_epoch_release_tx, wait_epoch_release_rx) = oneshot::channel();
644
645 let (mut tx, source) = MockSource::channel();
646 let source = source.into_executor(schema, stream_key);
647 let dml_executor = DmlExecutor::new(
648 ActorContext::for_test(0),
649 source,
650 dml_manager.clone(),
651 table_id,
652 INITIAL_TABLE_VERSION_ID,
653 column_descs,
654 1024,
655 RateLimit::Disabled,
656 MockWaitEpochStateStore {
657 wait_epoch_called_tx: Arc::new(Mutex::new(Some(wait_epoch_called_tx))),
658 wait_epoch_release_rx: Arc::new(tokio::sync::Mutex::new(Some(
659 wait_epoch_release_rx,
660 ))),
661 },
662 );
663 let mut dml_executor = dml_executor.boxed().execute();
664
665 tx.push_barrier_with_prev_epoch_for_test(test_epoch(10), test_epoch(9), false);
666 let msg = dml_executor.next().await.unwrap().unwrap();
667 assert!(matches!(msg, Message::Barrier(_)));
668
669 let table_dml_handle = dml_manager
670 .table_dml_handle(table_id, INITIAL_TABLE_VERSION_ID)
671 .unwrap();
672 let mut write_handle = table_dml_handle
673 .write_handle(TEST_SESSION_ID, TEST_TRANSACTION_ID)
674 .unwrap();
675 write_handle.begin().unwrap();
676 write_handle
677 .write_chunk(StreamChunk::from_pretty(
678 " I
679 + 7",
680 ))
681 .await
682 .unwrap();
683
684 let mut persistence_future = Box::pin(write_handle.end_wait_persistence().unwrap());
685
686 assert!(dml_executor.next().now_or_never().is_none());
689
690 let drain_handle = tokio::spawn(async move {
691 while let Some(msg) = dml_executor.next().await {
692 let _ = msg.unwrap();
693 }
694 });
695
696 tx.push_barrier_with_prev_epoch_for_test(test_epoch(11), test_epoch(10), false);
697
698 let (wait_epoch, options) = wait_epoch_called_rx.await.unwrap();
699 assert!(matches!(
700 wait_epoch,
701 HummockReadEpoch::Committed(epoch) if epoch == test_epoch(10)
702 ));
703 assert_eq!(options.table_id, table_id);
704 assert!(persistence_future.as_mut().now_or_never().is_none());
705
706 wait_epoch_release_tx.send(()).unwrap();
707 persistence_future.await.unwrap();
708
709 drain_handle.abort();
710 }
711}