1use std::collections::hash_map::Entry;
16use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
17use std::sync::Arc;
18
19use anyhow::{Context, anyhow};
20use futures::TryStreamExt;
21use itertools::{Either, Itertools};
22use risingwave_common::bail;
23use risingwave_common::bitmap::Bitmap;
24use risingwave_common::catalog::{FragmentTypeFlag, FragmentTypeMask};
25use risingwave_common::hash::{VnodeCount, VnodeCountCompat};
26use risingwave_common::id::JobId;
27use risingwave_common::system_param::AdaptiveParallelismStrategy;
28use risingwave_common::system_param::adaptive_parallelism_strategy::parse_strategy;
29use risingwave_common::util::stream_graph_visitor::visit_stream_node_body;
30use risingwave_connector::source::SplitImpl;
31use risingwave_connector::source::cdc::CdcScanOptions;
32use risingwave_meta_model::fragment::DistributionType;
33use risingwave_meta_model::object::ObjectType;
34use risingwave_meta_model::prelude::{
35 Fragment as FragmentModel, FragmentRelation, FragmentSplits, Sink, StreamingJob,
36};
37use risingwave_meta_model::{
38 ActorId, ConnectorSplits, DatabaseId, DispatcherType, ExprContext, FragmentId, I32Array,
39 JobStatus, ObjectId, SchemaId, SinkId, SourceId, StreamNode, StreamingParallelism, TableId,
40 TableIdArray, VnodeBitmap, WorkerId, database, fragment, fragment_relation, fragment_splits,
41 object, sink, source, streaming_job, table,
42};
43use risingwave_meta_model_migration::{ExprTrait, OnConflict, SimpleExpr};
44use risingwave_pb::catalog::PbTable;
45use risingwave_pb::common::PbActorLocation;
46use risingwave_pb::meta::subscribe_response::{
47 Info as NotificationInfo, Operation as NotificationOperation,
48};
49use risingwave_pb::meta::table_fragments::fragment::{
50 FragmentDistributionType, PbFragmentDistributionType,
51};
52use risingwave_pb::meta::table_fragments::{PbActorStatus, PbState};
53use risingwave_pb::meta::{FragmentDistribution, PbFragmentWorkerSlotMapping};
54use risingwave_pb::source::{ConnectorSplit, PbConnectorSplits};
55use risingwave_pb::stream_plan;
56use risingwave_pb::stream_plan::stream_node::NodeBody;
57use risingwave_pb::stream_plan::{
58 PbDispatchOutputMapping, PbDispatcherType, PbStreamNode, PbStreamScanType, SinkLogStoreType,
59 StreamScanType,
60};
61use sea_orm::ActiveValue::Set;
62use sea_orm::sea_query::Expr;
63use sea_orm::{
64 ColumnTrait, ConnectionTrait, DatabaseTransaction, EntityTrait, FromQueryResult, JoinType,
65 PaginatorTrait, QueryFilter, QuerySelect, RelationTrait, StreamTrait, TransactionTrait,
66};
67use serde::{Deserialize, Serialize};
68
69use crate::barrier::{SharedActorInfos, SharedFragmentInfo, SnapshotBackfillInfo};
70use crate::controller::catalog::{CatalogController, CatalogControllerInner};
71use crate::controller::scale::{
72 FragmentRenderMap, LoadedFragmentContext, NoShuffleEnsemble, RenderedGraph,
73 find_fragment_no_shuffle_dags_detailed, load_fragment_context_for_jobs,
74 render_actor_assignments, resolve_streaming_job_definition,
75};
76use crate::controller::utils::{
77 FragmentDesc, PartialActorLocation, PartialFragmentStateTables, compose_dispatchers,
78 get_sink_fragment_by_ids, has_table_been_migrated, rebuild_fragment_mapping,
79 resolve_no_shuffle_actor_mapping,
80};
81use crate::error::MetaError;
82use crate::manager::{ActiveStreamingWorkerNodes, LocalNotification, NotificationManager};
83use crate::model::{
84 DownstreamFragmentRelation, Fragment, FragmentActorDispatchers, FragmentDownstreamRelation,
85 StreamActor, StreamContext, StreamJobFragments, StreamingJobModelContextExt as _,
86};
87use crate::rpc::ddl_controller::build_upstream_sink_info;
88use crate::stream::UpstreamSinkInfo;
89use crate::{MetaResult, model};
90
91#[derive(Debug)]
93pub struct InflightActorInfo {
94 pub worker_id: WorkerId,
95 pub vnode_bitmap: Option<Bitmap>,
96 pub splits: Vec<SplitImpl>,
97}
98
99#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
100struct ActorInfo {
101 pub actor_id: ActorId,
102 pub fragment_id: FragmentId,
103 pub splits: ConnectorSplits,
104 pub worker_id: WorkerId,
105 pub vnode_bitmap: Option<VnodeBitmap>,
106 pub expr_context: ExprContext,
107 pub config_override: Arc<str>,
108}
109
110#[derive(Debug)]
111struct FragmentDescRow {
112 fragment_id: FragmentId,
113 job_id: JobId,
114 fragment_type_mask: i32,
115 distribution_type: DistributionType,
116 state_table_ids: TableIdArray,
117 vnode_count: i32,
118 parallelism_override: Option<StreamingParallelism>,
119 stream_node: Option<StreamNode>,
120}
121
122#[derive(Debug)]
123pub struct InflightFragmentInfo {
124 pub fragment_id: FragmentId,
125 pub distribution_type: DistributionType,
126 pub fragment_type_mask: FragmentTypeMask,
127 pub vnode_count: usize,
128 pub nodes: PbStreamNode,
129 pub actors: HashMap<ActorId, InflightActorInfo>,
130 pub state_table_ids: HashSet<TableId>,
131}
132
133#[derive(Clone, Debug)]
134pub struct FragmentServingInfo {
135 pub result_table_id: Option<TableId>,
138 pub distribution_type: FragmentDistributionType,
139 pub vnode_count: usize,
140}
141
142#[easy_ext::ext(FragmentTypeMaskExt)]
143pub impl FragmentTypeMask {
144 fn contains_non_recoverable_fragment() -> SimpleExpr {
146 Self::intersects(FragmentTypeFlag::Values)
147 }
148
149 fn intersects(flag: FragmentTypeFlag) -> SimpleExpr {
150 Expr::col(fragment::Column::FragmentTypeMask)
151 .bit_and(Expr::value(flag as i32))
152 .ne(0)
153 }
154
155 fn intersects_any(flags: impl IntoIterator<Item = FragmentTypeFlag>) -> SimpleExpr {
156 Expr::col(fragment::Column::FragmentTypeMask)
157 .bit_and(Expr::value(FragmentTypeFlag::raw_flag(flags) as i32))
158 .ne(0)
159 }
160
161 fn disjoint(flag: FragmentTypeFlag) -> SimpleExpr {
162 Expr::col(fragment::Column::FragmentTypeMask)
163 .bit_and(Expr::value(flag as i32))
164 .eq(0)
165 }
166}
167
168#[derive(Clone, Debug, FromQueryResult, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")] pub struct StreamingJobInfo {
171 pub job_id: JobId,
172 pub obj_type: ObjectType,
173 pub name: String,
174 pub job_status: JobStatus,
175 pub parallelism: StreamingParallelism,
176 pub adaptive_parallelism_strategy: Option<String>,
177 #[serde(skip)]
181 pub backfill_parallelism: Option<StreamingParallelism>,
182 #[serde(skip)]
183 pub backfill_adaptive_parallelism_strategy: Option<String>,
184 pub max_parallelism: i32,
185 pub resource_group: String,
186 pub config_override: String,
187 pub database_id: DatabaseId,
188 pub schema_id: SchemaId,
189}
190
191impl NotificationManager {
192 pub(crate) fn notify_streaming_fragment_mapping(
198 &self,
199 operation: NotificationOperation,
200 fragment_mappings: Vec<PbFragmentWorkerSlotMapping>,
201 ) {
202 if fragment_mappings.is_empty() {
203 return;
204 }
205 for fragment_mapping in fragment_mappings {
207 self.notify_frontend_without_version(
208 operation,
209 NotificationInfo::StreamingWorkerSlotMapping(fragment_mapping),
210 );
211 }
212 }
213
214 pub(crate) fn notify_serving_fragment_mapping_update(
219 &self,
220 fragment_ids: Vec<crate::model::FragmentId>,
221 ) {
222 if fragment_ids.is_empty() {
223 return;
224 }
225 self.notify_local_subscribers(LocalNotification::ServingFragmentMappingsUpsert(
226 fragment_ids,
227 ));
228 }
229
230 pub(crate) fn notify_serving_fragment_mapping_delete(
235 &self,
236 fragment_ids: Vec<crate::model::FragmentId>,
237 ) {
238 if fragment_ids.is_empty() {
239 return;
240 }
241 self.notify_local_subscribers(LocalNotification::ServingFragmentMappingsDelete(
242 fragment_ids,
243 ));
244 }
245}
246
247impl CatalogControllerInner {
248 pub async fn fragment_serving_infos(
254 &self,
255 ) -> MetaResult<HashMap<FragmentId, FragmentServingInfo>> {
256 let query = FragmentModel::find().select_only().columns([
257 fragment::Column::FragmentId,
258 fragment::Column::JobId,
259 fragment::Column::DistributionType,
260 fragment::Column::VnodeCount,
261 fragment::Column::StateTableIds,
262 ]);
263 let fragments: Vec<(FragmentId, JobId, DistributionType, i32, TableIdArray)> =
264 query.into_tuple().all(&self.db).await?;
265
266 Ok(fragments
267 .into_iter()
268 .map(
269 |(fragment_id, job_id, distribution_type, vnode_count, state_table_ids)| {
270 let result_table_id = job_id.as_mv_table_id();
273 (
274 fragment_id,
275 FragmentServingInfo {
276 result_table_id: state_table_ids
277 .0
278 .contains(&result_table_id)
279 .then_some(result_table_id),
280 distribution_type: PbFragmentDistributionType::from(distribution_type),
281 vnode_count: vnode_count as usize,
282 },
283 )
284 },
285 )
286 .collect())
287 }
288}
289
290impl CatalogController {
291 pub fn prepare_fragment_models_from_fragments(
292 job_id: JobId,
293 fragments: impl Iterator<Item = &Fragment>,
294 ) -> MetaResult<Vec<fragment::Model>> {
295 fragments
296 .map(|fragment| Self::prepare_fragment_model_for_new_job(job_id, fragment))
297 .try_collect()
298 }
299
300 pub fn prepare_fragment_model_for_new_job(
301 job_id: JobId,
302 fragment: &Fragment,
303 ) -> MetaResult<fragment::Model> {
304 let vnode_count = fragment.vnode_count();
305 let Fragment {
306 fragment_id: pb_fragment_id,
307 fragment_type_mask: pb_fragment_type_mask,
308 distribution_type: pb_distribution_type,
309 state_table_ids: pb_state_table_ids,
310 nodes,
311 ..
312 } = fragment;
313
314 let state_table_ids = pb_state_table_ids.clone().into();
315
316 let fragment_parallelism = nodes
317 .node_body
318 .as_ref()
319 .and_then(|body| match body {
320 NodeBody::StreamCdcScan(node) => Some(node),
321 _ => None,
322 })
323 .and_then(|node| node.options.as_ref())
324 .map(CdcScanOptions::from_proto)
325 .filter(|opts| opts.is_parallelized_backfill())
326 .map(|opts| StreamingParallelism::Fixed(opts.backfill_parallelism as usize));
327
328 let stream_node = StreamNode::from(nodes);
329
330 let distribution_type = PbFragmentDistributionType::try_from(*pb_distribution_type)
331 .unwrap()
332 .into();
333
334 #[expect(deprecated)]
335 let fragment = fragment::Model {
336 fragment_id: *pb_fragment_id as _,
337 job_id,
338 fragment_type_mask: (*pb_fragment_type_mask).into(),
339 distribution_type,
340 stream_node,
341 state_table_ids,
342 upstream_fragment_id: Default::default(),
343 vnode_count: vnode_count as _,
344 parallelism: fragment_parallelism,
345 };
346
347 Ok(fragment)
348 }
349
350 #[expect(clippy::type_complexity)]
351 fn compose_table_fragments(
352 job_id: JobId,
353 state: PbState,
354 ctx: StreamContext,
355 fragments: Vec<(fragment::Model, Vec<ActorInfo>)>,
356 max_parallelism: usize,
357 job_definition: Option<String>,
358 ) -> MetaResult<(
359 StreamJobFragments,
360 HashMap<FragmentId, Vec<StreamActor>>,
361 HashMap<crate::model::ActorId, PbActorStatus>,
362 )> {
363 let mut pb_fragments = BTreeMap::new();
364 let mut fragment_actors = HashMap::new();
365 let mut all_actor_status = HashMap::new();
366
367 for (fragment, actors) in fragments {
368 let (fragment, actors, actor_status, _) =
369 Self::compose_fragment(fragment, actors, job_definition.clone())?;
370 let fragment_id = fragment.fragment_id;
371 fragment_actors.insert(fragment_id, actors);
372 all_actor_status.extend(actor_status);
373
374 pb_fragments.insert(fragment_id, fragment);
375 }
376
377 let table_fragments = StreamJobFragments {
378 stream_job_id: job_id,
379 state: state as _,
380 fragments: pb_fragments,
381 ctx,
382 max_parallelism,
383 };
384
385 Ok((table_fragments, fragment_actors, all_actor_status))
386 }
387
388 #[expect(clippy::type_complexity)]
389 fn compose_fragment(
390 fragment: fragment::Model,
391 actors: Vec<ActorInfo>,
392 job_definition: Option<String>,
393 ) -> MetaResult<(
394 Fragment,
395 Vec<StreamActor>,
396 HashMap<crate::model::ActorId, PbActorStatus>,
397 HashMap<crate::model::ActorId, PbConnectorSplits>,
398 )> {
399 let fragment::Model {
400 fragment_id,
401 fragment_type_mask,
402 distribution_type,
403 stream_node,
404 state_table_ids,
405 vnode_count,
406 ..
407 } = fragment;
408
409 let stream_node = stream_node.to_protobuf();
410 let mut upstream_fragments = HashSet::new();
411 visit_stream_node_body(&stream_node, |body| {
412 if let NodeBody::Merge(m) = body {
413 assert!(
414 upstream_fragments.insert(m.upstream_fragment_id),
415 "non-duplicate upstream fragment"
416 );
417 }
418 });
419
420 let mut pb_actors = vec![];
421
422 let mut pb_actor_status = HashMap::new();
423 let mut pb_actor_splits = HashMap::new();
424
425 for actor in actors {
426 if actor.fragment_id != fragment_id {
427 bail!(
428 "fragment id {} from actor {} is different from fragment {}",
429 actor.fragment_id,
430 actor.actor_id,
431 fragment_id
432 )
433 }
434
435 let ActorInfo {
436 actor_id,
437 fragment_id,
438 worker_id,
439 splits,
440 vnode_bitmap,
441 expr_context,
442 config_override,
443 ..
444 } = actor;
445
446 let vnode_bitmap =
447 vnode_bitmap.map(|vnode_bitmap| Bitmap::from(vnode_bitmap.to_protobuf()));
448 let pb_expr_context = Some(expr_context.to_protobuf());
449
450 pb_actor_status.insert(
451 actor_id as _,
452 PbActorStatus {
453 location: PbActorLocation::from_worker(worker_id),
454 },
455 );
456
457 pb_actor_splits.insert(actor_id as _, splits.to_protobuf());
458
459 pb_actors.push(StreamActor {
460 actor_id: actor_id as _,
461 fragment_id: fragment_id as _,
462 vnode_bitmap,
463 mview_definition: job_definition.clone().unwrap_or("".to_owned()),
464 expr_context: pb_expr_context,
465 config_override,
466 })
467 }
468
469 let pb_state_table_ids = state_table_ids.0;
470 let pb_distribution_type = PbFragmentDistributionType::from(distribution_type) as _;
471 let pb_fragment = Fragment {
472 fragment_id: fragment_id as _,
473 fragment_type_mask: fragment_type_mask.into(),
474 distribution_type: pb_distribution_type,
475 state_table_ids: pb_state_table_ids,
476 maybe_vnode_count: VnodeCount::set(vnode_count).to_protobuf(),
477 nodes: stream_node,
478 };
479
480 Ok((pb_fragment, pb_actors, pb_actor_status, pb_actor_splits))
481 }
482
483 pub async fn fragment_serving_infos(
484 &self,
485 ) -> MetaResult<HashMap<FragmentId, FragmentServingInfo>> {
486 let inner = self.inner.read().await;
487 inner.fragment_serving_infos().await
488 }
489
490 pub async fn fragment_job_mapping(&self) -> MetaResult<HashMap<FragmentId, JobId>> {
491 let inner = self.inner.read().await;
492 let fragment_jobs: Vec<(FragmentId, JobId)> = FragmentModel::find()
493 .select_only()
494 .columns([fragment::Column::FragmentId, fragment::Column::JobId])
495 .into_tuple()
496 .all(&inner.db)
497 .await?;
498 Ok(fragment_jobs.into_iter().collect())
499 }
500
501 pub async fn get_fragment_job_id(
502 &self,
503 fragment_ids: Vec<FragmentId>,
504 ) -> MetaResult<Vec<ObjectId>> {
505 let inner = self.inner.read().await;
506 self.get_fragment_job_id_in_txn(&inner.db, fragment_ids)
507 .await
508 }
509
510 pub async fn get_fragment_job_id_in_txn<C>(
511 &self,
512 txn: &C,
513 fragment_ids: Vec<FragmentId>,
514 ) -> MetaResult<Vec<ObjectId>>
515 where
516 C: ConnectionTrait + Send,
517 {
518 let object_ids: Vec<ObjectId> = FragmentModel::find()
519 .select_only()
520 .column(fragment::Column::JobId)
521 .filter(fragment::Column::FragmentId.is_in(fragment_ids))
522 .into_tuple()
523 .all(txn)
524 .await?;
525
526 Ok(object_ids)
527 }
528
529 pub async fn get_fragment_desc_by_id(
530 &self,
531 fragment_id: FragmentId,
532 ) -> MetaResult<Option<(FragmentDesc, Vec<FragmentId>)>> {
533 let inner = self.inner.read().await;
534
535 let fragment_model = match FragmentModel::find_by_id(fragment_id)
536 .one(&inner.db)
537 .await?
538 {
539 Some(fragment) => fragment,
540 None => return Ok(None),
541 };
542
543 let job_parallelism: Option<(StreamingParallelism, Option<String>)> =
544 StreamingJob::find_by_id(fragment_model.job_id)
545 .select_only()
546 .columns([
547 streaming_job::Column::Parallelism,
548 streaming_job::Column::AdaptiveParallelismStrategy,
549 ])
550 .into_tuple::<(StreamingParallelism, Option<String>)>()
551 .one(&inner.db)
552 .await?;
553
554 let upstream_entries: Vec<(FragmentId, DispatcherType)> = FragmentRelation::find()
555 .select_only()
556 .columns([
557 fragment_relation::Column::SourceFragmentId,
558 fragment_relation::Column::DispatcherType,
559 ])
560 .filter(fragment_relation::Column::TargetFragmentId.eq(fragment_id))
561 .into_tuple()
562 .all(&inner.db)
563 .await?;
564
565 let upstreams: Vec<_> = upstream_entries
566 .into_iter()
567 .map(|(source_id, _)| source_id)
568 .collect();
569
570 let root_fragment_map = find_fragment_no_shuffle_dags_detailed(&inner.db, &[fragment_id])
571 .await
572 .map(Self::collect_root_fragment_mapping)?;
573 let root_fragments = root_fragment_map
574 .get(&fragment_id)
575 .cloned()
576 .unwrap_or_default();
577
578 let info = self.env.shared_actor_infos().read_guard();
579 let SharedFragmentInfo { actors, .. } = info
580 .get_fragment(fragment_model.fragment_id as _)
581 .unwrap_or_else(|| {
582 panic!(
583 "Failed to retrieve fragment description: fragment {} (job_id {}) not found in shared actor info",
584 fragment_model.fragment_id,
585 fragment_model.job_id
586 )
587 });
588
589 let parallelism_policy = Self::format_fragment_parallelism_policy(
590 fragment_model.distribution_type,
591 fragment_model.parallelism.as_ref(),
592 job_parallelism.as_ref().map(|(parallelism, _)| parallelism),
593 job_parallelism
594 .as_ref()
595 .and_then(|(_, strategy)| strategy.as_deref()),
596 &root_fragments,
597 );
598
599 let fragment = FragmentDesc {
600 fragment_id: fragment_model.fragment_id,
601 job_id: fragment_model.job_id,
602 fragment_type_mask: fragment_model.fragment_type_mask,
603 distribution_type: fragment_model.distribution_type,
604 state_table_ids: fragment_model.state_table_ids.clone(),
605 parallelism: actors.len() as _,
606 vnode_count: fragment_model.vnode_count,
607 stream_node: fragment_model.stream_node.clone(),
608 parallelism_policy,
609 };
610
611 Ok(Some((fragment, upstreams)))
612 }
613
614 pub async fn list_fragment_database_ids(
615 &self,
616 select_fragment_ids: Option<Vec<FragmentId>>,
617 ) -> MetaResult<Vec<(FragmentId, DatabaseId)>> {
618 let inner = self.inner.read().await;
619 let select = FragmentModel::find()
620 .select_only()
621 .column(fragment::Column::FragmentId)
622 .column(object::Column::DatabaseId)
623 .join(JoinType::InnerJoin, fragment::Relation::Object.def());
624 let select = if let Some(select_fragment_ids) = select_fragment_ids {
625 select.filter(fragment::Column::FragmentId.is_in(select_fragment_ids))
626 } else {
627 select
628 };
629 Ok(select.into_tuple().all(&inner.db).await?)
630 }
631
632 pub async fn get_job_fragments_by_id(
633 &self,
634 job_id: JobId,
635 ) -> MetaResult<(
636 StreamJobFragments,
637 HashMap<FragmentId, Vec<StreamActor>>,
638 HashMap<ActorId, PbActorStatus>,
639 )> {
640 let inner = self.inner.read().await;
641
642 let fragments: Vec<_> = FragmentModel::find()
644 .filter(fragment::Column::JobId.eq(job_id))
645 .all(&inner.db)
646 .await?;
647
648 let job_info = StreamingJob::find_by_id(job_id)
649 .one(&inner.db)
650 .await?
651 .ok_or_else(|| anyhow::anyhow!("job {} not found in database", job_id))?;
652
653 let fragment_actors =
654 self.collect_fragment_actor_pairs(fragments, job_info.stream_context())?;
655
656 let job_definition = resolve_streaming_job_definition(&inner.db, &HashSet::from([job_id]))
657 .await?
658 .remove(&job_id);
659
660 Self::compose_table_fragments(
661 job_id,
662 job_info.job_status.into(),
663 job_info.stream_context(),
664 fragment_actors,
665 job_info.max_parallelism as _,
666 job_definition,
667 )
668 }
669
670 pub async fn get_fragment_actor_dispatchers(
671 &self,
672 fragment_ids: Vec<FragmentId>,
673 ) -> MetaResult<FragmentActorDispatchers> {
674 let inner = self.inner.read().await;
675
676 self.get_fragment_actor_dispatchers_txn(&inner.db, fragment_ids)
677 .await
678 }
679
680 pub async fn get_fragment_actor_dispatchers_txn(
681 &self,
682 c: &impl ConnectionTrait,
683 fragment_ids: Vec<FragmentId>,
684 ) -> MetaResult<FragmentActorDispatchers> {
685 let fragment_relations = FragmentRelation::find()
686 .filter(fragment_relation::Column::SourceFragmentId.is_in(fragment_ids))
687 .all(c)
688 .await?;
689
690 type FragmentActorInfo = (
691 DistributionType,
692 Arc<HashMap<crate::model::ActorId, Option<Bitmap>>>,
693 );
694
695 let shared_info = self.env.shared_actor_infos();
696 let mut fragment_actor_cache: HashMap<FragmentId, FragmentActorInfo> = HashMap::new();
697 let get_fragment_actors = |fragment_id: FragmentId| async move {
698 let result: MetaResult<FragmentActorInfo> = try {
699 let read_guard = shared_info.read_guard();
700
701 let fragment = read_guard.get_fragment(fragment_id as _).unwrap();
702
703 (
704 fragment.distribution_type,
705 Arc::new(
706 fragment
707 .actors
708 .iter()
709 .map(|(actor_id, actor_info)| {
710 (
711 *actor_id,
712 actor_info
713 .vnode_bitmap
714 .as_ref()
715 .map(|bitmap| Bitmap::from(bitmap.to_protobuf())),
716 )
717 })
718 .collect(),
719 ),
720 )
721 };
722 result
723 };
724
725 let mut actor_dispatchers_map: HashMap<_, HashMap<_, Vec<_>>> = HashMap::new();
726 for fragment_relation::Model {
727 source_fragment_id,
728 target_fragment_id,
729 dispatcher_type,
730 dist_key_indices,
731 output_indices,
732 output_type_mapping,
733 } in fragment_relations
734 {
735 let (source_fragment_distribution, source_fragment_actors) = {
736 let (distribution, actors) = {
737 match fragment_actor_cache.entry(source_fragment_id) {
738 Entry::Occupied(entry) => entry.into_mut(),
739 Entry::Vacant(entry) => {
740 entry.insert(get_fragment_actors(source_fragment_id).await?)
741 }
742 }
743 };
744 (*distribution, actors.clone())
745 };
746 let (target_fragment_distribution, target_fragment_actors) = {
747 let (distribution, actors) = {
748 match fragment_actor_cache.entry(target_fragment_id) {
749 Entry::Occupied(entry) => entry.into_mut(),
750 Entry::Vacant(entry) => {
751 entry.insert(get_fragment_actors(target_fragment_id).await?)
752 }
753 }
754 };
755 (*distribution, actors.clone())
756 };
757 let output_mapping = PbDispatchOutputMapping {
758 indices: output_indices.into_u32_array(),
759 types: output_type_mapping.unwrap_or_default().to_protobuf(),
760 };
761 let (dispatchers, _) = compose_dispatchers(
762 source_fragment_distribution,
763 &source_fragment_actors,
764 target_fragment_id as _,
765 target_fragment_distribution,
766 &target_fragment_actors,
767 dispatcher_type,
768 dist_key_indices.into_u32_array(),
769 output_mapping,
770 );
771 let actor_dispatchers_map = actor_dispatchers_map
772 .entry(source_fragment_id as _)
773 .or_default();
774 for (actor_id, dispatchers) in dispatchers {
775 actor_dispatchers_map
776 .entry(actor_id as _)
777 .or_default()
778 .push(dispatchers);
779 }
780 }
781 Ok(actor_dispatchers_map)
782 }
783
784 pub async fn get_fragment_downstream_relations(
785 &self,
786 fragment_ids: Vec<FragmentId>,
787 ) -> MetaResult<FragmentDownstreamRelation> {
788 let inner = self.inner.read().await;
789 self.get_fragment_downstream_relations_in_txn(&inner.db, fragment_ids)
790 .await
791 }
792
793 pub async fn get_fragment_downstream_relations_in_txn<C>(
794 &self,
795 txn: &C,
796 fragment_ids: Vec<FragmentId>,
797 ) -> MetaResult<FragmentDownstreamRelation>
798 where
799 C: ConnectionTrait + StreamTrait + Send,
800 {
801 let mut stream = FragmentRelation::find()
802 .filter(fragment_relation::Column::SourceFragmentId.is_in(fragment_ids))
803 .stream(txn)
804 .await?;
805 let mut relations = FragmentDownstreamRelation::new();
806 while let Some(relation) = stream.try_next().await? {
807 relations
808 .entry(relation.source_fragment_id as _)
809 .or_default()
810 .push(DownstreamFragmentRelation {
811 downstream_fragment_id: relation.target_fragment_id as _,
812 dispatcher_type: relation.dispatcher_type,
813 dist_key_indices: relation.dist_key_indices.into_u32_array(),
814 output_mapping: PbDispatchOutputMapping {
815 indices: relation.output_indices.into_u32_array(),
816 types: relation
817 .output_type_mapping
818 .unwrap_or_default()
819 .to_protobuf(),
820 },
821 });
822 }
823 Ok(relations)
824 }
825
826 pub async fn get_job_fragment_backfill_scan_type(
827 &self,
828 job_id: JobId,
829 ) -> MetaResult<HashMap<crate::model::FragmentId, PbStreamScanType>> {
830 let inner = self.inner.read().await;
831 self.get_job_fragment_backfill_scan_type_in_txn(&inner.db, job_id)
832 .await
833 }
834
835 pub async fn get_job_fragment_backfill_scan_type_in_txn<C>(
836 &self,
837 txn: &C,
838 job_id: JobId,
839 ) -> MetaResult<HashMap<crate::model::FragmentId, PbStreamScanType>>
840 where
841 C: ConnectionTrait + Send,
842 {
843 let fragments: Vec<_> = FragmentModel::find()
844 .filter(fragment::Column::JobId.eq(job_id))
845 .all(txn)
846 .await?;
847
848 let mut result = HashMap::new();
849
850 for fragment::Model {
851 fragment_id,
852 stream_node,
853 ..
854 } in fragments
855 {
856 let stream_node = stream_node.to_protobuf();
857 visit_stream_node_body(&stream_node, |body| {
858 if let NodeBody::StreamScan(node) = body {
859 match node.stream_scan_type() {
860 StreamScanType::Unspecified => {}
861 scan_type => {
862 result.insert(fragment_id as crate::model::FragmentId, scan_type);
863 }
864 }
865 }
866 });
867 }
868
869 Ok(result)
870 }
871
872 pub async fn count_streaming_jobs(&self) -> MetaResult<usize> {
873 let inner = self.inner.read().await;
874 let count = StreamingJob::find().count(&inner.db).await?;
875 Ok(usize::try_from(count).context("streaming job count overflow")?)
876 }
877
878 pub async fn list_streaming_job_infos(&self) -> MetaResult<Vec<StreamingJobInfo>> {
879 let inner = self.inner.read().await;
880 let job_states = StreamingJob::find()
881 .select_only()
882 .column(streaming_job::Column::JobId)
883 .join(JoinType::InnerJoin, streaming_job::Relation::Object.def())
884 .join(JoinType::InnerJoin, object::Relation::Database2.def())
885 .column(object::Column::ObjType)
886 .join(JoinType::LeftJoin, table::Relation::Object1.def().rev())
887 .join(JoinType::LeftJoin, source::Relation::Object.def().rev())
888 .join(JoinType::LeftJoin, sink::Relation::Object.def().rev())
889 .column_as(
890 Expr::if_null(
891 Expr::col((table::Entity, table::Column::Name)),
892 Expr::if_null(
893 Expr::col((source::Entity, source::Column::Name)),
894 Expr::if_null(
895 Expr::col((sink::Entity, sink::Column::Name)),
896 Expr::val("<unknown>"),
897 ),
898 ),
899 ),
900 "name",
901 )
902 .columns([
903 streaming_job::Column::JobStatus,
904 streaming_job::Column::Parallelism,
905 streaming_job::Column::AdaptiveParallelismStrategy,
906 streaming_job::Column::BackfillParallelism,
907 streaming_job::Column::BackfillAdaptiveParallelismStrategy,
908 streaming_job::Column::MaxParallelism,
909 ])
910 .column_as(
911 Expr::if_null(
912 Expr::col((
913 streaming_job::Entity,
914 streaming_job::Column::SpecificResourceGroup,
915 )),
916 Expr::col((database::Entity, database::Column::ResourceGroup)),
917 ),
918 "resource_group",
919 )
920 .column_as(
921 Expr::if_null(
922 Expr::col((streaming_job::Entity, streaming_job::Column::ConfigOverride)),
923 Expr::val(""),
924 ),
925 "config_override",
926 )
927 .column(object::Column::DatabaseId)
928 .column(object::Column::SchemaId)
929 .into_model()
930 .all(&inner.db)
931 .await?;
932 Ok(job_states)
933 }
934
935 pub async fn get_max_parallelism_by_id(&self, job_id: JobId) -> MetaResult<usize> {
936 let inner = self.inner.read().await;
937 let max_parallelism: i32 = StreamingJob::find_by_id(job_id)
938 .select_only()
939 .column(streaming_job::Column::MaxParallelism)
940 .into_tuple()
941 .one(&inner.db)
942 .await?
943 .ok_or_else(|| anyhow::anyhow!("job {} not found in database", job_id))?;
944 Ok(max_parallelism as usize)
945 }
946
947 pub async fn get_job_internal_table_ids(&self) -> Option<Vec<(JobId, Vec<TableId>)>> {
949 if let Ok(inner) = self.inner.try_read()
950 && let Ok(job_state_tables) = FragmentModel::find()
951 .select_only()
952 .columns([fragment::Column::JobId, fragment::Column::StateTableIds])
953 .into_tuple::<(JobId, I32Array)>()
954 .all(&inner.db)
955 .await
956 {
957 let mut job_internal_table_ids = HashMap::new();
958 for (job_id, state_table_ids) in job_state_tables {
959 job_internal_table_ids
960 .entry(job_id)
961 .or_insert_with(Vec::new)
962 .extend(
963 state_table_ids
964 .into_inner()
965 .into_iter()
966 .map(|table_id| TableId::new(table_id as _)),
967 );
968 }
969 return Some(job_internal_table_ids.into_iter().collect());
970 }
971 None
972 }
973
974 pub async fn has_any_running_jobs(&self) -> MetaResult<bool> {
975 let inner = self.inner.read().await;
976 let count = FragmentModel::find().count(&inner.db).await?;
977 Ok(count > 0)
978 }
979
980 pub fn worker_actor_count(&self) -> MetaResult<HashMap<WorkerId, usize>> {
981 let read_guard = self.env.shared_actor_infos().read_guard();
982 let actor_cnt: HashMap<WorkerId, _> = read_guard
983 .iter_over_fragments()
984 .flat_map(|(_, fragment)| {
985 fragment
986 .actors
987 .iter()
988 .map(|(actor_id, actor)| (actor.worker_id, *actor_id))
989 })
990 .into_group_map()
991 .into_iter()
992 .map(|(k, v)| (k, v.len()))
993 .collect();
994
995 Ok(actor_cnt)
996 }
997
998 fn collect_fragment_actor_map(
999 &self,
1000 fragment_ids: &[FragmentId],
1001 stream_context: StreamContext,
1002 ) -> MetaResult<HashMap<FragmentId, Vec<ActorInfo>>> {
1003 let guard = self.env.shared_actor_infos().read_guard();
1004 let pb_expr_context = stream_context.to_expr_context();
1005 let expr_context: ExprContext = (&pb_expr_context).into();
1006
1007 let mut actor_map = HashMap::with_capacity(fragment_ids.len());
1008 for fragment_id in fragment_ids {
1009 let fragment_info = guard.get_fragment(*fragment_id as _).ok_or_else(|| {
1010 anyhow!("fragment {} not found in shared actor info", fragment_id)
1011 })?;
1012
1013 let actors = fragment_info
1014 .actors
1015 .iter()
1016 .map(|(actor_id, actor_info)| ActorInfo {
1017 actor_id: *actor_id as _,
1018 fragment_id: *fragment_id,
1019 splits: ConnectorSplits::from(&PbConnectorSplits {
1020 splits: actor_info.splits.iter().map(ConnectorSplit::from).collect(),
1021 }),
1022 worker_id: actor_info.worker_id as _,
1023 vnode_bitmap: actor_info
1024 .vnode_bitmap
1025 .as_ref()
1026 .map(|bitmap| VnodeBitmap::from(&bitmap.to_protobuf())),
1027 expr_context: expr_context.clone(),
1028 config_override: stream_context.config_override.clone(),
1029 })
1030 .collect();
1031
1032 actor_map.insert(*fragment_id, actors);
1033 }
1034
1035 Ok(actor_map)
1036 }
1037
1038 fn collect_fragment_actor_pairs(
1039 &self,
1040 fragments: Vec<fragment::Model>,
1041 stream_context: StreamContext,
1042 ) -> MetaResult<Vec<(fragment::Model, Vec<ActorInfo>)>> {
1043 let fragment_ids: Vec<_> = fragments.iter().map(|f| f.fragment_id).collect();
1044 let mut actor_map = self.collect_fragment_actor_map(&fragment_ids, stream_context)?;
1045 fragments
1046 .into_iter()
1047 .map(|fragment| {
1048 let actors = actor_map.remove(&fragment.fragment_id).ok_or_else(|| {
1049 anyhow!(
1050 "fragment {} missing in shared actor info map",
1051 fragment.fragment_id
1052 )
1053 })?;
1054 Ok((fragment, actors))
1055 })
1056 .collect()
1057 }
1058
1059 pub async fn table_fragments(
1061 &self,
1062 ) -> MetaResult<
1063 BTreeMap<
1064 JobId,
1065 (
1066 StreamJobFragments,
1067 HashMap<FragmentId, Vec<StreamActor>>,
1068 HashMap<crate::model::ActorId, PbActorStatus>,
1069 ),
1070 >,
1071 > {
1072 let inner = self.inner.read().await;
1073 let jobs = StreamingJob::find().all(&inner.db).await?;
1074
1075 let mut job_definition = resolve_streaming_job_definition(
1076 &inner.db,
1077 &HashSet::from_iter(jobs.iter().map(|job| job.job_id)),
1078 )
1079 .await?;
1080
1081 let mut table_fragments = BTreeMap::new();
1082 for job in jobs {
1083 let fragments = FragmentModel::find()
1084 .filter(fragment::Column::JobId.eq(job.job_id))
1085 .all(&inner.db)
1086 .await?;
1087
1088 let fragment_actors =
1089 self.collect_fragment_actor_pairs(fragments, job.stream_context())?;
1090
1091 table_fragments.insert(
1092 job.job_id,
1093 Self::compose_table_fragments(
1094 job.job_id,
1095 job.job_status.into(),
1096 job.stream_context(),
1097 fragment_actors,
1098 job.max_parallelism as _,
1099 job_definition.remove(&job.job_id),
1100 )?,
1101 );
1102 }
1103
1104 Ok(table_fragments)
1105 }
1106
1107 pub async fn upstream_fragments(
1108 &self,
1109 fragment_ids: impl Iterator<Item = crate::model::FragmentId>,
1110 ) -> MetaResult<HashMap<crate::model::FragmentId, HashSet<crate::model::FragmentId>>> {
1111 let inner = self.inner.read().await;
1112 self.upstream_fragments_in_txn(&inner.db, fragment_ids)
1113 .await
1114 }
1115
1116 pub async fn upstream_fragments_in_txn<C>(
1117 &self,
1118 txn: &C,
1119 fragment_ids: impl Iterator<Item = crate::model::FragmentId>,
1120 ) -> MetaResult<HashMap<crate::model::FragmentId, HashSet<crate::model::FragmentId>>>
1121 where
1122 C: ConnectionTrait + StreamTrait + Send,
1123 {
1124 let mut stream = FragmentRelation::find()
1125 .select_only()
1126 .columns([
1127 fragment_relation::Column::SourceFragmentId,
1128 fragment_relation::Column::TargetFragmentId,
1129 ])
1130 .filter(
1131 fragment_relation::Column::TargetFragmentId
1132 .is_in(fragment_ids.map(|id| id as FragmentId)),
1133 )
1134 .into_tuple::<(FragmentId, FragmentId)>()
1135 .stream(txn)
1136 .await?;
1137 let mut upstream_fragments: HashMap<_, HashSet<_>> = HashMap::new();
1138 while let Some((upstream_fragment_id, downstream_fragment_id)) = stream.try_next().await? {
1139 upstream_fragments
1140 .entry(downstream_fragment_id as crate::model::FragmentId)
1141 .or_default()
1142 .insert(upstream_fragment_id as crate::model::FragmentId);
1143 }
1144 Ok(upstream_fragments)
1145 }
1146
1147 pub fn list_actor_locations(&self) -> MetaResult<Vec<PartialActorLocation>> {
1148 let info = self.env.shared_actor_infos().read_guard();
1149
1150 let actor_locations = info
1151 .iter_over_fragments()
1152 .flat_map(|(fragment_id, fragment)| {
1153 fragment
1154 .actors
1155 .iter()
1156 .map(|(actor_id, actor)| PartialActorLocation {
1157 actor_id: *actor_id as _,
1158 fragment_id: *fragment_id as _,
1159 worker_id: actor.worker_id,
1160 })
1161 })
1162 .collect_vec();
1163
1164 Ok(actor_locations)
1165 }
1166
1167 pub async fn list_actor_info(
1168 &self,
1169 database_id: Option<DatabaseId>,
1170 ) -> MetaResult<Vec<(ActorId, FragmentId, ObjectId, SchemaId, ObjectType)>> {
1171 let inner = self.inner.read().await;
1172
1173 let fragment_objects: Vec<(FragmentId, ObjectId, SchemaId, ObjectType)> =
1174 FragmentModel::find()
1175 .select_only()
1176 .join(JoinType::LeftJoin, fragment::Relation::Object.def())
1177 .column(fragment::Column::FragmentId)
1178 .column_as(object::Column::Oid, "job_id")
1179 .column_as(object::Column::SchemaId, "schema_id")
1180 .column_as(object::Column::ObjType, "type")
1181 .filter(
1182 database_id
1183 .map(|id| object::Column::DatabaseId.eq(i64::from(id.as_raw_id())))
1185 .unwrap_or_else(|| SimpleExpr::from(true)),
1186 )
1187 .into_tuple()
1188 .all(&inner.db)
1189 .await?;
1190
1191 let actor_infos = {
1192 let info = self.env.shared_actor_infos().read_guard();
1193
1194 let mut result = Vec::new();
1195
1196 for (fragment_id, object_id, schema_id, object_type) in fragment_objects {
1197 let Some(fragment) = info.get_fragment(fragment_id as _) else {
1198 return Err(MetaError::unavailable(format!(
1199 "shared actor info missing for fragment {fragment_id} while listing actors"
1200 )));
1201 };
1202
1203 for actor_id in fragment.actors.keys() {
1204 result.push((
1205 *actor_id as _,
1206 fragment.fragment_id as _,
1207 object_id,
1208 schema_id,
1209 object_type,
1210 ));
1211 }
1212 }
1213
1214 result
1215 };
1216
1217 Ok(actor_infos)
1218 }
1219
1220 pub fn get_worker_slot_mappings(&self) -> Vec<PbFragmentWorkerSlotMapping> {
1221 let guard = self.env.shared_actor_info.read_guard();
1222 guard
1223 .iter_over_fragments()
1224 .map(|(_, fragment)| rebuild_fragment_mapping(fragment))
1225 .collect_vec()
1226 }
1227
1228 pub async fn list_fragment_descs_with_node(
1229 &self,
1230 is_creating: bool,
1231 ) -> MetaResult<Vec<(FragmentDistribution, Vec<FragmentId>)>> {
1232 let inner = self.inner.read().await;
1233 let txn = inner.db.begin().await?;
1234
1235 let fragments_query = Self::build_fragment_query(is_creating);
1236 let fragments: Vec<fragment::Model> = fragments_query.all(&txn).await?;
1237
1238 let rows = fragments
1239 .into_iter()
1240 .map(|fragment| FragmentDescRow {
1241 fragment_id: fragment.fragment_id,
1242 job_id: fragment.job_id,
1243 fragment_type_mask: fragment.fragment_type_mask,
1244 distribution_type: fragment.distribution_type,
1245 state_table_ids: fragment.state_table_ids,
1246 vnode_count: fragment.vnode_count,
1247 parallelism_override: fragment.parallelism,
1248 stream_node: Some(fragment.stream_node),
1249 })
1250 .collect_vec();
1251
1252 self.build_fragment_distributions(&txn, rows).await
1253 }
1254
1255 pub async fn list_fragment_descs_without_node(
1256 &self,
1257 is_creating: bool,
1258 ) -> MetaResult<Vec<(FragmentDistribution, Vec<FragmentId>)>> {
1259 let inner = self.inner.read().await;
1260 let txn = inner.db.begin().await?;
1261
1262 let fragments_query = Self::build_fragment_query(is_creating);
1263 #[expect(clippy::type_complexity)]
1264 let fragments: Vec<(
1265 FragmentId,
1266 JobId,
1267 i32,
1268 DistributionType,
1269 TableIdArray,
1270 i32,
1271 Option<StreamingParallelism>,
1272 )> = fragments_query
1273 .select_only()
1274 .columns([
1275 fragment::Column::FragmentId,
1276 fragment::Column::JobId,
1277 fragment::Column::FragmentTypeMask,
1278 fragment::Column::DistributionType,
1279 fragment::Column::StateTableIds,
1280 fragment::Column::VnodeCount,
1281 fragment::Column::Parallelism,
1282 ])
1283 .into_tuple()
1284 .all(&txn)
1285 .await?;
1286
1287 let rows = fragments
1288 .into_iter()
1289 .map(
1290 |(
1291 fragment_id,
1292 job_id,
1293 fragment_type_mask,
1294 distribution_type,
1295 state_table_ids,
1296 vnode_count,
1297 parallelism_override,
1298 )| FragmentDescRow {
1299 fragment_id,
1300 job_id,
1301 fragment_type_mask,
1302 distribution_type,
1303 state_table_ids,
1304 vnode_count,
1305 parallelism_override,
1306 stream_node: None,
1307 },
1308 )
1309 .collect_vec();
1310
1311 self.build_fragment_distributions(&txn, rows).await
1312 }
1313
1314 fn build_fragment_query(is_creating: bool) -> sea_orm::Select<fragment::Entity> {
1315 if is_creating {
1316 FragmentModel::find()
1317 .join(JoinType::LeftJoin, fragment::Relation::Object.def())
1318 .join(JoinType::LeftJoin, object::Relation::StreamingJob.def())
1319 .filter(
1320 streaming_job::Column::JobStatus
1321 .eq(JobStatus::Initial)
1322 .or(streaming_job::Column::JobStatus.eq(JobStatus::Creating)),
1323 )
1324 } else {
1325 FragmentModel::find()
1326 }
1327 }
1328
1329 async fn build_fragment_distributions(
1330 &self,
1331 txn: &DatabaseTransaction,
1332 rows: Vec<FragmentDescRow>,
1333 ) -> MetaResult<Vec<(FragmentDistribution, Vec<FragmentId>)>> {
1334 let fragment_ids = rows.iter().map(|row| row.fragment_id).collect_vec();
1335 let job_ids = rows.iter().map(|row| row.job_id).unique().collect_vec();
1336
1337 let job_parallelisms: HashMap<JobId, (StreamingParallelism, Option<String>)> =
1338 if fragment_ids.is_empty() {
1339 HashMap::new()
1340 } else {
1341 StreamingJob::find()
1342 .select_only()
1343 .columns([
1344 streaming_job::Column::JobId,
1345 streaming_job::Column::Parallelism,
1346 streaming_job::Column::AdaptiveParallelismStrategy,
1347 ])
1348 .filter(streaming_job::Column::JobId.is_in(job_ids))
1349 .into_tuple::<(JobId, StreamingParallelism, Option<String>)>()
1350 .all(txn)
1351 .await?
1352 .into_iter()
1353 .map(|(job_id, parallelism, strategy)| (job_id, (parallelism, strategy)))
1354 .collect()
1355 };
1356
1357 let upstream_entries: Vec<(FragmentId, FragmentId, DispatcherType)> =
1358 if fragment_ids.is_empty() {
1359 Vec::new()
1360 } else {
1361 FragmentRelation::find()
1362 .select_only()
1363 .columns([
1364 fragment_relation::Column::TargetFragmentId,
1365 fragment_relation::Column::SourceFragmentId,
1366 fragment_relation::Column::DispatcherType,
1367 ])
1368 .filter(fragment_relation::Column::TargetFragmentId.is_in(fragment_ids.clone()))
1369 .into_tuple()
1370 .all(txn)
1371 .await?
1372 };
1373
1374 let mut all_upstreams: HashMap<FragmentId, Vec<FragmentId>> = HashMap::new();
1375 for (target_id, source_id, _) in upstream_entries {
1376 all_upstreams.entry(target_id).or_default().push(source_id);
1377 }
1378
1379 let root_fragment_map = if fragment_ids.is_empty() {
1380 HashMap::new()
1381 } else {
1382 let ensembles = find_fragment_no_shuffle_dags_detailed(txn, &fragment_ids).await?;
1383 Self::collect_root_fragment_mapping(ensembles)
1384 };
1385
1386 let guard = self.env.shared_actor_info.read_guard();
1387 let mut result = Vec::with_capacity(rows.len());
1388
1389 for row in rows {
1390 let parallelism = guard
1391 .get_fragment(row.fragment_id as _)
1392 .map(|fragment| fragment.actors.len())
1393 .unwrap_or_default();
1394
1395 let root_fragments = root_fragment_map
1396 .get(&row.fragment_id)
1397 .cloned()
1398 .unwrap_or_default();
1399
1400 let upstreams = all_upstreams.remove(&row.fragment_id).unwrap_or_default();
1401
1402 let parallelism_policy = Self::format_fragment_parallelism_policy(
1403 row.distribution_type,
1404 row.parallelism_override.as_ref(),
1405 job_parallelisms
1406 .get(&row.job_id)
1407 .map(|(parallelism, _)| parallelism),
1408 job_parallelisms
1409 .get(&row.job_id)
1410 .and_then(|(_, strategy)| strategy.as_deref()),
1411 &root_fragments,
1412 );
1413
1414 let fragment = FragmentDistribution {
1415 fragment_id: row.fragment_id,
1416 table_id: row.job_id,
1417 distribution_type: PbFragmentDistributionType::from(row.distribution_type) as _,
1418 state_table_ids: row.state_table_ids.0,
1419 upstream_fragment_ids: upstreams.clone(),
1420 fragment_type_mask: row.fragment_type_mask as _,
1421 parallelism: parallelism as _,
1422 vnode_count: row.vnode_count as _,
1423 node: row.stream_node.map(|node| node.to_protobuf()),
1424 parallelism_policy,
1425 };
1426
1427 result.push((fragment, upstreams));
1428 }
1429
1430 Ok(result)
1431 }
1432
1433 pub async fn get_refresh_staging_table_id(
1435 &self,
1436 job_id: JobId,
1437 ) -> MetaResult<Option<risingwave_common::catalog::TableId>> {
1438 let inner = self.inner.read().await;
1439 let stream_nodes: Vec<(StreamNode,)> = FragmentModel::find()
1440 .select_only()
1441 .column(fragment::Column::StreamNode)
1442 .filter(fragment::Column::JobId.eq(job_id))
1443 .filter(FragmentTypeMask::intersects(FragmentTypeFlag::Mview))
1444 .into_tuple()
1445 .all(&inner.db)
1446 .await?;
1447 let mut staging_table_id = None;
1448 for (stream_node,) in stream_nodes {
1449 visit_stream_node_body(&stream_node.to_protobuf(), |body| {
1450 if let NodeBody::Materialize(node) = body
1451 && let Some(table) = &node.staging_table
1452 {
1453 staging_table_id = Some(table.id);
1454 }
1455 });
1456 }
1457 Ok(staging_table_id)
1458 }
1459
1460 pub async fn list_sink_log_store_tables(&self) -> MetaResult<Vec<(SinkId, TableId)>> {
1461 let inner = self.inner.read().await;
1462 let txn = inner.db.begin().await?;
1463
1464 let fragments: Vec<(JobId, StreamNode)> = FragmentModel::find()
1465 .select_only()
1466 .columns([fragment::Column::JobId, fragment::Column::StreamNode])
1467 .filter(FragmentTypeMask::intersects(FragmentTypeFlag::Sink))
1468 .into_tuple()
1469 .all(&txn)
1470 .await?;
1471
1472 let mut mapping: HashMap<SinkId, TableId> = HashMap::new();
1473
1474 for (job_id, stream_node) in fragments {
1475 let sink_id = SinkId::new(job_id.as_raw_id());
1476 let stream_node = stream_node.to_protobuf();
1477 let mut internal_table_id: Option<TableId> = None;
1478
1479 visit_stream_node_body(&stream_node, |body| {
1480 if let NodeBody::Sink(node) = body
1481 && node.log_store_type == SinkLogStoreType::KvLogStore as i32
1482 && let Some(table) = &node.table
1483 {
1484 internal_table_id = Some(TableId::new(table.id.as_raw_id()));
1485 }
1486 });
1487
1488 if let Some(table_id) = internal_table_id
1489 && let Some(existing) = mapping.insert(sink_id, table_id)
1490 && existing != table_id
1491 {
1492 tracing::warn!(
1493 "sink {sink_id:?} has multiple log store tables: {existing:?} vs {table_id:?}"
1494 );
1495 }
1496 }
1497
1498 Ok(mapping.into_iter().collect())
1499 }
1500
1501 fn collect_root_fragment_mapping(
1503 ensembles: Vec<NoShuffleEnsemble>,
1504 ) -> HashMap<FragmentId, Vec<FragmentId>> {
1505 let mut mapping = HashMap::new();
1506
1507 for ensemble in ensembles {
1508 let mut roots: Vec<_> = ensemble.entry_fragments().collect();
1509 roots.sort_unstable();
1510 roots.dedup();
1511
1512 if roots.is_empty() {
1513 continue;
1514 }
1515
1516 let root_set: HashSet<_> = roots.iter().copied().collect();
1517
1518 for fragment_id in ensemble.component_fragments() {
1519 if root_set.contains(&fragment_id) {
1520 mapping.insert(fragment_id, Vec::new());
1521 } else {
1522 mapping.insert(fragment_id, roots.clone());
1523 }
1524 }
1525 }
1526
1527 mapping
1528 }
1529
1530 fn format_fragment_parallelism_policy(
1531 distribution_type: DistributionType,
1532 fragment_parallelism: Option<&StreamingParallelism>,
1533 job_parallelism: Option<&StreamingParallelism>,
1534 job_adaptive_parallelism_strategy: Option<&str>,
1535 root_fragments: &[FragmentId],
1536 ) -> String {
1537 if distribution_type == DistributionType::Single {
1538 return "single".to_owned();
1539 }
1540
1541 if let Some(parallelism) = fragment_parallelism {
1542 return format!(
1543 "override({})",
1544 Self::format_streaming_parallelism(parallelism, job_adaptive_parallelism_strategy)
1545 );
1546 }
1547
1548 if !root_fragments.is_empty() {
1549 let mut upstreams = root_fragments.to_vec();
1550 upstreams.sort_unstable();
1551 upstreams.dedup();
1552
1553 return format!("upstream_fragment({upstreams:?})");
1554 }
1555
1556 let inherited = job_parallelism
1557 .map(|parallelism| {
1558 Self::format_streaming_parallelism(parallelism, job_adaptive_parallelism_strategy)
1559 })
1560 .unwrap_or_else(|| "unknown".to_owned());
1561 format!("inherit({inherited})")
1562 }
1563
1564 fn format_streaming_parallelism(
1565 parallelism: &StreamingParallelism,
1566 adaptive_parallelism_strategy: Option<&str>,
1567 ) -> String {
1568 match parallelism {
1569 StreamingParallelism::Adaptive => adaptive_parallelism_strategy
1570 .and_then(Self::format_adaptive_parallelism_strategy)
1571 .unwrap_or_else(|| "adaptive".to_owned()),
1572 StreamingParallelism::Fixed(n) => n.to_string(),
1573 StreamingParallelism::Custom => adaptive_parallelism_strategy
1574 .and_then(Self::format_adaptive_parallelism_strategy)
1575 .unwrap_or_else(|| "custom".to_owned()),
1576 }
1577 }
1578
1579 fn format_adaptive_parallelism_strategy(strategy: &str) -> Option<String> {
1580 parse_strategy(strategy)
1581 .ok()
1582 .map(|strategy| match strategy {
1583 AdaptiveParallelismStrategy::Auto | AdaptiveParallelismStrategy::Full => {
1584 "adaptive".to_owned()
1585 }
1586 AdaptiveParallelismStrategy::Bounded(n) => format!("bounded({n})"),
1587 AdaptiveParallelismStrategy::Ratio(r) => format!("ratio({r})"),
1588 })
1589 }
1590
1591 pub async fn list_sink_actor_mapping(
1592 &self,
1593 ) -> MetaResult<HashMap<SinkId, (String, Vec<ActorId>)>> {
1594 let inner = self.inner.read().await;
1595 let sink_id_names: Vec<(SinkId, String)> = Sink::find()
1596 .select_only()
1597 .columns([sink::Column::SinkId, sink::Column::Name])
1598 .into_tuple()
1599 .all(&inner.db)
1600 .await?;
1601 let (sink_ids, _): (Vec<_>, Vec<_>) = sink_id_names.iter().cloned().unzip();
1602
1603 let sink_name_mapping: HashMap<SinkId, String> = sink_id_names.into_iter().collect();
1604
1605 let actor_with_type: Vec<(ActorId, SinkId)> = {
1606 let info = self.env.shared_actor_infos().read_guard();
1607
1608 info.iter_over_fragments()
1609 .filter(|(_, fragment)| {
1610 sink_ids.contains(&fragment.job_id.as_sink_id())
1611 && fragment.fragment_type_mask.contains(FragmentTypeFlag::Sink)
1612 })
1613 .flat_map(|(_, fragment)| {
1614 fragment
1615 .actors
1616 .keys()
1617 .map(move |actor_id| (*actor_id as _, fragment.job_id.as_sink_id()))
1618 })
1619 .collect()
1620 };
1621
1622 let mut sink_actor_mapping = HashMap::new();
1623 for (actor_id, sink_id) in actor_with_type {
1624 sink_actor_mapping
1625 .entry(sink_id)
1626 .or_insert_with(|| (sink_name_mapping.get(&sink_id).unwrap().clone(), vec![]))
1627 .1
1628 .push(actor_id);
1629 }
1630
1631 Ok(sink_actor_mapping)
1632 }
1633
1634 pub async fn list_fragment_state_tables(&self) -> MetaResult<Vec<PartialFragmentStateTables>> {
1635 let inner = self.inner.read().await;
1636 let fragment_state_tables: Vec<PartialFragmentStateTables> = FragmentModel::find()
1637 .select_only()
1638 .columns([
1639 fragment::Column::FragmentId,
1640 fragment::Column::JobId,
1641 fragment::Column::StateTableIds,
1642 ])
1643 .into_partial_model()
1644 .all(&inner.db)
1645 .await?;
1646 Ok(fragment_state_tables)
1647 }
1648
1649 pub async fn load_all_actors_dynamic(
1652 &self,
1653 database_id: Option<DatabaseId>,
1654 worker_nodes: &ActiveStreamingWorkerNodes,
1655 ) -> MetaResult<FragmentRenderMap> {
1656 let loaded = self.load_fragment_context(database_id).await?;
1657
1658 if loaded.is_empty() {
1659 return Ok(HashMap::new());
1660 }
1661
1662 let RenderedGraph { fragments, .. } = render_actor_assignments(
1663 self.env.actor_id_generator(),
1664 worker_nodes.current(),
1665 &loaded,
1666 )?;
1667
1668 tracing::trace!(?fragments, "reload all actors");
1669
1670 Ok(fragments)
1671 }
1672
1673 pub async fn load_fragment_context(
1675 &self,
1676 database_id: Option<DatabaseId>,
1677 ) -> MetaResult<LoadedFragmentContext> {
1678 let inner = self.inner.read().await;
1679 let txn = inner.db.begin().await?;
1680
1681 self.load_fragment_context_in_txn(&txn, database_id).await
1682 }
1683
1684 pub async fn load_fragment_context_in_txn<C>(
1685 &self,
1686 txn: &C,
1687 database_id: Option<DatabaseId>,
1688 ) -> MetaResult<LoadedFragmentContext>
1689 where
1690 C: ConnectionTrait,
1691 {
1692 let mut query = StreamingJob::find()
1693 .select_only()
1694 .column(streaming_job::Column::JobId);
1695
1696 if let Some(database_id) = database_id {
1697 query = query
1698 .join(JoinType::InnerJoin, streaming_job::Relation::Object.def())
1699 .filter(object::Column::DatabaseId.eq(database_id));
1700 }
1701
1702 let jobs: Vec<JobId> = query.into_tuple().all(txn).await?;
1703
1704 let jobs: HashSet<JobId> = jobs.into_iter().collect();
1705
1706 if jobs.is_empty() {
1707 return Ok(LoadedFragmentContext::default());
1708 }
1709
1710 load_fragment_context_for_jobs(txn, jobs).await
1711 }
1712
1713 #[await_tree::instrument]
1714 pub async fn fill_snapshot_backfill_epoch(
1715 &self,
1716 fragment_ids: impl Iterator<Item = FragmentId>,
1717 snapshot_backfill_info: Option<&SnapshotBackfillInfo>,
1718 cross_db_snapshot_backfill_info: &SnapshotBackfillInfo,
1719 ) -> MetaResult<()> {
1720 let inner = self.inner.write().await;
1721 let txn = inner.db.begin().await?;
1722 for fragment_id in fragment_ids {
1723 let fragment = FragmentModel::find_by_id(fragment_id)
1724 .one(&txn)
1725 .await?
1726 .context(format!("fragment {} not found", fragment_id))?;
1727 let mut node = fragment.stream_node.to_protobuf();
1728 if crate::stream::fill_snapshot_backfill_epoch(
1729 &mut node,
1730 snapshot_backfill_info,
1731 cross_db_snapshot_backfill_info,
1732 )? {
1733 let node = StreamNode::from(&node);
1734 FragmentModel::update(fragment::ActiveModel {
1735 fragment_id: Set(fragment_id),
1736 stream_node: Set(node),
1737 ..Default::default()
1738 })
1739 .exec(&txn)
1740 .await?;
1741 }
1742 }
1743 txn.commit().await?;
1744 Ok(())
1745 }
1746
1747 pub fn get_running_actors_of_fragment(
1749 &self,
1750 fragment_id: FragmentId,
1751 ) -> MetaResult<HashSet<model::ActorId>> {
1752 let info = self.env.shared_actor_infos().read_guard();
1753
1754 let actors = info
1755 .get_fragment(fragment_id as _)
1756 .map(|SharedFragmentInfo { actors, .. }| actors.keys().copied().collect())
1757 .unwrap_or_default();
1758
1759 Ok(actors)
1760 }
1761
1762 pub async fn get_running_actors_for_source_backfill(
1765 &self,
1766 source_backfill_fragment_id: FragmentId,
1767 source_fragment_id: FragmentId,
1768 ) -> MetaResult<Vec<(ActorId, ActorId)>> {
1769 let inner = self.inner.read().await;
1770 let txn = inner.db.begin().await?;
1771
1772 let fragment_relation: DispatcherType = FragmentRelation::find()
1773 .select_only()
1774 .column(fragment_relation::Column::DispatcherType)
1775 .filter(fragment_relation::Column::SourceFragmentId.eq(source_fragment_id))
1776 .filter(fragment_relation::Column::TargetFragmentId.eq(source_backfill_fragment_id))
1777 .into_tuple()
1778 .one(&txn)
1779 .await?
1780 .ok_or_else(|| {
1781 anyhow!(
1782 "no fragment connection from source fragment {} to source backfill fragment {}",
1783 source_fragment_id,
1784 source_backfill_fragment_id
1785 )
1786 })?;
1787
1788 if fragment_relation != DispatcherType::NoShuffle {
1789 return Err(anyhow!("expected NoShuffle but got {:?}", fragment_relation).into());
1790 }
1791
1792 let load_fragment_distribution_type = |txn, fragment_id: FragmentId| async move {
1793 let result: MetaResult<DistributionType> = try {
1794 FragmentModel::find_by_id(fragment_id)
1795 .select_only()
1796 .column(fragment::Column::DistributionType)
1797 .into_tuple()
1798 .one(txn)
1799 .await
1800 .map_err(MetaError::from)?
1801 .ok_or_else(|| {
1802 MetaError::from(anyhow!("failed to find fragment: {}", fragment_id))
1803 })?
1804 };
1805 result
1806 };
1807
1808 let source_backfill_distribution_type =
1809 load_fragment_distribution_type(&txn, source_backfill_fragment_id).await?;
1810 let source_distribution_type =
1811 load_fragment_distribution_type(&txn, source_fragment_id).await?;
1812
1813 let load_fragment_actor_distribution =
1814 |actor_info: &SharedActorInfos,
1815 fragment_id: FragmentId|
1816 -> HashMap<crate::model::ActorId, Option<Bitmap>> {
1817 let guard = actor_info.read_guard();
1818
1819 guard
1820 .get_fragment(fragment_id as _)
1821 .map(|fragment| {
1822 fragment
1823 .actors
1824 .iter()
1825 .map(|(actor_id, actor)| {
1826 (
1827 *actor_id as _,
1828 actor
1829 .vnode_bitmap
1830 .as_ref()
1831 .map(|bitmap| Bitmap::from(bitmap.to_protobuf())),
1832 )
1833 })
1834 .collect()
1835 })
1836 .unwrap_or_default()
1837 };
1838
1839 let source_backfill_actors: HashMap<crate::model::ActorId, Option<Bitmap>> =
1840 load_fragment_actor_distribution(
1841 self.env.shared_actor_infos(),
1842 source_backfill_fragment_id,
1843 );
1844
1845 let source_actors =
1846 load_fragment_actor_distribution(self.env.shared_actor_infos(), source_fragment_id);
1847
1848 Ok(resolve_no_shuffle_actor_mapping(
1849 source_distribution_type,
1850 source_actors.iter().map(|(&id, bitmap)| (id, bitmap)),
1851 source_backfill_distribution_type,
1852 source_backfill_actors
1853 .iter()
1854 .map(|(&id, bitmap)| (id, bitmap)),
1855 )
1856 .into_iter()
1857 .map(|(source_actor, source_backfill_actor)| {
1858 (source_backfill_actor as _, source_actor as _)
1859 })
1860 .collect())
1861 }
1862
1863 pub async fn get_root_fragments(
1876 &self,
1877 job_ids: Vec<JobId>,
1878 ) -> MetaResult<HashMap<JobId, Fragment>> {
1879 let inner = self.inner.read().await;
1880
1881 let all_fragments = FragmentModel::find()
1882 .filter(fragment::Column::JobId.is_in(job_ids))
1883 .all(&inner.db)
1884 .await?;
1885 let mut root_fragments = HashMap::<JobId, Fragment>::new();
1887 for fragment in all_fragments {
1888 let mask = FragmentTypeMask::from(fragment.fragment_type_mask);
1889 if mask.contains_any([FragmentTypeFlag::Mview, FragmentTypeFlag::Sink]) {
1890 _ = root_fragments.insert(fragment.job_id, fragment.into());
1891 } else if mask.contains(FragmentTypeFlag::Source) {
1892 _ = root_fragments.try_insert(fragment.job_id, fragment.into());
1895 }
1896 }
1897
1898 Ok(root_fragments)
1899 }
1900
1901 pub async fn get_root_fragment(&self, job_id: JobId) -> MetaResult<Fragment> {
1902 let mut root_fragments = self.get_root_fragments(vec![job_id]).await?;
1903 let root_fragment = root_fragments
1904 .remove(&job_id)
1905 .context(format!("root fragment for job {} not found", job_id))?;
1906
1907 Ok(root_fragment)
1908 }
1909
1910 pub async fn get_downstream_fragments(
1912 &self,
1913 job_id: JobId,
1914 ) -> MetaResult<Vec<(stream_plan::DispatcherType, Fragment)>> {
1915 let root_fragment = self.get_root_fragment(job_id).await?;
1916
1917 let inner = self.inner.read().await;
1918 let txn = inner.db.begin().await?;
1919 let downstream_fragment_relations: Vec<fragment_relation::Model> = FragmentRelation::find()
1920 .filter(
1921 fragment_relation::Column::SourceFragmentId
1922 .eq(root_fragment.fragment_id as FragmentId),
1923 )
1924 .all(&txn)
1925 .await?;
1926
1927 let downstream_fragment_ids = downstream_fragment_relations
1928 .iter()
1929 .map(|model| model.target_fragment_id as FragmentId)
1930 .collect::<HashSet<_>>();
1931
1932 let downstream_fragments: Vec<fragment::Model> = FragmentModel::find()
1933 .filter(fragment::Column::FragmentId.is_in(downstream_fragment_ids))
1934 .all(&txn)
1935 .await?;
1936
1937 let mut downstream_fragments_map: HashMap<_, _> = downstream_fragments
1938 .into_iter()
1939 .map(|fragment| (fragment.fragment_id, fragment))
1940 .collect();
1941
1942 let mut downstream_fragments = vec![];
1943
1944 let fragment_map: HashMap<_, _> = downstream_fragment_relations
1945 .iter()
1946 .map(|model| (model.target_fragment_id, model.dispatcher_type))
1947 .collect();
1948
1949 for (fragment_id, dispatcher_type) in fragment_map {
1950 let dispatch_type = PbDispatcherType::from(dispatcher_type);
1951
1952 let fragment = downstream_fragments_map
1953 .remove(&fragment_id)
1954 .context(format!(
1955 "downstream fragment node for id {} not found",
1956 fragment_id
1957 ))?
1958 .into();
1959
1960 downstream_fragments.push((dispatch_type, fragment));
1961 }
1962 Ok(downstream_fragments)
1963 }
1964
1965 pub async fn load_source_fragment_ids(
1966 &self,
1967 ) -> MetaResult<HashMap<SourceId, BTreeSet<FragmentId>>> {
1968 let inner = self.inner.read().await;
1969 let fragments: Vec<(FragmentId, StreamNode)> = FragmentModel::find()
1970 .select_only()
1971 .columns([fragment::Column::FragmentId, fragment::Column::StreamNode])
1972 .filter(FragmentTypeMask::intersects(FragmentTypeFlag::Source))
1973 .into_tuple()
1974 .all(&inner.db)
1975 .await?;
1976
1977 let mut source_fragment_ids = HashMap::new();
1978 for (fragment_id, stream_node) in fragments {
1979 if let Some(source_id) = stream_node.to_protobuf().find_stream_source() {
1980 source_fragment_ids
1981 .entry(source_id)
1982 .or_insert_with(BTreeSet::new)
1983 .insert(fragment_id);
1984 }
1985 }
1986 Ok(source_fragment_ids)
1987 }
1988
1989 pub async fn load_backfill_fragment_ids(
1990 &self,
1991 ) -> MetaResult<HashMap<SourceId, BTreeSet<(FragmentId, FragmentId)>>> {
1992 let inner = self.inner.read().await;
1993 let fragments: Vec<(FragmentId, StreamNode)> = FragmentModel::find()
1994 .select_only()
1995 .columns([fragment::Column::FragmentId, fragment::Column::StreamNode])
1996 .filter(FragmentTypeMask::intersects(FragmentTypeFlag::SourceScan))
1997 .into_tuple()
1998 .all(&inner.db)
1999 .await?;
2000
2001 let mut source_fragment_ids = HashMap::new();
2002 for (fragment_id, stream_node) in fragments {
2003 if let Some((source_id, upstream_source_fragment_id)) =
2004 stream_node.to_protobuf().find_source_backfill()
2005 {
2006 source_fragment_ids
2007 .entry(source_id)
2008 .or_insert_with(BTreeSet::new)
2009 .insert((fragment_id, upstream_source_fragment_id));
2010 }
2011 }
2012 Ok(source_fragment_ids)
2013 }
2014
2015 pub async fn get_all_upstream_sink_infos(
2016 &self,
2017 target_table: &PbTable,
2018 target_fragment_id: FragmentId,
2019 ) -> MetaResult<Vec<UpstreamSinkInfo>> {
2020 let inner = self.inner.read().await;
2021 let txn = inner.db.begin().await?;
2022
2023 self.get_all_upstream_sink_infos_in_txn(&txn, target_table, target_fragment_id)
2024 .await
2025 }
2026
2027 pub async fn get_all_upstream_sink_infos_in_txn<C>(
2028 &self,
2029 txn: &C,
2030 target_table: &PbTable,
2031 target_fragment_id: FragmentId,
2032 ) -> MetaResult<Vec<UpstreamSinkInfo>>
2033 where
2034 C: ConnectionTrait,
2035 {
2036 let incoming_sinks = Sink::find()
2037 .filter(sink::Column::TargetTable.eq(target_table.id))
2038 .all(txn)
2039 .await?;
2040
2041 let sink_ids = incoming_sinks.iter().map(|s| s.sink_id).collect_vec();
2042 let sink_fragment_ids = get_sink_fragment_by_ids(txn, sink_ids).await?;
2043
2044 let mut upstream_sink_infos = Vec::with_capacity(incoming_sinks.len());
2045 for sink in &incoming_sinks {
2046 let sink_fragment_id =
2047 sink_fragment_ids
2048 .get(&sink.sink_id)
2049 .cloned()
2050 .ok_or(anyhow::anyhow!(
2051 "sink fragment not found for sink id {}",
2052 sink.sink_id
2053 ))?;
2054 let upstream_info = build_upstream_sink_info(
2055 sink.sink_id,
2056 sink.original_target_columns
2057 .as_ref()
2058 .map(|cols| cols.to_protobuf())
2059 .unwrap_or_default(),
2060 sink_fragment_id,
2061 target_table,
2062 target_fragment_id,
2063 )?;
2064 upstream_sink_infos.push(upstream_info);
2065 }
2066
2067 Ok(upstream_sink_infos)
2068 }
2069
2070 pub async fn get_mview_fragment_by_id(&self, job_id: JobId) -> MetaResult<FragmentId> {
2071 let inner = self.inner.read().await;
2072 let txn = inner.db.begin().await?;
2073
2074 let mview_fragment: Vec<FragmentId> = FragmentModel::find()
2075 .select_only()
2076 .column(fragment::Column::FragmentId)
2077 .filter(
2078 fragment::Column::JobId
2079 .eq(job_id)
2080 .and(FragmentTypeMask::intersects(FragmentTypeFlag::Mview)),
2081 )
2082 .into_tuple()
2083 .all(&txn)
2084 .await?;
2085
2086 if mview_fragment.len() != 1 {
2087 return Err(anyhow::anyhow!(
2088 "expected exactly one mview fragment for job {}, found {}",
2089 job_id,
2090 mview_fragment.len()
2091 )
2092 .into());
2093 }
2094
2095 Ok(mview_fragment.into_iter().next().unwrap())
2096 }
2097
2098 pub async fn has_table_been_migrated(&self, table_id: TableId) -> MetaResult<bool> {
2099 let inner = self.inner.read().await;
2100 let txn = inner.db.begin().await?;
2101 has_table_been_migrated(&txn, table_id).await
2102 }
2103
2104 pub async fn update_fragment_splits<C>(
2105 &self,
2106 txn: &C,
2107 fragment_splits: &HashMap<FragmentId, Vec<SplitImpl>>,
2108 ) -> MetaResult<()>
2109 where
2110 C: ConnectionTrait,
2111 {
2112 if fragment_splits.is_empty() {
2113 return Ok(());
2114 }
2115
2116 let existing_fragment_ids: HashSet<FragmentId> = FragmentModel::find()
2117 .select_only()
2118 .column(fragment::Column::FragmentId)
2119 .filter(fragment::Column::FragmentId.is_in(fragment_splits.keys().copied()))
2120 .into_tuple()
2121 .all(txn)
2122 .await?
2123 .into_iter()
2124 .collect();
2125
2126 let (models, skipped_fragment_ids): (Vec<_>, Vec<_>) = fragment_splits
2128 .iter()
2129 .partition_map(|(fragment_id, splits)| {
2130 if existing_fragment_ids.contains(fragment_id) {
2131 Either::Left(fragment_splits::ActiveModel {
2132 fragment_id: Set(*fragment_id as _),
2133 splits: Set(Some(ConnectorSplits::from(&PbConnectorSplits {
2134 splits: splits.iter().map(Into::into).collect_vec(),
2135 }))),
2136 })
2137 } else {
2138 Either::Right(*fragment_id)
2139 }
2140 });
2141
2142 if !skipped_fragment_ids.is_empty() {
2143 tracing::warn!(
2144 skipped_fragment_ids = ?skipped_fragment_ids,
2145 total_fragment_ids = fragment_splits.len(),
2146 "skipping stale fragment split updates for missing fragments"
2147 );
2148 }
2149
2150 if models.is_empty() {
2151 return Ok(());
2152 }
2153
2154 FragmentSplits::insert_many(models)
2155 .on_conflict(
2156 OnConflict::column(fragment_splits::Column::FragmentId)
2157 .update_column(fragment_splits::Column::Splits)
2158 .to_owned(),
2159 )
2160 .exec(txn)
2161 .await?;
2162
2163 Ok(())
2164 }
2165}
2166
2167#[cfg(test)]
2168mod tests {
2169 use std::collections::{BTreeMap, HashMap, HashSet};
2170
2171 use itertools::Itertools;
2172 use risingwave_common::catalog::{FragmentTypeFlag, FragmentTypeMask};
2173 use risingwave_common::hash::{ActorMapping, VirtualNode, VnodeCount};
2174 use risingwave_common::id::JobId;
2175 use risingwave_common::util::iter_util::ZipEqDebug;
2176 use risingwave_common::util::stream_graph_visitor::visit_stream_node_body;
2177 use risingwave_meta_model::fragment::DistributionType;
2178 use risingwave_meta_model::*;
2179 use risingwave_pb::meta::table_fragments::fragment::PbFragmentDistributionType;
2180 use risingwave_pb::plan_common::PbExprContext;
2181 use risingwave_pb::source::{PbConnectorSplit, PbConnectorSplits};
2182 use risingwave_pb::stream_plan::stream_node::PbNodeBody;
2183 use risingwave_pb::stream_plan::{MergeNode, PbStreamNode, PbUnionNode};
2184
2185 use super::ActorInfo;
2186 use crate::MetaResult;
2187 use crate::controller::catalog::CatalogController;
2188 use crate::model::{Fragment, StreamActor};
2189
2190 type ActorUpstreams = BTreeMap<crate::model::FragmentId, HashSet<crate::model::ActorId>>;
2191
2192 type FragmentActorUpstreams = HashMap<crate::model::ActorId, ActorUpstreams>;
2193
2194 const TEST_FRAGMENT_ID: FragmentId = FragmentId::new(1);
2195
2196 const TEST_UPSTREAM_FRAGMENT_ID: FragmentId = FragmentId::new(2);
2197
2198 const TEST_JOB_ID: JobId = JobId::new(1);
2199
2200 const TEST_STATE_TABLE_ID: TableId = TableId::new(1000);
2201
2202 fn generate_upstream_actor_ids_for_actor(actor_id: ActorId) -> ActorUpstreams {
2203 let mut upstream_actor_ids = BTreeMap::new();
2204 upstream_actor_ids.insert(
2205 TEST_UPSTREAM_FRAGMENT_ID,
2206 HashSet::from_iter([(actor_id + 100)]),
2207 );
2208 upstream_actor_ids.insert(
2209 (TEST_UPSTREAM_FRAGMENT_ID + 1) as _,
2210 HashSet::from_iter([(actor_id + 200)]),
2211 );
2212 upstream_actor_ids
2213 }
2214
2215 fn generate_merger_stream_node(actor_upstream_actor_ids: &ActorUpstreams) -> PbStreamNode {
2216 let mut input = vec![];
2217 for &upstream_fragment_id in actor_upstream_actor_ids.keys() {
2218 input.push(PbStreamNode {
2219 node_body: Some(PbNodeBody::Merge(Box::new(MergeNode {
2220 upstream_fragment_id,
2221 ..Default::default()
2222 }))),
2223 ..Default::default()
2224 });
2225 }
2226
2227 PbStreamNode {
2228 input,
2229 node_body: Some(PbNodeBody::Union(PbUnionNode {})),
2230 ..Default::default()
2231 }
2232 }
2233
2234 #[tokio::test]
2235 async fn test_extract_fragment() -> MetaResult<()> {
2236 let actor_count = 3u32;
2237 let upstream_actor_ids: FragmentActorUpstreams = (0..actor_count)
2238 .map(|actor_id| {
2239 (
2240 actor_id.into(),
2241 generate_upstream_actor_ids_for_actor(actor_id.into()),
2242 )
2243 })
2244 .collect();
2245
2246 let stream_node = generate_merger_stream_node(upstream_actor_ids.values().next().unwrap());
2247
2248 let pb_fragment = Fragment {
2249 fragment_id: TEST_FRAGMENT_ID as _,
2250 fragment_type_mask: FragmentTypeMask::from(FragmentTypeFlag::Source as u32),
2251 distribution_type: PbFragmentDistributionType::Hash as _,
2252 state_table_ids: vec![TEST_STATE_TABLE_ID as _],
2253 maybe_vnode_count: VnodeCount::for_test().to_protobuf(),
2254 nodes: stream_node,
2255 };
2256
2257 let fragment =
2258 CatalogController::prepare_fragment_model_for_new_job(TEST_JOB_ID, &pb_fragment)?;
2259
2260 check_fragment(fragment, pb_fragment);
2261
2262 Ok(())
2263 }
2264
2265 #[tokio::test]
2266 async fn test_compose_fragment() -> MetaResult<()> {
2267 let actor_count = 3u32;
2268
2269 let upstream_actor_ids: FragmentActorUpstreams = (0..actor_count)
2270 .map(|actor_id| {
2271 (
2272 actor_id.into(),
2273 generate_upstream_actor_ids_for_actor(actor_id.into()),
2274 )
2275 })
2276 .collect();
2277
2278 let mut actor_bitmaps = ActorMapping::new_uniform(
2279 (0..actor_count).map(|i| i.into()),
2280 VirtualNode::COUNT_FOR_TEST,
2281 )
2282 .to_bitmaps();
2283
2284 let actors = (0..actor_count)
2285 .map(|actor_id| {
2286 let actor_splits = ConnectorSplits::from(&PbConnectorSplits {
2287 splits: vec![PbConnectorSplit {
2288 split_type: "dummy".to_owned(),
2289 ..Default::default()
2290 }],
2291 });
2292
2293 ActorInfo {
2294 actor_id: actor_id.into(),
2295 fragment_id: TEST_FRAGMENT_ID,
2296 splits: actor_splits,
2297 worker_id: 0.into(),
2298 vnode_bitmap: actor_bitmaps
2299 .remove(&actor_id)
2300 .map(|bitmap| bitmap.to_protobuf())
2301 .as_ref()
2302 .map(VnodeBitmap::from),
2303 expr_context: ExprContext::from(&PbExprContext {
2304 time_zone: String::from("America/New_York"),
2305 strict_mode: false,
2306 }),
2307 config_override: "a.b.c = true".into(),
2308 }
2309 })
2310 .collect_vec();
2311
2312 let stream_node = {
2313 let template_actor = actors.first().cloned().unwrap();
2314
2315 let template_upstream_actor_ids =
2316 upstream_actor_ids.get(&template_actor.actor_id).unwrap();
2317
2318 generate_merger_stream_node(template_upstream_actor_ids)
2319 };
2320
2321 #[expect(deprecated)]
2322 let fragment = fragment::Model {
2323 fragment_id: TEST_FRAGMENT_ID,
2324 job_id: TEST_JOB_ID,
2325 fragment_type_mask: 0,
2326 distribution_type: DistributionType::Hash,
2327 stream_node: StreamNode::from(&stream_node),
2328 state_table_ids: TableIdArray(vec![TEST_STATE_TABLE_ID]),
2329 upstream_fragment_id: Default::default(),
2330 vnode_count: VirtualNode::COUNT_FOR_TEST as _,
2331 parallelism: None,
2332 };
2333
2334 let (pb_fragment, pb_actors, pb_actor_status, pb_actor_splits) =
2335 CatalogController::compose_fragment(fragment.clone(), actors.clone(), None).unwrap();
2336
2337 assert_eq!(pb_actor_status.len(), actor_count as usize);
2338 assert!(
2339 pb_actor_status
2340 .values()
2341 .all(|actor_status| actor_status.location.is_some())
2342 );
2343 assert_eq!(pb_actor_splits.len(), actor_count as usize);
2344
2345 check_fragment(fragment, pb_fragment);
2346 check_actors(
2347 actors,
2348 &upstream_actor_ids,
2349 pb_actors,
2350 pb_actor_splits,
2351 &stream_node,
2352 );
2353
2354 Ok(())
2355 }
2356
2357 fn check_actors(
2358 actors: Vec<ActorInfo>,
2359 actor_upstreams: &FragmentActorUpstreams,
2360 pb_actors: Vec<StreamActor>,
2361 pb_actor_splits: HashMap<ActorId, PbConnectorSplits>,
2362 stream_node: &PbStreamNode,
2363 ) {
2364 for (
2365 ActorInfo {
2366 actor_id,
2367 fragment_id,
2368 splits,
2369 worker_id: _,
2370 vnode_bitmap,
2371 expr_context,
2372 ..
2373 },
2374 StreamActor {
2375 actor_id: pb_actor_id,
2376 fragment_id: pb_fragment_id,
2377 vnode_bitmap: pb_vnode_bitmap,
2378 mview_definition,
2379 expr_context: pb_expr_context,
2380 ..
2381 },
2382 ) in actors.into_iter().zip_eq_debug(pb_actors.into_iter())
2383 {
2384 assert_eq!(actor_id, pb_actor_id as ActorId);
2385 assert_eq!(fragment_id, pb_fragment_id as FragmentId);
2386
2387 assert_eq!(
2388 vnode_bitmap.map(|bitmap| bitmap.to_protobuf().into()),
2389 pb_vnode_bitmap,
2390 );
2391
2392 assert_eq!(mview_definition, "");
2393
2394 visit_stream_node_body(stream_node, |body| {
2395 if let PbNodeBody::Merge(m) = body {
2396 assert!(
2397 actor_upstreams
2398 .get(&actor_id)
2399 .unwrap()
2400 .contains_key(&m.upstream_fragment_id)
2401 );
2402 }
2403 });
2404
2405 assert_eq!(
2406 splits,
2407 pb_actor_splits
2408 .get(&pb_actor_id)
2409 .map(ConnectorSplits::from)
2410 .unwrap_or_default()
2411 );
2412
2413 assert_eq!(Some(expr_context.to_protobuf()), pb_expr_context);
2414 }
2415 }
2416
2417 fn check_fragment(fragment: fragment::Model, pb_fragment: Fragment) {
2418 let Fragment {
2419 fragment_id,
2420 fragment_type_mask,
2421 distribution_type: pb_distribution_type,
2422 state_table_ids: pb_state_table_ids,
2423 maybe_vnode_count: _,
2424 nodes,
2425 } = pb_fragment;
2426
2427 assert_eq!(fragment_id, TEST_FRAGMENT_ID);
2428 assert_eq!(fragment_type_mask, fragment.fragment_type_mask.into());
2429 assert_eq!(
2430 pb_distribution_type,
2431 PbFragmentDistributionType::from(fragment.distribution_type)
2432 );
2433
2434 assert_eq!(pb_state_table_ids, fragment.state_table_ids.0);
2435 assert_eq!(fragment.stream_node.to_protobuf(), nodes);
2436 }
2437
2438 #[test]
2439 fn test_parallelism_policy_with_root_fragments() {
2440 #[expect(deprecated)]
2441 let fragment = fragment::Model {
2442 fragment_id: 3.into(),
2443 job_id: TEST_JOB_ID,
2444 fragment_type_mask: 0,
2445 distribution_type: DistributionType::Hash,
2446 stream_node: StreamNode::from(&PbStreamNode::default()),
2447 state_table_ids: TableIdArray::default(),
2448 upstream_fragment_id: Default::default(),
2449 vnode_count: 0,
2450 parallelism: None,
2451 };
2452
2453 let job_parallelism = StreamingParallelism::Fixed(4);
2454
2455 let policy = super::CatalogController::format_fragment_parallelism_policy(
2456 fragment.distribution_type,
2457 fragment.parallelism.as_ref(),
2458 Some(&job_parallelism),
2459 None,
2460 &[],
2461 );
2462
2463 assert_eq!(policy, "inherit(4)");
2464 }
2465
2466 #[test]
2467 fn test_parallelism_policy_with_adaptive_strategy() {
2468 #[expect(deprecated)]
2469 let fragment = fragment::Model {
2470 fragment_id: 4.into(),
2471 job_id: TEST_JOB_ID,
2472 fragment_type_mask: 0,
2473 distribution_type: DistributionType::Hash,
2474 stream_node: StreamNode::from(&PbStreamNode::default()),
2475 state_table_ids: TableIdArray::default(),
2476 upstream_fragment_id: Default::default(),
2477 vnode_count: 0,
2478 parallelism: None,
2479 };
2480
2481 let job_parallelism = StreamingParallelism::Adaptive;
2482
2483 let policy = super::CatalogController::format_fragment_parallelism_policy(
2484 fragment.distribution_type,
2485 fragment.parallelism.as_ref(),
2486 Some(&job_parallelism),
2487 Some("RATIO(0.5)"),
2488 &[],
2489 );
2490
2491 assert_eq!(policy, "inherit(ratio(0.5))");
2492 }
2493
2494 #[test]
2495 fn test_parallelism_policy_with_custom_strategy() {
2496 #[expect(deprecated)]
2497 let fragment = fragment::Model {
2498 fragment_id: 6.into(),
2499 job_id: TEST_JOB_ID,
2500 fragment_type_mask: 0,
2501 distribution_type: DistributionType::Hash,
2502 stream_node: StreamNode::from(&PbStreamNode::default()),
2503 state_table_ids: TableIdArray::default(),
2504 upstream_fragment_id: Default::default(),
2505 vnode_count: 0,
2506 parallelism: None,
2507 };
2508
2509 let job_parallelism = StreamingParallelism::Custom;
2510
2511 let policy = super::CatalogController::format_fragment_parallelism_policy(
2512 fragment.distribution_type,
2513 fragment.parallelism.as_ref(),
2514 Some(&job_parallelism),
2515 Some("BOUNDED(8)"),
2516 &[],
2517 );
2518
2519 assert_eq!(policy, "inherit(bounded(8))");
2520 }
2521
2522 #[test]
2523 fn test_parallelism_policy_with_invalid_adaptive_strategy_falls_back() {
2524 #[expect(deprecated)]
2525 let fragment = fragment::Model {
2526 fragment_id: 7.into(),
2527 job_id: TEST_JOB_ID,
2528 fragment_type_mask: 0,
2529 distribution_type: DistributionType::Hash,
2530 stream_node: StreamNode::from(&PbStreamNode::default()),
2531 state_table_ids: TableIdArray::default(),
2532 upstream_fragment_id: Default::default(),
2533 vnode_count: 0,
2534 parallelism: None,
2535 };
2536
2537 let job_parallelism = StreamingParallelism::Adaptive;
2538
2539 let policy = super::CatalogController::format_fragment_parallelism_policy(
2540 fragment.distribution_type,
2541 fragment.parallelism.as_ref(),
2542 Some(&job_parallelism),
2543 Some("NOT_A_STRATEGY"),
2544 &[],
2545 );
2546
2547 assert_eq!(policy, "inherit(adaptive)");
2548 }
2549
2550 #[test]
2551 fn test_parallelism_policy_with_upstream_roots() {
2552 #[expect(deprecated)]
2553 let fragment = fragment::Model {
2554 fragment_id: 5.into(),
2555 job_id: TEST_JOB_ID,
2556 fragment_type_mask: 0,
2557 distribution_type: DistributionType::Hash,
2558 stream_node: StreamNode::from(&PbStreamNode::default()),
2559 state_table_ids: TableIdArray::default(),
2560 upstream_fragment_id: Default::default(),
2561 vnode_count: 0,
2562 parallelism: None,
2563 };
2564
2565 let policy = super::CatalogController::format_fragment_parallelism_policy(
2566 fragment.distribution_type,
2567 fragment.parallelism.as_ref(),
2568 None,
2569 None,
2570 &[3.into(), 1.into(), 2.into(), 1.into()],
2571 );
2572
2573 assert_eq!(policy, "upstream_fragment([1, 2, 3])");
2574 }
2575}