1use core::time::Duration;
16use std::fmt::Debug;
17use std::sync::Arc;
18
19use async_recursion::async_recursion;
20use futures::{FutureExt, TryFutureExt};
21use itertools::Itertools;
22use risingwave_common::bail;
23use risingwave_common::bitmap::Bitmap;
24use risingwave_common::catalog::{ColumnId, Field, Schema};
25use risingwave_common::config::{MetricLevel, StreamingConfig, merge_streaming_config_section};
26use risingwave_common::hash::VnodeBitmapExt;
27use risingwave_common::operator::{unique_executor_id, unique_operator_id};
28use risingwave_common::util::runtime::BackgroundShutdownRuntime;
29use risingwave_pb::id::{ExecutorId, GlobalOperatorId};
30use risingwave_pb::stream_plan::stream_node::NodeBody;
31use risingwave_pb::stream_plan::{
32 self, StreamNode, StreamScanNode, StreamScanType, SyncLogStoreNode,
33};
34use risingwave_pb::stream_service::inject_barrier_request::BuildActorInfo;
35use risingwave_storage::monitor::HummockTraceFutureExt;
36use risingwave_storage::table::batch_table::BatchTable;
37use risingwave_storage::{StateStore, dispatch_state_store};
38use thiserror_ext::AsReport;
39use tokio::sync::mpsc::UnboundedReceiver;
40use tokio::task::JoinHandle;
41
42use crate::common::table::state_table::StateTableBuilder;
43use crate::error::StreamResult;
44use crate::executor::monitor::StreamingMetrics;
45use crate::executor::subtask::SubtaskHandle;
46use crate::executor::{
47 Actor, ActorContext, ActorContextRef, DispatchExecutor, Execute, Executor, ExecutorInfo,
48 MergeExecutorInput, SnapshotBackfillExecutor, SyncLogStoreDispatchExecutor,
49 TroublemakerExecutor, WrapperExecutor,
50};
51use crate::from_proto::{MergeExecutorBuilder, create_executor};
52use crate::task::{
53 ActorEvalErrorReport, ActorId, AtomicU64Ref, FragmentId, LocalBarrierManager, NewOutputRequest,
54 StreamEnvironment, await_tree_key,
55};
56
57fn actor_vnode_bitmap(actor: &BuildActorInfo) -> Bitmap {
59 actor
60 .vnode_bitmap
61 .as_ref()
62 .map_or_else(|| Bitmap::singleton().clone(), Bitmap::from)
63}
64
65pub const CONFIG_OVERRIDE_CACHE_DEFAULT_CAPACITY: u64 = 256;
68pub type ConfigOverrideCache = moka::sync::Cache<String, Arc<StreamingConfig>>;
69
70pub(crate) struct StreamActorManager {
74 pub(super) env: StreamEnvironment,
75 pub(super) streaming_metrics: Arc<StreamingMetrics>,
76
77 pub(super) watermark_epoch: AtomicU64Ref,
79
80 pub(super) await_tree_reg: Option<await_tree::Registry>,
82
83 pub(super) runtime: BackgroundShutdownRuntime,
85
86 pub(super) config_override_cache: ConfigOverrideCache,
91}
92
93impl StreamActorManager {
94 fn get_executor_id(actor_context: &ActorContext, node: &StreamNode) -> ExecutorId {
95 unique_executor_id(actor_context.id, node.operator_id)
98 }
99
100 fn get_executor_info(node: &StreamNode, executor_id: ExecutorId) -> ExecutorInfo {
101 let schema: Schema = node.fields.iter().map(Field::from).collect();
102
103 let stream_key = node
104 .get_stream_key()
105 .iter()
106 .map(|idx| *idx as usize)
107 .collect::<Vec<_>>();
108
109 let stream_kind = node.stream_kind();
110
111 let identity = format!("{} {:X}", node.get_node_body().unwrap(), executor_id);
112
113 ExecutorInfo {
114 schema,
115 stream_key,
116 stream_kind,
117 identity,
118 id: executor_id,
119 }
120 }
121
122 async fn create_merge_executor_allow_empty_upstream(
123 &self,
124 stream_node: &StreamNode,
125 actor_context: &ActorContextRef,
126 local_barrier_manager: &LocalBarrierManager,
127 ) -> StreamResult<MergeExecutorInput> {
128 let info = Self::get_executor_info(
129 stream_node,
130 Self::get_executor_id(actor_context, stream_node),
131 );
132 let NodeBody::Merge(merge_node) = stream_node.get_node_body()? else {
133 bail!("expected Merge input, got {}", stream_node.get_identity());
134 };
135 MergeExecutorBuilder::new_input_allow_empty_upstream(
136 local_barrier_manager.clone(),
137 self.streaming_metrics.clone(),
138 actor_context.clone(),
139 info,
140 merge_node,
141 actor_context.config.developer.chunk_size,
142 )
143 .await
144 }
145
146 async fn create_snapshot_backfill_node(
147 &self,
148 stream_node: &StreamNode,
149 node: &StreamScanNode,
150 actor_context: &ActorContextRef,
151 vnode_bitmap: Bitmap,
152 local_barrier_manager: &LocalBarrierManager,
153 state_store: impl StateStore,
154 ) -> StreamResult<Executor> {
155 let [upstream_node, _]: &[_; 2] = stream_node.input.as_slice().try_into().unwrap();
156 let chunk_size = actor_context.config.developer.chunk_size;
157 let upstream = self
158 .create_merge_executor_allow_empty_upstream(
159 upstream_node,
160 actor_context,
161 local_barrier_manager,
162 )
163 .await?;
164
165 let table_desc = node.get_table_desc()?;
166
167 let output_indices = node
168 .output_indices
169 .iter()
170 .map(|&i| i as usize)
171 .collect_vec();
172 let info = Self::get_executor_info(
173 stream_node,
174 Self::get_executor_id(actor_context, stream_node),
175 );
176 let stream_key = info.stream_key.clone();
177
178 let column_ids = node
179 .upstream_column_ids
180 .iter()
181 .map(ColumnId::from)
182 .collect_vec();
183
184 let progress = local_barrier_manager.register_create_mview_progress(actor_context);
185
186 let vnodes = Arc::new(vnode_bitmap);
187 let upstream_table = BatchTable::new_partial(
188 state_store.clone(),
189 column_ids,
190 Some(vnodes.clone()),
191 table_desc,
192 );
193
194 let state_table = node.get_state_table()?;
195 let state_table = StateTableBuilder::new(state_table, state_store.clone(), Some(vnodes))
196 .enable_preload_all_rows_by_config(&actor_context.config)
197 .build()
198 .await;
199
200 let executor = SnapshotBackfillExecutor::new(
201 upstream_table,
202 state_table,
203 upstream,
204 node.pk_scan_range.as_ref(),
205 output_indices,
206 stream_key,
207 actor_context.clone(),
208 progress,
209 chunk_size,
210 node.rate_limit.into(),
211 self.streaming_metrics.clone(),
212 node.snapshot_backfill_epoch,
213 )?
214 .boxed();
215
216 if crate::consistency::insane() {
217 let mut troubled_info = info.clone();
218 troubled_info.identity = format!("{} (troubled)", info.identity);
219 Ok((
220 info,
221 TroublemakerExecutor::new((troubled_info, executor).into(), chunk_size),
222 )
223 .into())
224 } else {
225 Ok((info, executor).into())
226 }
227 }
228
229 #[expect(clippy::too_many_arguments)]
231 #[async_recursion]
232 async fn create_nodes_inner(
233 &self,
234 fragment_id: FragmentId,
235 node: &stream_plan::StreamNode,
236 env: StreamEnvironment,
237 store: impl StateStore,
238 actor_context: &ActorContextRef,
239 vnode_bitmap: Bitmap,
240 has_stateful: bool,
241 subtasks: &mut Vec<SubtaskHandle>,
242 local_barrier_manager: &LocalBarrierManager,
243 ) -> StreamResult<Executor> {
244 fn is_stateful_executor(stream_node: &StreamNode) -> bool {
247 matches!(
248 stream_node.get_node_body().unwrap(),
249 NodeBody::HashAgg(_)
250 | NodeBody::HashJoin(_)
251 | NodeBody::DeltaIndexJoin(_)
252 | NodeBody::Lookup(_)
253 | NodeBody::StreamScan(_)
254 | NodeBody::StreamCdcScan(_)
255 | NodeBody::DynamicFilter(_)
256 | NodeBody::GroupTopN(_)
257 | NodeBody::Now(_)
258 )
259 }
260 let is_stateful = is_stateful_executor(node);
261
262 let executor = if let NodeBody::StreamScan(stream_scan) = node.get_node_body().unwrap()
263 && let Ok(StreamScanType::SnapshotBackfill) = stream_scan.get_stream_scan_type()
264 {
265 dispatch_state_store!(env.state_store(), store, {
266 self.create_snapshot_backfill_node(
267 node,
268 stream_scan,
269 actor_context,
270 vnode_bitmap,
271 local_barrier_manager,
272 store,
273 )
274 .await
275 })?
276 } else if matches!(
277 node.get_node_body().unwrap(),
278 NodeBody::IcebergWithPkIndexWriter(_)
279 ) {
280 let [first_merge_node, second_merge_node] = node.input.as_slice() else {
283 bail!("IcebergWithPkIndexWriter requires exactly two Merge inputs");
284 };
285 let merge_nodes = [first_merge_node, second_merge_node];
286 let mut input = Vec::with_capacity(merge_nodes.len());
287 for merge_node in merge_nodes {
288 let merge_input = self
289 .create_merge_executor_allow_empty_upstream(
290 merge_node,
291 actor_context,
292 local_barrier_manager,
293 )
294 .await?;
295 let merge_executor = merge_input.into_executor();
296 input.push(Self::wrap_executor(
297 merge_executor,
298 actor_context,
299 has_stateful || is_stateful,
300 subtasks,
301 ));
302 }
303 self.generate_executor_from_inputs(
304 fragment_id,
305 node,
306 env,
307 store,
308 actor_context,
309 vnode_bitmap,
310 local_barrier_manager,
311 input,
312 )
313 .await?
314 } else {
315 let mut input = Vec::with_capacity(node.input.iter().len());
317 for input_stream_node in &node.input {
318 input.push(
319 self.create_nodes_inner(
320 fragment_id,
321 input_stream_node,
322 env.clone(),
323 store.clone(),
324 actor_context,
325 vnode_bitmap.clone(),
326 has_stateful || is_stateful,
327 subtasks,
328 local_barrier_manager,
329 )
330 .await?,
331 );
332 }
333
334 self.generate_executor_from_inputs(
335 fragment_id,
336 node,
337 env,
338 store,
339 actor_context,
340 vnode_bitmap,
341 local_barrier_manager,
342 input,
343 )
344 .await?
345 };
346 Ok(Self::wrap_executor(
347 executor,
348 actor_context,
349 has_stateful || is_stateful,
350 subtasks,
351 ))
352 }
353
354 #[expect(clippy::too_many_arguments)]
355 async fn generate_executor_from_inputs(
356 &self,
357 fragment_id: FragmentId,
358 node: &stream_plan::StreamNode,
359 env: StreamEnvironment,
360 store: impl StateStore,
361 actor_context: &ActorContextRef,
362 vnode_bitmap: Bitmap,
363 local_barrier_manager: &LocalBarrierManager,
364 input: Vec<Executor>,
365 ) -> StreamResult<Executor> {
366 let op_info = node.get_identity().clone();
367
368 let executor_id = Self::get_executor_id(actor_context, node);
371 let operator_id = unique_operator_id(fragment_id, node.operator_id);
372
373 let info = Self::get_executor_info(node, executor_id);
374
375 let eval_error_report = ActorEvalErrorReport {
376 actor_context: actor_context.clone(),
377 identity: info.identity.clone().into(),
378 };
379
380 let executor_params = ExecutorParams {
382 env: env.clone(),
383
384 info: info.clone(),
385 executor_id,
386 operator_id,
387 op_info,
388 input,
389 fragment_id,
390 executor_stats: self.streaming_metrics.clone(),
391 actor_context: actor_context.clone(),
392 vnode_bitmap,
393 eval_error_report,
394 watermark_epoch: self.watermark_epoch.clone(),
395 local_barrier_manager: local_barrier_manager.clone(),
396 config: actor_context.config.clone(),
397 };
398
399 create_executor(executor_params, node, store).await
400 }
401
402 fn wrap_executor(
403 executor: Executor,
404 actor_context: &ActorContextRef,
405 has_stateful: bool,
406 subtasks: &mut Vec<SubtaskHandle>,
407 ) -> Executor {
408 let info = executor.info().clone();
409 let wrapped = WrapperExecutor::new(executor, actor_context.clone());
411 let executor = (info, wrapped).into();
412
413 if has_stateful {
415 let _ = subtasks;
421 }
422 executor
423 }
424
425 async fn create_nodes(
427 &self,
428 fragment_id: FragmentId,
429 node: &stream_plan::StreamNode,
430 env: StreamEnvironment,
431 actor_context: &ActorContextRef,
432 vnode_bitmap: Bitmap,
433 local_barrier_manager: &LocalBarrierManager,
434 ) -> StreamResult<(Executor, Vec<SubtaskHandle>)> {
435 let mut subtasks = vec![];
436
437 let executor = dispatch_state_store!(env.state_store(), store, {
438 self.create_nodes_inner(
439 fragment_id,
440 node,
441 env,
442 store,
443 actor_context,
444 vnode_bitmap,
445 false,
446 &mut subtasks,
447 local_barrier_manager,
448 )
449 .await
450 })?;
451
452 Ok((executor, subtasks))
453 }
454
455 fn get_overridden_config(
457 &self,
458 config_override: &str,
459 actor_id: ActorId,
460 ) -> Arc<StreamingConfig> {
461 self.config_override_cache
462 .get_with_by_ref(config_override, || {
463 let global = self.env.global_config();
464 match merge_streaming_config_section(global.as_ref(), config_override) {
465 Ok(Some(config)) => {
466 tracing::info!(%actor_id, "applied configuration override");
467 Arc::new(config)
468 }
469 Ok(None) => global.clone(), Err(e) => {
471 tracing::error!(
475 error = %e.as_report(),
476 %actor_id,
477 "failed to apply configuration override, use global config instead",
478 );
479 global.clone()
480 }
481 }
482 })
483 }
484
485 async fn create_actor(
486 self: Arc<Self>,
487 actor: BuildActorInfo,
488 fragment_id: FragmentId,
489 node: Arc<StreamNode>,
490 local_barrier_manager: LocalBarrierManager,
491 new_output_request_rx: UnboundedReceiver<(ActorId, NewOutputRequest)>,
492 actor_config: Arc<StreamingConfig>,
493 ) -> StreamResult<Actor<DispatchExecutor>> {
494 let expr_context = actor.expr_context.clone().unwrap();
495 let actor_context = ActorContext::create(
496 &actor,
497 fragment_id,
498 self.env.total_mem_usage(),
499 self.streaming_metrics.clone(),
500 self.env.meta_client(),
501 actor_config,
502 self.env.clone(),
503 );
504 let vnode_bitmap = actor_vnode_bitmap(&actor);
505
506 let (executor, subtasks) = self
507 .create_nodes(
508 fragment_id,
509 &node,
510 self.env.clone(),
511 &actor_context,
512 vnode_bitmap,
513 &local_barrier_manager,
514 )
515 .await?;
516
517 let dispatcher = DispatchExecutor::new(
518 executor,
519 new_output_request_rx,
520 actor.dispatchers,
521 &actor_context,
522 )
523 .await?;
524
525 let actor = Actor::new(
526 dispatcher,
527 subtasks,
528 self.streaming_metrics.clone(),
529 actor_context.clone(),
530 expr_context,
531 local_barrier_manager,
532 );
533 Ok(actor)
534 }
535
536 #[expect(clippy::too_many_arguments)]
537 async fn create_actor_with_log_store_dispatcher<S: StateStore>(
538 self: Arc<Self>,
539 actor: BuildActorInfo,
540 fragment_id: FragmentId,
541 node: Arc<StreamNode>,
542 local_barrier_manager: LocalBarrierManager,
543 new_output_request_rx: UnboundedReceiver<(ActorId, NewOutputRequest)>,
544 actor_config: Arc<StreamingConfig>,
545 sync: Box<SyncLogStoreNode>,
546 state_store: S,
547 ) -> StreamResult<Actor<SyncLogStoreDispatchExecutor<S>>> {
548 let expr_context = actor.expr_context.clone().unwrap();
549 let actor_context = ActorContext::create(
550 &actor,
551 fragment_id,
552 self.env.total_mem_usage(),
553 self.streaming_metrics.clone(),
554 self.env.meta_client(),
555 actor_config,
556 self.env.clone(),
557 );
558 let vnode_bitmap = actor_vnode_bitmap(&actor);
559
560 let [input] = node.input.as_slice() else {
561 bail!("SyncLogStoreNode should have exactly one input");
562 };
563
564 let (executor, subtasks) = self
565 .create_nodes(
566 fragment_id,
567 input,
568 self.env.clone(),
569 &actor_context,
570 vnode_bitmap.clone(),
571 &local_barrier_manager,
572 )
573 .await?;
574
575 let dispatcher = SyncLogStoreDispatchExecutor::new(
576 executor,
577 new_output_request_rx,
578 actor.dispatchers,
579 &actor_context,
580 sync.as_ref(),
581 vnode_bitmap,
582 state_store,
583 )
584 .await?;
585
586 let actor = Actor::new(
587 dispatcher,
588 subtasks,
589 self.streaming_metrics.clone(),
590 actor_context.clone(),
591 expr_context,
592 local_barrier_manager,
593 );
594 Ok(actor)
595 }
596
597 pub(super) fn spawn_actor(
598 self: &Arc<Self>,
599 actor: BuildActorInfo,
600 fragment_id: FragmentId,
601 node: Arc<StreamNode>,
602 local_barrier_manager: LocalBarrierManager,
603 new_output_request_rx: UnboundedReceiver<(ActorId, NewOutputRequest)>,
604 ) -> (JoinHandle<()>, Option<JoinHandle<()>>) {
605 let stream_actor_ref = &actor;
606 let actor_id = stream_actor_ref.actor_id;
607 let actor_config = self.get_overridden_config(&actor.config_override, actor_id);
608
609 let monitor = actor_config
610 .developer
611 .enable_actor_tokio_metrics
612 .then(tokio_metrics::TaskMonitor::new);
613
614 let handle = {
615 let trace_span = format!("Actor {actor_id}: `{}`", stream_actor_ref.mview_definition);
616 let barrier_manager = local_barrier_manager;
617 let node_body = node.get_node_body().unwrap().clone();
618 let use_sync_log_store_dispatcher =
619 !actor_config.developer.disable_sync_log_store_dispatcher;
620 let actor = match node_body {
622 NodeBody::SyncLogStore(sync) if use_sync_log_store_dispatcher => {
623 tracing::info!(
624 "SyncLogStoreDispatchExecutor is created for fragment {}",
625 fragment_id
626 );
627 dispatch_state_store!(self.env.state_store(), store, {
628 self
629 .clone()
630 .create_actor_with_log_store_dispatcher(
631 actor,
632 fragment_id,
633 node,
634 barrier_manager.clone(),
635 new_output_request_rx,
636 actor_config,
637 sync,
638 store,
639 )
640 .and_then(|actor| actor.run())
641 .map(move |result| {
642 if let Err(err) = result {
643 tracing::error!(%actor_id, error = ?err.as_report(), "actor exit with error");
644 barrier_manager.notify_failure(actor_id, err);
645 }
646 })
647 .boxed()
648 })
649 }
650 _ => {
651 self
652 .clone()
653 .create_actor(
654 actor,
655 fragment_id,
656 node,
657 barrier_manager.clone(),
658 new_output_request_rx,
659 actor_config,
660 )
661 .and_then(|actor| actor.run())
662 .map(move |result| {
663 if let Err(err) = result {
664 tracing::error!(%actor_id, error = ?err.as_report(), "actor exit with error");
667 barrier_manager.notify_failure(actor_id, err);
668 }
669 })
670 .boxed()
671 }
672 };
673 let traced = match &self.await_tree_reg {
674 Some(m) => m
675 .register(await_tree_key::Actor(actor_id), trace_span)
676 .instrument(actor)
677 .left_future(),
678 None => actor.right_future(),
679 };
680 let instrumented = match &monitor {
681 Some(m) => m.instrument(traced).left_future(),
682 None => traced.right_future(),
683 };
684 let may_track_hummock = instrumented.may_trace_hummock();
686
687 self.runtime.spawn(may_track_hummock)
688 };
689
690 let enable_count_metrics = self.streaming_metrics.level >= MetricLevel::Debug;
691 let monitor_handle = if let Some(monitor) = monitor {
692 let streaming_metrics = self.streaming_metrics.clone();
693 let actor_monitor_task = self.runtime.spawn(async move {
694 let metrics = streaming_metrics.new_actor_metrics(actor_id, fragment_id);
695 let mut interval = tokio::time::interval(Duration::from_secs(15));
696 for task_metrics in monitor.intervals() {
697 interval.tick().await; metrics
699 .actor_poll_duration
700 .inc_by(task_metrics.total_poll_duration.as_nanos() as u64);
701 metrics
702 .actor_idle_duration
703 .inc_by(task_metrics.total_idle_duration.as_nanos() as u64);
704 metrics
705 .actor_scheduled_duration
706 .inc_by(task_metrics.total_scheduled_duration.as_nanos() as u64);
707
708 if enable_count_metrics {
709 metrics.actor_poll_cnt.inc_by(task_metrics.total_poll_count);
710 metrics
711 .actor_idle_cnt
712 .inc_by(task_metrics.total_idled_count);
713 metrics
714 .actor_scheduled_cnt
715 .inc_by(task_metrics.total_scheduled_count);
716 }
717 }
718 });
719 Some(actor_monitor_task)
720 } else {
721 None
722 };
723 (handle, monitor_handle)
724 }
725}
726
727pub struct ExecutorParams {
731 pub env: StreamEnvironment,
732
733 pub info: ExecutorInfo,
735
736 pub executor_id: ExecutorId,
738
739 pub operator_id: GlobalOperatorId,
741
742 pub op_info: String,
745
746 pub input: Vec<Executor>,
748
749 pub fragment_id: FragmentId,
751
752 pub executor_stats: Arc<StreamingMetrics>,
754
755 pub actor_context: ActorContextRef,
757
758 pub vnode_bitmap: Bitmap,
760
761 pub eval_error_report: ActorEvalErrorReport,
763
764 pub watermark_epoch: AtomicU64Ref,
766
767 pub local_barrier_manager: LocalBarrierManager,
768
769 pub config: Arc<StreamingConfig>,
773}
774
775impl Debug for ExecutorParams {
776 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
777 f.debug_struct("ExecutorParams")
778 .field("info", &self.info)
779 .field("executor_id", &self.executor_id)
780 .field("operator_id", &self.operator_id)
781 .field("op_info", &self.op_info)
782 .field("input", &self.input.len())
783 .field("actor_id", &self.actor_context.id)
784 .finish_non_exhaustive()
785 }
786}