1use std::collections::{HashMap, HashSet};
16use std::fmt::{Display, Formatter};
17
18use itertools::Itertools;
19use risingwave_common::bitmap::Bitmap;
20use risingwave_common::catalog::{DatabaseId, TableId};
21use risingwave_common::hash::{ActorMapping, VnodeCountCompat};
22use risingwave_common::id::{JobId, SinkId, SourceId};
23use risingwave_common::must_match;
24use risingwave_connector::source::{CdcTableSnapshotSplitRaw, SplitImpl};
25use risingwave_hummock_sdk::change_log::build_table_change_log_delta;
26use risingwave_hummock_sdk::vector_index::VectorIndexDelta;
27use risingwave_meta_model::{DispatcherType, WorkerId, fragment_relation, streaming_job};
28use risingwave_pb::catalog::CreateType;
29use risingwave_pb::common::PbActorInfo;
30use risingwave_pb::hummock::vector_index_delta::PbVectorIndexInit;
31use risingwave_pb::plan_common::{ColumnCatalog as PbColumnCatalog, PbField};
32use risingwave_pb::source::{
33 ConnectorSplit, ConnectorSplits, PbCdcTableSnapshotSplits,
34 PbCdcTableSnapshotSplitsWithGeneration,
35};
36use risingwave_pb::stream_plan::add_mutation::PbNewUpstreamSink;
37use risingwave_pb::stream_plan::barrier::BarrierKind as PbBarrierKind;
38use risingwave_pb::stream_plan::barrier_mutation::Mutation;
39use risingwave_pb::stream_plan::connector_props_change_mutation::ConnectorPropsInfo;
40use risingwave_pb::stream_plan::sink_schema_change::Op as PbSinkSchemaChangeOp;
41use risingwave_pb::stream_plan::throttle_mutation::ThrottleConfig;
42use risingwave_pb::stream_plan::update_mutation::{DispatcherUpdate, MergeUpdate};
43use risingwave_pb::stream_plan::{
44 AddMutation, ConnectorPropsChangeMutation, Dispatcher, Dispatchers, DropSubscriptionsMutation,
45 ListFinishMutation, LoadFinishMutation, PauseMutation, PbSinkAddColumnsOp, PbSinkDropColumnsOp,
46 PbSinkSchemaChange, PbStreamNode, PbUpstreamSinkInfo, ResumeMutation,
47 SourceChangeSplitMutation, StartFragmentBackfillMutation, StopMutation,
48 SubscriptionUpstreamInfo, ThrottleMutation, UpdateMutation,
49};
50use risingwave_pb::stream_service::BarrierCompleteResponse;
51use tracing::warn;
52
53use super::info::InflightDatabaseInfo;
54use crate::barrier::backfill_order_control::get_nodes_with_backfill_dependencies;
55use crate::barrier::complete_task::CompleteBarrierTask;
56use crate::barrier::edge_builder::FragmentEdgeBuildResult;
57use crate::barrier::partial_graph::PartialGraphBarrierInfo;
58use crate::barrier::rpc::{ControlStreamManager, to_partial_graph_id};
59use crate::barrier::utils::{collect_new_vector_index_info, collect_resp_info};
60use crate::controller::fragment::{InflightActorInfo, InflightFragmentInfo};
61use crate::controller::scale::LoadedFragmentContext;
62use crate::controller::utils::StreamingJobExtraInfo;
63use crate::hummock::NewTableFragmentInfo;
64use crate::manager::{StreamingJob, StreamingJobType};
65use crate::model::{
66 ActorId, ActorUpstreams, DispatcherId, FragmentActorDispatchers, FragmentDownstreamRelation,
67 FragmentId, FragmentReplaceUpstream, StreamActor, StreamActorWithDispatchers,
68 StreamJobActorsToCreate, StreamJobFragments, StreamJobFragmentsToCreate, SubscriptionId,
69};
70use crate::stream::{
71 AutoRefreshSchemaSinkContext, ConnectorPropsChange, ExtendedFragmentBackfillOrder,
72 ReplaceJobSplitPlan, SourceSplitAssignment, SplitAssignment, SplitState, UpstreamSinkInfo,
73 build_actor_connector_splits,
74};
75use crate::{MetaError, MetaResult};
76
77pub(crate) type ThrottleConfigMap = HashMap<FragmentId, (ThrottleConfig, PbStreamNode)>;
78
79pub(crate) fn extract_throttle_config(
80 config: &mut ThrottleConfigMap,
81 mut apply: impl FnMut(FragmentId, &PbStreamNode) -> bool,
82) -> Option<Mutation> {
83 let fragment_throttle = config
84 .extract_if(|fragment_id, (_, stream_node)| apply(*fragment_id, stream_node))
85 .map(|(fragment_id, (throttle_config, _))| (fragment_id, throttle_config))
86 .collect::<HashMap<_, _>>();
87 (!fragment_throttle.is_empty())
88 .then_some(Mutation::Throttle(ThrottleMutation { fragment_throttle }))
89}
90
91#[derive(Debug, Clone)]
94pub struct Reschedule {
95 pub added_actors: HashMap<WorkerId, Vec<ActorId>>,
97
98 pub removed_actors: HashSet<ActorId>,
100
101 pub vnode_bitmap_updates: HashMap<ActorId, Bitmap>,
103
104 pub upstream_fragment_dispatcher_ids: Vec<(FragmentId, DispatcherId)>,
106 pub upstream_dispatcher_mapping: Option<ActorMapping>,
111
112 pub downstream_fragment_ids: Vec<FragmentId>,
114
115 pub actor_splits: HashMap<ActorId, Vec<SplitImpl>>,
119
120 pub newly_created_actors: HashMap<ActorId, (StreamActorWithDispatchers, WorkerId)>,
121}
122
123#[derive(Debug, Clone)]
124pub struct ReschedulePlan {
125 pub reschedules: HashMap<FragmentId, Reschedule>,
126 pub fragment_actors: HashMap<FragmentId, HashSet<ActorId>>,
129}
130
131#[derive(Debug, Clone)]
133pub struct RescheduleContext {
134 pub loaded: LoadedFragmentContext,
135 pub job_extra_info: HashMap<JobId, StreamingJobExtraInfo>,
136 pub upstream_fragments: HashMap<FragmentId, HashMap<FragmentId, DispatcherType>>,
137 pub downstream_fragments: HashMap<FragmentId, HashMap<FragmentId, DispatcherType>>,
138 pub downstream_relations: HashMap<(FragmentId, FragmentId), fragment_relation::Model>,
139}
140
141impl RescheduleContext {
142 pub fn empty() -> Self {
143 Self {
144 loaded: LoadedFragmentContext::default(),
145 job_extra_info: HashMap::new(),
146 upstream_fragments: HashMap::new(),
147 downstream_fragments: HashMap::new(),
148 downstream_relations: HashMap::new(),
149 }
150 }
151
152 pub fn is_empty(&self) -> bool {
153 self.loaded.is_empty()
154 }
155
156 pub fn for_database(&self, database_id: DatabaseId) -> Option<Self> {
157 let loaded = self.loaded.for_database(database_id)?;
158 let job_ids: HashSet<JobId> = loaded.job_map.keys().copied().collect();
159 let fragment_ids: HashSet<FragmentId> = loaded
162 .job_fragments
163 .values()
164 .flat_map(|fragments| fragments.keys().copied())
165 .collect();
166
167 let job_extra_info = self
168 .job_extra_info
169 .iter()
170 .filter(|(job_id, _)| job_ids.contains(*job_id))
171 .map(|(job_id, info)| (*job_id, info.clone()))
172 .collect();
173
174 let upstream_fragments = self
175 .upstream_fragments
176 .iter()
177 .filter(|(fragment_id, _)| fragment_ids.contains(*fragment_id))
178 .map(|(fragment_id, upstreams)| (*fragment_id, upstreams.clone()))
179 .collect();
180
181 let downstream_fragments = self
182 .downstream_fragments
183 .iter()
184 .filter(|(fragment_id, _)| fragment_ids.contains(*fragment_id))
185 .map(|(fragment_id, downstreams)| (*fragment_id, downstreams.clone()))
186 .collect();
187
188 let downstream_relations = self
189 .downstream_relations
190 .iter()
191 .filter(|((source_fragment_id, _), _)| fragment_ids.contains(source_fragment_id))
195 .map(|(key, relation)| (*key, relation.clone()))
196 .collect();
197
198 Some(Self {
199 loaded,
200 job_extra_info,
201 upstream_fragments,
202 downstream_fragments,
203 downstream_relations,
204 })
205 }
206
207 pub fn into_database_contexts(self) -> HashMap<DatabaseId, Self> {
210 let Self {
211 loaded,
212 job_extra_info,
213 upstream_fragments,
214 downstream_fragments,
215 downstream_relations,
216 } = self;
217
218 let mut contexts: HashMap<_, _> = loaded
219 .into_database_contexts()
220 .into_iter()
221 .map(|(database_id, loaded)| {
222 (
223 database_id,
224 Self {
225 loaded,
226 job_extra_info: HashMap::new(),
227 upstream_fragments: HashMap::new(),
228 downstream_fragments: HashMap::new(),
229 downstream_relations: HashMap::new(),
230 },
231 )
232 })
233 .collect();
234
235 if contexts.is_empty() {
236 return contexts;
237 }
238
239 let mut job_databases = HashMap::new();
240 let mut fragment_databases = HashMap::new();
241 for (&database_id, context) in &contexts {
242 for job_id in context.loaded.job_map.keys().copied() {
243 job_databases.insert(job_id, database_id);
244 }
245 for fragment_id in context
246 .loaded
247 .job_fragments
248 .values()
249 .flat_map(|fragments| fragments.keys().copied())
250 {
251 fragment_databases.insert(fragment_id, database_id);
252 }
253 }
254
255 for (job_id, info) in job_extra_info {
256 if let Some(database_id) = job_databases.get(&job_id).copied() {
257 contexts
258 .get_mut(&database_id)
259 .expect("database context should exist for job")
260 .job_extra_info
261 .insert(job_id, info);
262 }
263 }
264
265 for (fragment_id, upstreams) in upstream_fragments {
266 if let Some(database_id) = fragment_databases.get(&fragment_id).copied() {
267 contexts
268 .get_mut(&database_id)
269 .expect("database context should exist for fragment")
270 .upstream_fragments
271 .insert(fragment_id, upstreams);
272 }
273 }
274
275 for (fragment_id, downstreams) in downstream_fragments {
276 if let Some(database_id) = fragment_databases.get(&fragment_id).copied() {
277 contexts
278 .get_mut(&database_id)
279 .expect("database context should exist for fragment")
280 .downstream_fragments
281 .insert(fragment_id, downstreams);
282 }
283 }
284
285 for ((source_fragment_id, target_fragment_id), relation) in downstream_relations {
286 if let Some(database_id) = fragment_databases.get(&source_fragment_id).copied() {
289 contexts
290 .get_mut(&database_id)
291 .expect("database context should exist for relation source")
292 .downstream_relations
293 .insert((source_fragment_id, target_fragment_id), relation);
294 }
295 }
296
297 contexts
298 }
299}
300
301#[derive(Debug, Clone)]
308pub struct ReplaceStreamJobPlan {
309 pub old_fragments: StreamJobFragments,
310 pub new_fragments: StreamJobFragmentsToCreate,
311 pub database_resource_group: String,
313 pub replace_upstream: FragmentReplaceUpstream,
316 pub upstream_fragment_downstreams: FragmentDownstreamRelation,
317 pub split_plan: ReplaceJobSplitPlan,
320 pub streaming_job: StreamingJob,
322 pub streaming_job_model: streaming_job::Model,
324 pub tmp_id: JobId,
326 pub to_drop_state_table_ids: Vec<TableId>,
328 pub auto_refresh_schema_sinks: Option<Vec<AutoRefreshSchemaSinkContext>>,
329}
330
331impl ReplaceStreamJobPlan {
332 pub fn fragment_replacements(&self) -> HashMap<FragmentId, FragmentId> {
334 let mut fragment_replacements = HashMap::new();
335 for (upstream_fragment_id, new_upstream_fragment_id) in
336 self.replace_upstream.values().flatten()
337 {
338 {
339 let r =
340 fragment_replacements.insert(*upstream_fragment_id, *new_upstream_fragment_id);
341 if let Some(r) = r {
342 assert_eq!(
343 *new_upstream_fragment_id, r,
344 "one fragment is replaced by multiple fragments"
345 );
346 }
347 }
348 }
349 fragment_replacements
350 }
351}
352
353#[derive(educe::Educe, Clone)]
354#[educe(Debug)]
355pub struct CreateStreamingJobCommandInfo {
356 #[educe(Debug(ignore))]
357 pub stream_job_fragments: StreamJobFragmentsToCreate,
358 pub upstream_fragment_downstreams: FragmentDownstreamRelation,
359 pub database_resource_group: String,
361 pub init_split_assignment: SourceSplitAssignment,
363 pub definition: String,
364 pub job_type: StreamingJobType,
365 pub create_type: CreateType,
366 pub streaming_job: StreamingJob,
367 pub fragment_backfill_ordering: ExtendedFragmentBackfillOrder,
368 pub cdc_table_snapshot_splits: Option<Vec<CdcTableSnapshotSplitRaw>>,
369 pub locality_fragment_state_table_mapping: HashMap<FragmentId, Vec<TableId>>,
370 pub is_serverless: bool,
371 pub streaming_job_model: streaming_job::Model,
373 pub replace_sink: Option<SinkId>,
375 pub refresh_interval_sec: Option<u64>,
377}
378
379impl StreamJobFragments {
380 pub(super) fn new_fragment_info<'a>(
383 &'a self,
384 stream_actors: &'a HashMap<FragmentId, Vec<StreamActor>>,
385 actor_location: &'a HashMap<ActorId, WorkerId>,
386 assignment: &'a SplitAssignment,
387 ) -> impl Iterator<Item = (FragmentId, InflightFragmentInfo)> + 'a {
388 self.fragments.values().map(|fragment| {
389 (
390 fragment.fragment_id,
391 InflightFragmentInfo {
392 fragment_id: fragment.fragment_id,
393 distribution_type: fragment.distribution_type.into(),
394 fragment_type_mask: fragment.fragment_type_mask,
395 vnode_count: fragment.vnode_count(),
396 nodes: fragment.nodes.clone(),
397 actors: stream_actors
398 .get(&fragment.fragment_id)
399 .into_iter()
400 .flatten()
401 .map(|actor| {
402 (
403 actor.actor_id,
404 InflightActorInfo {
405 worker_id: actor_location[&actor.actor_id],
406 vnode_bitmap: actor.vnode_bitmap.clone(),
407 splits: assignment
408 .get(&fragment.fragment_id)
409 .and_then(|s| s.get(&actor.actor_id))
410 .cloned()
411 .unwrap_or_default(),
412 },
413 )
414 })
415 .collect(),
416 state_table_ids: fragment.state_table_ids.iter().copied().collect(),
417 },
418 )
419 })
420 }
421}
422
423pub type TableLogEpochs = Vec<(Vec<u64>, u64)>;
424pub type UpstreamTableLogEpochs = HashMap<TableId, TableLogEpochs>;
425pub type SinceTimestampResolvedEpoch = (u64, TableLogEpochs);
426
427#[derive(Debug, Clone)]
428pub struct SnapshotBackfillInfo {
429 pub upstream_mv_table_id_to_backfill_epoch: HashMap<TableId, Option<u64>>,
433}
434
435#[derive(Debug, Clone)]
436pub struct SinceEpochInfo {
437 pub provided_since_epoch: u64,
438 pub resolved: Option<SinceTimestampResolvedEpoch>,
439}
440
441#[derive(Debug, Clone)]
442pub struct BatchRefreshInfo {
443 pub snapshot_backfill_info: SnapshotBackfillInfo,
444 pub refresh_interval_sec: u64,
445}
446
447#[derive(Debug, Clone)]
448pub enum CreateStreamingJobType {
449 Normal,
450 SinkIntoTable(UpstreamSinkInfo),
451 SnapshotBackfill {
452 snapshot_backfill_info: SnapshotBackfillInfo,
453 since_epoch: Option<SinceEpochInfo>,
454 },
455 BatchRefresh(BatchRefreshInfo),
456}
457
458#[derive(Debug)]
463pub enum Command {
464 Flush,
467
468 Pause,
471
472 Resume,
476
477 DropStreamingJobs {
485 streaming_job_ids: HashSet<JobId>,
486 unregistered_state_table_ids: HashSet<TableId>,
488 dropped_sink_fragment_by_targets: HashMap<FragmentId, Vec<FragmentId>>,
490 },
491
492 CreateStreamingJob {
502 info: CreateStreamingJobCommandInfo,
503 job_type: CreateStreamingJobType,
504 cross_db_snapshot_backfill_info: SnapshotBackfillInfo,
505 },
506
507 RescheduleIntent {
509 context: RescheduleContext,
510 reschedule_plan: Option<ReschedulePlan>,
515 },
516
517 ReplaceStreamJob(ReplaceStreamJobPlan),
524
525 SourceChangeSplit(SplitState),
528
529 Throttle {
532 config: ThrottleConfigMap,
533 },
534
535 CreateSubscription {
538 subscription_id: SubscriptionId,
539 upstream_mv_table_id: TableId,
540 retention_second: u64,
541 },
542
543 DropSubscription {
547 subscription_id: SubscriptionId,
548 upstream_mv_table_id: TableId,
549 },
550
551 AlterSubscriptionRetention {
553 subscription_id: SubscriptionId,
554 upstream_mv_table_id: TableId,
555 retention_second: u64,
556 },
557
558 ConnectorPropsChange(ConnectorPropsChange),
559
560 Refresh {
563 table_id: TableId,
564 associated_source_id: SourceId,
565 },
566 ListFinish {
567 table_id: TableId,
568 associated_source_id: SourceId,
569 },
570 LoadFinish {
571 table_id: TableId,
572 associated_source_id: SourceId,
573 },
574
575 ResetSource {
578 source_id: SourceId,
579 },
580
581 ResumeBackfill {
584 target: ResumeBackfillTarget,
585 },
586
587 InjectSourceOffsets {
591 source_id: SourceId,
592 split_offsets: HashMap<String, String>,
594 },
595}
596
597#[derive(Debug, Clone, Copy)]
598pub enum ResumeBackfillTarget {
599 Job(JobId),
600 Fragment(FragmentId),
601}
602
603impl std::fmt::Display for Command {
605 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
606 match self {
607 Command::Flush => write!(f, "Flush"),
608 Command::Pause => write!(f, "Pause"),
609 Command::Resume => write!(f, "Resume"),
610 Command::DropStreamingJobs {
611 streaming_job_ids, ..
612 } => {
613 write!(
614 f,
615 "DropStreamingJobs: {}",
616 streaming_job_ids.iter().sorted().join(", ")
617 )
618 }
619 Command::CreateStreamingJob { info, .. } => {
620 write!(f, "CreateStreamingJob: {}", info.streaming_job)
621 }
622 Command::RescheduleIntent {
623 reschedule_plan, ..
624 } => {
625 if reschedule_plan.is_some() {
626 write!(f, "RescheduleIntent(planned)")
627 } else {
628 write!(f, "RescheduleIntent")
629 }
630 }
631 Command::ReplaceStreamJob(plan) => {
632 write!(f, "ReplaceStreamJob: {}", plan.streaming_job)
633 }
634 Command::SourceChangeSplit { .. } => write!(f, "SourceChangeSplit"),
635 Command::Throttle { .. } => write!(f, "Throttle"),
636 Command::CreateSubscription {
637 subscription_id, ..
638 } => write!(f, "CreateSubscription: {subscription_id}"),
639 Command::DropSubscription {
640 subscription_id, ..
641 } => write!(f, "DropSubscription: {subscription_id}"),
642 Command::AlterSubscriptionRetention {
643 subscription_id,
644 retention_second,
645 ..
646 } => write!(
647 f,
648 "AlterSubscriptionRetention: {subscription_id} -> {retention_second}"
649 ),
650 Command::ConnectorPropsChange(_) => write!(f, "ConnectorPropsChange"),
651 Command::Refresh {
652 table_id,
653 associated_source_id,
654 } => write!(
655 f,
656 "Refresh: {} (source: {})",
657 table_id, associated_source_id
658 ),
659 Command::ListFinish {
660 table_id,
661 associated_source_id,
662 } => write!(
663 f,
664 "ListFinish: {} (source: {})",
665 table_id, associated_source_id
666 ),
667 Command::LoadFinish {
668 table_id,
669 associated_source_id,
670 } => write!(
671 f,
672 "LoadFinish: {} (source: {})",
673 table_id, associated_source_id
674 ),
675 Command::ResetSource { source_id } => write!(f, "ResetSource: {source_id}"),
676 Command::ResumeBackfill { target } => match target {
677 ResumeBackfillTarget::Job(job_id) => {
678 write!(f, "ResumeBackfill: job={job_id}")
679 }
680 ResumeBackfillTarget::Fragment(fragment_id) => {
681 write!(f, "ResumeBackfill: fragment={fragment_id}")
682 }
683 },
684 Command::InjectSourceOffsets {
685 source_id,
686 split_offsets,
687 } => write!(
688 f,
689 "InjectSourceOffsets: {} ({} splits)",
690 source_id,
691 split_offsets.len()
692 ),
693 }
694 }
695}
696
697impl Command {
698 pub fn pause() -> Self {
699 Self::Pause
700 }
701
702 pub fn resume() -> Self {
703 Self::Resume
704 }
705
706 pub fn need_checkpoint(&self) -> bool {
707 !matches!(self, Command::Resume)
709 }
710}
711
712#[derive(Debug)]
713pub enum PostCollectCommand {
714 Command(String),
715 DropStreamingJobs,
716 CreateStreamingJob {
717 info: CreateStreamingJobCommandInfo,
718 job_type: CreateStreamingJobType,
719 cross_db_snapshot_backfill_info: SnapshotBackfillInfo,
720 resolved_split_assignment: SplitAssignment,
721 },
722 Reschedule {
723 reschedules: HashMap<FragmentId, Reschedule>,
724 },
725 ReplaceStreamJob {
726 plan: ReplaceStreamJobPlan,
727 resolved_split_assignment: SplitAssignment,
728 },
729 SourceChangeSplit {
730 split_assignment: SplitAssignment,
731 },
732 CreateSubscription {
733 subscription_id: SubscriptionId,
734 },
735 ConnectorPropsChange(ConnectorPropsChange),
736 ResumeBackfill {
737 target: ResumeBackfillTarget,
738 },
739}
740
741impl PostCollectCommand {
742 pub fn barrier() -> Self {
743 PostCollectCommand::Command("barrier".to_owned())
744 }
745
746 pub fn should_checkpoint(&self) -> bool {
747 match self {
748 PostCollectCommand::DropStreamingJobs
749 | PostCollectCommand::CreateStreamingJob { .. }
750 | PostCollectCommand::Reschedule { .. }
751 | PostCollectCommand::ReplaceStreamJob { .. }
752 | PostCollectCommand::SourceChangeSplit { .. }
753 | PostCollectCommand::CreateSubscription { .. }
754 | PostCollectCommand::ConnectorPropsChange(_)
755 | PostCollectCommand::ResumeBackfill { .. } => true,
756 PostCollectCommand::Command(_) => false,
757 }
758 }
759
760 pub fn command_name(&self) -> &str {
761 match self {
762 PostCollectCommand::Command(name) => name.as_str(),
763 PostCollectCommand::DropStreamingJobs => "DropStreamingJobs",
764 PostCollectCommand::CreateStreamingJob { .. } => "CreateStreamingJob",
765 PostCollectCommand::Reschedule { .. } => "Reschedule",
766 PostCollectCommand::ReplaceStreamJob { .. } => "ReplaceStreamJob",
767 PostCollectCommand::SourceChangeSplit { .. } => "SourceChangeSplit",
768 PostCollectCommand::CreateSubscription { .. } => "CreateSubscription",
769 PostCollectCommand::ConnectorPropsChange(_) => "ConnectorPropsChange",
770 PostCollectCommand::ResumeBackfill { .. } => "ResumeBackfill",
771 }
772 }
773}
774
775impl Display for PostCollectCommand {
776 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
777 f.write_str(self.command_name())
778 }
779}
780
781#[derive(Debug, Clone, PartialEq, Eq)]
782pub enum BarrierKind {
783 Initial,
784 Barrier,
785 Checkpoint(Vec<u64>),
787}
788
789impl BarrierKind {
790 pub fn to_protobuf(&self) -> PbBarrierKind {
791 match self {
792 BarrierKind::Initial => PbBarrierKind::Initial,
793 BarrierKind::Barrier => PbBarrierKind::Barrier,
794 BarrierKind::Checkpoint(_) => PbBarrierKind::Checkpoint,
795 }
796 }
797
798 pub fn is_checkpoint(&self) -> bool {
799 matches!(self, BarrierKind::Checkpoint(_))
800 }
801
802 pub fn is_initial(&self) -> bool {
803 matches!(self, BarrierKind::Initial)
804 }
805
806 pub fn as_str_name(&self) -> &'static str {
807 match self {
808 BarrierKind::Initial => "Initial",
809 BarrierKind::Barrier => "Barrier",
810 BarrierKind::Checkpoint(_) => "Checkpoint",
811 }
812 }
813}
814
815fn sink_original_schema_fields(columns: &[PbColumnCatalog]) -> Vec<PbField> {
816 columns
817 .iter()
818 .filter(|col| !col.is_hidden)
819 .map(|col| {
820 let column_desc = col
821 .column_desc
822 .as_ref()
823 .expect("sink column catalog should have a column descriptor");
824 PbField {
825 data_type: Some(
826 column_desc
827 .column_type
828 .as_ref()
829 .expect("sink column descriptor should have a column type")
830 .clone(),
831 ),
832 name: column_desc.name.clone(),
833 }
834 })
835 .collect()
836}
837
838impl Command {
839 pub(super) fn collect_commit_epoch_info(
840 database_info: &InflightDatabaseInfo,
841 barrier_info: &PartialGraphBarrierInfo,
842 task: &mut CompleteBarrierTask,
843 resps: Vec<BarrierCompleteResponse>,
844 backfill_pinned_upstream_tables: HashSet<TableId>,
845 ) {
846 let (
847 sst_to_context,
848 synced_ssts,
849 new_table_watermarks,
850 old_value_ssts,
851 vector_index_adds,
852 truncate_tables,
853 iceberg_pk_index_sink_metadata,
854 ) = collect_resp_info(resps);
855
856 let new_table_fragment_infos = match &barrier_info.post_collect_command {
857 PostCollectCommand::CreateStreamingJob { info, job_type, .. } => {
858 assert!(!matches!(
859 job_type,
860 CreateStreamingJobType::SnapshotBackfill { .. }
861 | CreateStreamingJobType::BatchRefresh(_)
862 ));
863 let table_fragments = &info.stream_job_fragments;
864 let mut table_ids: HashSet<_> =
865 table_fragments.internal_table_ids().into_iter().collect();
866 if let Some(mv_table_id) = table_fragments.mv_table_id() {
867 table_ids.insert(mv_table_id);
868 }
869
870 vec![NewTableFragmentInfo { table_ids }]
871 }
872 _ => vec![],
873 };
874
875 let mut log_store_table_ids = HashSet::new();
876 log_store_table_ids.extend(database_info.subscribed_tables());
878 log_store_table_ids.extend(backfill_pinned_upstream_tables);
879
880 let table_new_change_log = build_table_change_log_delta(
881 old_value_ssts.into_iter(),
882 synced_ssts.iter().map(|sst| &sst.sst_info),
883 must_match!(&barrier_info.barrier_info.kind, BarrierKind::Checkpoint(epochs) => epochs),
884 log_store_table_ids.into_iter(),
885 );
886
887 let epoch = barrier_info.barrier_info.prev_epoch();
888 let info = &mut task.commit_info;
889 for table_id in &barrier_info.table_ids_to_commit {
890 info.tables_to_commit
891 .try_insert(*table_id, epoch)
892 .expect("non duplicate");
893 }
894
895 info.sstables.extend(synced_ssts);
896 info.new_table_watermarks.extend(new_table_watermarks);
897 info.sst_to_context.extend(sst_to_context);
898 info.new_table_fragment_infos
899 .extend(new_table_fragment_infos);
900 info.change_log_delta.extend(table_new_change_log);
901 for (table_id, vector_index_adds) in vector_index_adds {
902 info.vector_index_delta
903 .try_insert(table_id, VectorIndexDelta::Adds(vector_index_adds))
904 .expect("non-duplicate");
905 }
906 if let PostCollectCommand::CreateStreamingJob { info: job_info, .. } =
907 &barrier_info.post_collect_command
908 && let Some(index_table) = collect_new_vector_index_info(job_info)
909 {
910 info.vector_index_delta
911 .try_insert(
912 index_table.id,
913 VectorIndexDelta::Init(PbVectorIndexInit {
914 info: Some(index_table.vector_index_info.unwrap()),
915 }),
916 )
917 .expect("non-duplicate");
918 }
919 info.truncate_tables.extend(truncate_tables);
920 task.iceberg_pk_index_pre_commit_metadata
921 .extend(iceberg_pk_index_sink_metadata.into_iter().map(Into::into));
922 }
923}
924
925impl Command {
926 pub(super) fn pause_to_mutation(is_currently_paused: bool) -> Option<Mutation> {
928 {
929 {
930 if !is_currently_paused {
933 Some(Mutation::Pause(PauseMutation {}))
934 } else {
935 None
936 }
937 }
938 }
939 }
940
941 pub(super) fn resume_to_mutation(is_currently_paused: bool) -> Option<Mutation> {
943 {
944 {
945 if is_currently_paused {
947 Some(Mutation::Resume(ResumeMutation {}))
948 } else {
949 None
950 }
951 }
952 }
953 }
954
955 pub(super) fn source_change_split_to_mutation(split_assignment: &SplitAssignment) -> Mutation {
957 {
958 {
959 let mut diff = HashMap::new();
960
961 for actor_splits in split_assignment.values() {
962 diff.extend(actor_splits.clone());
963 }
964
965 Mutation::Splits(SourceChangeSplitMutation {
966 actor_splits: build_actor_connector_splits(&diff),
967 })
968 }
969 }
970 }
971
972 pub(super) fn drop_streaming_jobs_to_mutation(
974 actors: &Vec<ActorId>,
975 dropped_sink_fragment_by_targets: &HashMap<FragmentId, Vec<FragmentId>>,
976 ) -> Mutation {
977 {
978 Mutation::Stop(StopMutation {
979 actors: actors.clone(),
980 dropped_sink_fragments: dropped_sink_fragment_by_targets
981 .values()
982 .flatten()
983 .cloned()
984 .collect(),
985 })
986 }
987 }
988
989 pub(super) fn create_streaming_job_to_mutation(
991 info: &CreateStreamingJobCommandInfo,
992 job_type: &CreateStreamingJobType,
993 dropped_actors: impl IntoIterator<Item = ActorId>,
994 is_currently_paused: bool,
995 edges: &mut FragmentEdgeBuildResult,
996 control_stream_manager: &ControlStreamManager,
997 actor_cdc_table_snapshot_splits: Option<HashMap<ActorId, PbCdcTableSnapshotSplits>>,
998 split_assignment: &SplitAssignment,
999 stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
1000 actor_location: &HashMap<ActorId, WorkerId>,
1001 ) -> MetaResult<Mutation> {
1002 {
1003 {
1004 let CreateStreamingJobCommandInfo {
1005 stream_job_fragments,
1006 upstream_fragment_downstreams,
1007 fragment_backfill_ordering,
1008 streaming_job,
1009 ..
1010 } = info;
1011 let database_id = streaming_job.database_id();
1012 let added_actors: Vec<ActorId> = stream_actors
1013 .values()
1014 .flatten()
1015 .map(|actor| actor.actor_id)
1016 .collect();
1017 let dropped_actors = dropped_actors.into_iter().collect();
1018 let actor_splits = split_assignment
1019 .values()
1020 .flat_map(build_actor_connector_splits)
1021 .collect();
1022 let subscriptions_to_add = {
1023 if let CreateStreamingJobType::SnapshotBackfill {
1024 snapshot_backfill_info,
1025 ..
1026 }
1027 | CreateStreamingJobType::BatchRefresh(BatchRefreshInfo {
1028 snapshot_backfill_info,
1029 ..
1030 }) = job_type
1031 {
1032 snapshot_backfill_info
1033 .upstream_mv_table_id_to_backfill_epoch
1034 .keys()
1035 .map(|table_id| SubscriptionUpstreamInfo {
1036 subscriber_id: stream_job_fragments
1037 .stream_job_id()
1038 .as_subscriber_id(),
1039 upstream_mv_table_id: *table_id,
1040 })
1041 .collect()
1042 } else {
1043 Default::default()
1044 }
1045 };
1046 let backfill_nodes_to_pause: Vec<_> =
1047 get_nodes_with_backfill_dependencies(fragment_backfill_ordering)
1048 .into_iter()
1049 .collect();
1050
1051 let new_upstream_sinks =
1052 if let CreateStreamingJobType::SinkIntoTable(UpstreamSinkInfo {
1053 sink_fragment_id,
1054 sink_output_fields,
1055 project_exprs,
1056 new_sink_downstream,
1057 ..
1058 }) = job_type
1059 {
1060 let new_sink_actors = stream_actors
1061 .get(sink_fragment_id)
1062 .unwrap_or_else(|| {
1063 panic!("upstream sink fragment {sink_fragment_id} does not exist")
1064 })
1065 .iter()
1066 .map(|actor| {
1067 let worker_id = actor_location[&actor.actor_id];
1068 PbActorInfo {
1069 actor_id: actor.actor_id,
1070 host: Some(control_stream_manager.host_addr(worker_id)),
1071 partial_graph_id: to_partial_graph_id(database_id, None),
1072 }
1073 });
1074 let new_upstream_sink = PbNewUpstreamSink {
1075 info: Some(PbUpstreamSinkInfo {
1076 upstream_fragment_id: *sink_fragment_id,
1077 sink_output_schema: sink_output_fields.clone(),
1078 project_exprs: project_exprs.clone(),
1079 }),
1080 upstream_actors: new_sink_actors.collect(),
1081 };
1082 HashMap::from([(
1083 new_sink_downstream.downstream_fragment_id,
1084 new_upstream_sink,
1085 )])
1086 } else {
1087 HashMap::new()
1088 };
1089
1090 let actor_cdc_table_snapshot_splits = actor_cdc_table_snapshot_splits
1091 .map(|splits| PbCdcTableSnapshotSplitsWithGeneration { splits });
1092
1093 let add_mutation = AddMutation {
1094 actor_dispatchers: edges
1095 .dispatchers
1096 .extract_if(|fragment_id, _| {
1097 upstream_fragment_downstreams.contains_key(fragment_id)
1098 })
1099 .flat_map(|(_, fragment_dispatchers)| fragment_dispatchers.into_iter())
1100 .map(|(actor_id, dispatchers)| (actor_id, Dispatchers { dispatchers }))
1101 .collect(),
1102 added_actors,
1103 actor_splits,
1104 pause: is_currently_paused,
1106 subscriptions_to_add,
1107 backfill_nodes_to_pause,
1108 actor_cdc_table_snapshot_splits,
1109 new_upstream_sinks,
1110 dropped_actors,
1111 sink_log_store_flush: info
1112 .replace_sink
1113 .map(|old_sink_id| vec![old_sink_id])
1114 .unwrap_or_default(),
1115 };
1116
1117 Ok(Mutation::Add(add_mutation))
1118 }
1119 }
1120 }
1121
1122 pub(super) fn replace_stream_job_to_mutation(
1124 ReplaceStreamJobPlan {
1125 old_fragments,
1126 replace_upstream,
1127 upstream_fragment_downstreams,
1128 auto_refresh_schema_sinks,
1129 ..
1130 }: &ReplaceStreamJobPlan,
1131 edges: &mut FragmentEdgeBuildResult,
1132 database_info: &mut InflightDatabaseInfo,
1133 split_assignment: &SplitAssignment,
1134 ) -> MetaResult<Option<Mutation>> {
1135 {
1136 {
1137 let merge_updates = edges
1138 .merge_updates
1139 .extract_if(|fragment_id, _| replace_upstream.contains_key(fragment_id))
1140 .collect();
1141 let dispatchers = edges
1142 .dispatchers
1143 .extract_if(|fragment_id, _| {
1144 upstream_fragment_downstreams.contains_key(fragment_id)
1145 })
1146 .collect();
1147 let actor_cdc_table_snapshot_splits = database_info
1148 .assign_cdc_backfill_splits(old_fragments.stream_job_id)?
1149 .map(|splits| PbCdcTableSnapshotSplitsWithGeneration { splits });
1150 let old_fragments = old_fragments.fragments.keys().copied();
1151 let auto_refresh_sink_fragment_ids = auto_refresh_schema_sinks
1152 .as_ref()
1153 .into_iter()
1154 .flat_map(|sinks| sinks.iter())
1155 .map(|sink| sink.original_fragment.fragment_id);
1156 Ok(Self::generate_update_mutation_for_replace_table(
1157 old_fragments
1158 .chain(auto_refresh_sink_fragment_ids)
1159 .flat_map(|fragment_id| {
1160 database_info.fragment(fragment_id).actors.keys().copied()
1161 }),
1162 merge_updates,
1163 dispatchers,
1164 split_assignment,
1165 actor_cdc_table_snapshot_splits,
1166 auto_refresh_schema_sinks.as_ref(),
1167 ))
1168 }
1169 }
1170 }
1171
1172 pub(super) fn reschedule_to_mutation(
1174 reschedules: &HashMap<FragmentId, Reschedule>,
1175 fragment_actors: &HashMap<FragmentId, HashSet<ActorId>>,
1176 control_stream_manager: &ControlStreamManager,
1177 database_info: &mut InflightDatabaseInfo,
1178 ) -> MetaResult<Option<Mutation>> {
1179 {
1180 {
1181 let database_id = database_info.database_id;
1182 let mut dispatcher_update = HashMap::new();
1183 for reschedule in reschedules.values() {
1184 for &(upstream_fragment_id, dispatcher_id) in
1185 &reschedule.upstream_fragment_dispatcher_ids
1186 {
1187 let upstream_actor_ids = fragment_actors
1189 .get(&upstream_fragment_id)
1190 .expect("should contain");
1191
1192 let upstream_reschedule = reschedules.get(&upstream_fragment_id);
1193
1194 for &actor_id in upstream_actor_ids {
1196 let added_downstream_actor_id = if upstream_reschedule
1197 .map(|reschedule| !reschedule.removed_actors.contains(&actor_id))
1198 .unwrap_or(true)
1199 {
1200 reschedule
1201 .added_actors
1202 .values()
1203 .flatten()
1204 .cloned()
1205 .collect()
1206 } else {
1207 Default::default()
1208 };
1209 dispatcher_update
1211 .try_insert(
1212 (actor_id, dispatcher_id),
1213 DispatcherUpdate {
1214 actor_id,
1215 dispatcher_id,
1216 hash_mapping: reschedule
1217 .upstream_dispatcher_mapping
1218 .as_ref()
1219 .map(|m| m.to_protobuf()),
1220 added_downstream_actor_id,
1221 removed_downstream_actor_id: reschedule
1222 .removed_actors
1223 .iter()
1224 .cloned()
1225 .collect(),
1226 },
1227 )
1228 .unwrap();
1229 }
1230 }
1231 }
1232 let dispatcher_update = dispatcher_update.into_values().collect();
1233
1234 let mut merge_update = HashMap::new();
1235 for (&fragment_id, reschedule) in reschedules {
1236 for &downstream_fragment_id in &reschedule.downstream_fragment_ids {
1237 let downstream_actor_ids = fragment_actors
1239 .get(&downstream_fragment_id)
1240 .expect("should contain");
1241
1242 let downstream_removed_actors: HashSet<_> = reschedules
1246 .get(&downstream_fragment_id)
1247 .map(|downstream_reschedule| {
1248 downstream_reschedule
1249 .removed_actors
1250 .iter()
1251 .copied()
1252 .collect()
1253 })
1254 .unwrap_or_default();
1255
1256 for &actor_id in downstream_actor_ids {
1258 if downstream_removed_actors.contains(&actor_id) {
1259 continue;
1260 }
1261
1262 merge_update
1264 .try_insert(
1265 (actor_id, fragment_id),
1266 MergeUpdate {
1267 actor_id,
1268 upstream_fragment_id: fragment_id,
1269 new_upstream_fragment_id: None,
1270 added_upstream_actors: reschedule
1271 .added_actors
1272 .iter()
1273 .flat_map(|(worker_id, actors)| {
1274 let host =
1275 control_stream_manager.host_addr(*worker_id);
1276 actors.iter().map(move |&actor_id| PbActorInfo {
1277 actor_id,
1278 host: Some(host.clone()),
1279 partial_graph_id: to_partial_graph_id(
1281 database_id,
1282 None,
1283 ),
1284 })
1285 })
1286 .collect(),
1287 removed_upstream_actor_id: reschedule
1288 .removed_actors
1289 .iter()
1290 .cloned()
1291 .collect(),
1292 },
1293 )
1294 .unwrap();
1295 }
1296 }
1297 }
1298 let merge_update = merge_update.into_values().collect();
1299
1300 let mut actor_vnode_bitmap_update = HashMap::new();
1301 for reschedule in reschedules.values() {
1302 for (&actor_id, bitmap) in &reschedule.vnode_bitmap_updates {
1304 let bitmap = bitmap.to_protobuf();
1305 actor_vnode_bitmap_update
1306 .try_insert(actor_id, bitmap)
1307 .unwrap();
1308 }
1309 }
1310 let dropped_actors = reschedules
1311 .values()
1312 .flat_map(|r| r.removed_actors.iter().copied())
1313 .collect();
1314 let mut actor_splits = HashMap::new();
1315 let mut actor_cdc_table_snapshot_splits = HashMap::new();
1316 for (fragment_id, reschedule) in reschedules {
1317 for (actor_id, splits) in &reschedule.actor_splits {
1318 actor_splits.insert(
1319 *actor_id,
1320 ConnectorSplits {
1321 splits: splits.iter().map(ConnectorSplit::from).collect(),
1322 },
1323 );
1324 }
1325
1326 if let Some(assignment) =
1327 database_info.may_assign_fragment_cdc_backfill_splits(*fragment_id)?
1328 {
1329 actor_cdc_table_snapshot_splits.extend(assignment)
1330 }
1331 }
1332
1333 let actor_new_dispatchers = HashMap::new();
1335 let mutation = Mutation::Update(UpdateMutation {
1336 dispatcher_update,
1337 merge_update,
1338 actor_vnode_bitmap_update,
1339 dropped_actors,
1340 actor_splits,
1341 actor_new_dispatchers,
1342 actor_cdc_table_snapshot_splits: Some(PbCdcTableSnapshotSplitsWithGeneration {
1343 splits: actor_cdc_table_snapshot_splits,
1344 }),
1345 sink_schema_change: Default::default(),
1346 subscriptions_to_drop: vec![],
1347 iceberg_pk_index_compaction: None,
1348 });
1349 tracing::debug!("update mutation: {mutation:?}");
1350 Ok(Some(mutation))
1351 }
1352 }
1353 }
1354
1355 pub(super) fn create_subscription_to_mutation(
1357 upstream_mv_table_id: TableId,
1358 subscription_id: SubscriptionId,
1359 ) -> Mutation {
1360 {
1361 Mutation::Add(AddMutation {
1362 actor_dispatchers: Default::default(),
1363 added_actors: vec![],
1364 actor_splits: Default::default(),
1365 pause: false,
1366 subscriptions_to_add: vec![SubscriptionUpstreamInfo {
1367 upstream_mv_table_id,
1368 subscriber_id: subscription_id.as_subscriber_id(),
1369 }],
1370 backfill_nodes_to_pause: vec![],
1371 actor_cdc_table_snapshot_splits: None,
1372 new_upstream_sinks: Default::default(),
1373 dropped_actors: Default::default(),
1374 sink_log_store_flush: Default::default(),
1375 })
1376 }
1377 }
1378
1379 pub(super) fn drop_subscription_to_mutation(
1381 upstream_mv_table_id: TableId,
1382 subscription_id: SubscriptionId,
1383 ) -> Mutation {
1384 {
1385 Mutation::DropSubscriptions(DropSubscriptionsMutation {
1386 info: vec![SubscriptionUpstreamInfo {
1387 subscriber_id: subscription_id.as_subscriber_id(),
1388 upstream_mv_table_id,
1389 }],
1390 })
1391 }
1392 }
1393
1394 pub(super) fn connector_props_change_to_mutation(config: &ConnectorPropsChange) -> Mutation {
1396 {
1397 {
1398 let mut connector_props_infos = HashMap::default();
1399 for (k, v) in config {
1400 connector_props_infos.insert(
1401 k.as_raw_id(),
1402 ConnectorPropsInfo {
1403 connector_props_info: v.clone(),
1404 },
1405 );
1406 }
1407 Mutation::ConnectorPropsChange(ConnectorPropsChangeMutation {
1408 connector_props_infos,
1409 })
1410 }
1411 }
1412 }
1413
1414 pub(super) fn refresh_to_mutation(
1416 table_id: TableId,
1417 associated_source_id: SourceId,
1418 ) -> Mutation {
1419 Mutation::RefreshStart(risingwave_pb::stream_plan::RefreshStartMutation {
1420 table_id,
1421 associated_source_id,
1422 })
1423 }
1424
1425 pub(super) fn list_finish_to_mutation(associated_source_id: SourceId) -> Mutation {
1427 Mutation::ListFinish(ListFinishMutation {
1428 associated_source_id,
1429 })
1430 }
1431
1432 pub(super) fn load_finish_to_mutation(associated_source_id: SourceId) -> Mutation {
1434 Mutation::LoadFinish(LoadFinishMutation {
1435 associated_source_id,
1436 })
1437 }
1438
1439 pub(super) fn reset_source_to_mutation(source_id: SourceId) -> Mutation {
1441 Mutation::ResetSource(risingwave_pb::stream_plan::ResetSourceMutation {
1442 source_id: source_id.as_raw_id(),
1443 })
1444 }
1445
1446 pub(super) fn resume_backfill_to_mutation(
1448 target: &ResumeBackfillTarget,
1449 database_info: &InflightDatabaseInfo,
1450 ) -> MetaResult<Option<Mutation>> {
1451 {
1452 {
1453 let fragment_ids: HashSet<_> = match target {
1454 ResumeBackfillTarget::Job(job_id) => {
1455 database_info.backfill_fragment_ids_for_job(*job_id)?
1456 }
1457 ResumeBackfillTarget::Fragment(fragment_id) => {
1458 if !database_info.is_backfill_fragment(*fragment_id)? {
1459 return Err(MetaError::invalid_parameter(format!(
1460 "fragment {} is not a backfill node",
1461 fragment_id
1462 )));
1463 }
1464 HashSet::from([*fragment_id])
1465 }
1466 };
1467 if fragment_ids.is_empty() {
1468 warn!(
1469 ?target,
1470 "resume backfill command ignored because no backfill fragments found"
1471 );
1472 Ok(None)
1473 } else {
1474 Ok(Some(Mutation::StartFragmentBackfill(
1475 StartFragmentBackfillMutation {
1476 fragment_ids: fragment_ids.into_iter().collect(),
1477 },
1478 )))
1479 }
1480 }
1481 }
1482 }
1483
1484 pub(super) fn inject_source_offsets_to_mutation(
1486 source_id: SourceId,
1487 split_offsets: &HashMap<String, String>,
1488 ) -> Mutation {
1489 Mutation::InjectSourceOffsets(risingwave_pb::stream_plan::InjectSourceOffsetsMutation {
1490 source_id: source_id.as_raw_id(),
1491 split_offsets: split_offsets.clone(),
1492 })
1493 }
1494
1495 pub(super) fn create_streaming_job_actors_to_create(
1497 info: &CreateStreamingJobCommandInfo,
1498 edges: &mut FragmentEdgeBuildResult,
1499 stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
1500 actor_location: &HashMap<ActorId, WorkerId>,
1501 ) -> StreamJobActorsToCreate {
1502 {
1503 {
1504 edges.collect_actors_to_create(info.stream_job_fragments.fragments.values().map(
1505 |fragment| {
1506 let actors = stream_actors
1507 .get(&fragment.fragment_id)
1508 .into_iter()
1509 .flatten()
1510 .map(|actor| (actor, actor_location[&actor.actor_id]));
1511 (
1512 fragment.fragment_id,
1513 &fragment.nodes,
1514 actors,
1515 [], )
1517 },
1518 ))
1519 }
1520 }
1521 }
1522
1523 pub(super) fn reschedule_actors_to_create(
1525 reschedules: &HashMap<FragmentId, Reschedule>,
1526 fragment_actors: &HashMap<FragmentId, HashSet<ActorId>>,
1527 database_info: &InflightDatabaseInfo,
1528 control_stream_manager: &ControlStreamManager,
1529 ) -> StreamJobActorsToCreate {
1530 {
1531 {
1532 let mut actor_upstreams = Self::collect_database_partial_graph_actor_upstreams(
1533 reschedules.iter().map(|(fragment_id, reschedule)| {
1534 (
1535 *fragment_id,
1536 reschedule.newly_created_actors.values().map(
1537 |((actor, dispatchers), _)| {
1538 (actor.actor_id, dispatchers.as_slice())
1539 },
1540 ),
1541 )
1542 }),
1543 Some((reschedules, fragment_actors)),
1544 database_info,
1545 control_stream_manager,
1546 );
1547 let mut map: HashMap<WorkerId, HashMap<_, (_, Vec<_>, _)>> = HashMap::new();
1548 for (fragment_id, (actor, dispatchers), worker_id) in
1549 reschedules.iter().flat_map(|(fragment_id, reschedule)| {
1550 reschedule
1551 .newly_created_actors
1552 .values()
1553 .map(|(actors, status)| (*fragment_id, actors, status))
1554 })
1555 {
1556 let upstreams = actor_upstreams.remove(&actor.actor_id).unwrap_or_default();
1557 map.entry(*worker_id)
1558 .or_default()
1559 .entry(fragment_id)
1560 .or_insert_with(|| {
1561 let node = database_info.fragment(fragment_id).nodes.clone();
1562 let subscribers =
1563 database_info.fragment_subscribers(fragment_id).collect();
1564 (node, vec![], subscribers)
1565 })
1566 .1
1567 .push((actor.clone(), upstreams, dispatchers.clone()));
1568 }
1569 map
1570 }
1571 }
1572 }
1573
1574 pub(super) fn replace_stream_job_actors_to_create(
1576 replace_table: &ReplaceStreamJobPlan,
1577 edges: &mut FragmentEdgeBuildResult,
1578 database_info: &InflightDatabaseInfo,
1579 stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
1580 actor_location: &HashMap<ActorId, WorkerId>,
1581 ) -> StreamJobActorsToCreate {
1582 {
1583 {
1584 let mut actors = edges.collect_actors_to_create(
1585 replace_table
1586 .new_fragments
1587 .fragments
1588 .values()
1589 .map(|fragment| {
1590 let actors = stream_actors
1591 .get(&fragment.fragment_id)
1592 .into_iter()
1593 .flatten()
1594 .map(|actor| (actor, actor_location[&actor.actor_id]));
1595 (
1596 fragment.fragment_id,
1597 &fragment.nodes,
1598 actors,
1599 database_info
1600 .job_subscribers(replace_table.old_fragments.stream_job_id),
1601 )
1602 }),
1603 );
1604
1605 if let Some(sinks) = &replace_table.auto_refresh_schema_sinks {
1607 let sink_actors = edges.collect_actors_to_create(sinks.iter().map(|sink| {
1608 (
1609 sink.new_fragment.fragment_id,
1610 &sink.new_fragment.nodes,
1611 stream_actors
1612 .get(&sink.new_fragment.fragment_id)
1613 .into_iter()
1614 .flatten()
1615 .map(|actor| (actor, actor_location[&actor.actor_id])),
1616 database_info.job_subscribers(sink.original_sink.id.as_job_id()),
1617 )
1618 }));
1619 for (worker_id, fragment_actors) in sink_actors {
1620 actors.entry(worker_id).or_default().extend(fragment_actors);
1621 }
1622 }
1623 actors
1624 }
1625 }
1626 }
1627
1628 fn generate_update_mutation_for_replace_table(
1629 dropped_actors: impl IntoIterator<Item = ActorId>,
1630 merge_updates: HashMap<FragmentId, Vec<MergeUpdate>>,
1631 dispatchers: FragmentActorDispatchers,
1632 split_assignment: &SplitAssignment,
1633 cdc_table_snapshot_split_assignment: Option<PbCdcTableSnapshotSplitsWithGeneration>,
1634 auto_refresh_schema_sinks: Option<&Vec<AutoRefreshSchemaSinkContext>>,
1635 ) -> Option<Mutation> {
1636 let dropped_actors = dropped_actors.into_iter().collect();
1637
1638 let actor_new_dispatchers = dispatchers
1639 .into_values()
1640 .flatten()
1641 .map(|(actor_id, dispatchers)| (actor_id, Dispatchers { dispatchers }))
1642 .collect();
1643
1644 let actor_splits = split_assignment
1645 .values()
1646 .flat_map(build_actor_connector_splits)
1647 .collect();
1648 Some(Mutation::Update(UpdateMutation {
1649 actor_new_dispatchers,
1650 merge_update: merge_updates.into_values().flatten().collect(),
1651 dropped_actors,
1652 actor_splits,
1653 actor_cdc_table_snapshot_splits: cdc_table_snapshot_split_assignment,
1654 sink_schema_change: auto_refresh_schema_sinks
1655 .as_ref()
1656 .into_iter()
1657 .flat_map(|sinks| {
1658 sinks.iter().map(|sink| {
1659 let op = if !sink.removed_column_names.is_empty() {
1660 PbSinkSchemaChangeOp::DropColumns(PbSinkDropColumnsOp {
1661 column_names: sink.removed_column_names.clone(),
1662 })
1663 } else {
1664 PbSinkSchemaChangeOp::AddColumns(PbSinkAddColumnsOp {
1665 fields: sink
1666 .newly_add_fields
1667 .iter()
1668 .map(|field| field.to_prost())
1669 .collect(),
1670 })
1671 };
1672 (
1673 sink.original_sink.id.as_raw_id(),
1674 PbSinkSchemaChange {
1675 original_schema: sink_original_schema_fields(
1676 &sink.original_sink.columns,
1677 ),
1678 op: Some(op),
1679 },
1680 )
1681 })
1682 })
1683 .collect(),
1684 ..Default::default()
1685 }))
1686 }
1687}
1688
1689impl Command {
1690 #[expect(clippy::type_complexity)]
1691 pub(super) fn collect_database_partial_graph_actor_upstreams(
1692 actor_dispatchers: impl Iterator<
1693 Item = (FragmentId, impl Iterator<Item = (ActorId, &[Dispatcher])>),
1694 >,
1695 reschedule_dispatcher_update: Option<(
1696 &HashMap<FragmentId, Reschedule>,
1697 &HashMap<FragmentId, HashSet<ActorId>>,
1698 )>,
1699 database_info: &InflightDatabaseInfo,
1700 control_stream_manager: &ControlStreamManager,
1701 ) -> HashMap<ActorId, ActorUpstreams> {
1702 let mut actor_upstreams: HashMap<ActorId, ActorUpstreams> = HashMap::new();
1703 for (upstream_fragment_id, upstream_actors) in actor_dispatchers {
1704 let upstream_fragment = database_info.fragment(upstream_fragment_id);
1705 for (upstream_actor_id, dispatchers) in upstream_actors {
1706 let upstream_actor_location =
1707 upstream_fragment.actors[&upstream_actor_id].worker_id;
1708 let upstream_actor_host = control_stream_manager.host_addr(upstream_actor_location);
1709 for downstream_actor_id in dispatchers
1710 .iter()
1711 .flat_map(|dispatcher| dispatcher.downstream_actor_id.iter())
1712 {
1713 actor_upstreams
1714 .entry(*downstream_actor_id)
1715 .or_default()
1716 .entry(upstream_fragment_id)
1717 .or_default()
1718 .insert(
1719 upstream_actor_id,
1720 PbActorInfo {
1721 actor_id: upstream_actor_id,
1722 host: Some(upstream_actor_host.clone()),
1723 partial_graph_id: to_partial_graph_id(
1724 database_info.database_id,
1725 None,
1726 ),
1727 },
1728 );
1729 }
1730 }
1731 }
1732 if let Some((reschedules, fragment_actors)) = reschedule_dispatcher_update {
1733 for reschedule in reschedules.values() {
1734 for (upstream_fragment_id, _) in &reschedule.upstream_fragment_dispatcher_ids {
1735 let upstream_fragment = database_info.fragment(*upstream_fragment_id);
1736 let upstream_reschedule = reschedules.get(upstream_fragment_id);
1737 for upstream_actor_id in fragment_actors
1738 .get(upstream_fragment_id)
1739 .expect("should exist")
1740 {
1741 let upstream_actor_location =
1742 upstream_fragment.actors[upstream_actor_id].worker_id;
1743 let upstream_actor_host =
1744 control_stream_manager.host_addr(upstream_actor_location);
1745 if let Some(upstream_reschedule) = upstream_reschedule
1746 && upstream_reschedule
1747 .removed_actors
1748 .contains(upstream_actor_id)
1749 {
1750 continue;
1751 }
1752 for (_, downstream_actor_id) in
1753 reschedule
1754 .added_actors
1755 .iter()
1756 .flat_map(|(worker_id, actors)| {
1757 actors.iter().map(|actor| (*worker_id, *actor))
1758 })
1759 {
1760 actor_upstreams
1761 .entry(downstream_actor_id)
1762 .or_default()
1763 .entry(*upstream_fragment_id)
1764 .or_default()
1765 .insert(
1766 *upstream_actor_id,
1767 PbActorInfo {
1768 actor_id: *upstream_actor_id,
1769 host: Some(upstream_actor_host.clone()),
1770 partial_graph_id: to_partial_graph_id(
1771 database_info.database_id,
1772 None,
1773 ),
1774 },
1775 );
1776 }
1777 }
1778 }
1779 }
1780 }
1781 actor_upstreams
1782 }
1783}
1784
1785#[cfg(test)]
1786mod tests {
1787 use risingwave_pb::data::PbDataType;
1788 use risingwave_pb::data::data_type::PbTypeName;
1789 use risingwave_pb::plan_common::{ColumnCatalog as PbColumnCatalog, ColumnDesc};
1790
1791 use super::sink_original_schema_fields;
1792
1793 fn column(name: &str, type_name: PbTypeName, is_hidden: bool) -> PbColumnCatalog {
1794 PbColumnCatalog {
1795 column_desc: Some(ColumnDesc {
1796 column_type: Some(PbDataType {
1797 type_name: type_name as i32,
1798 ..Default::default()
1799 }),
1800 name: name.to_owned(),
1801 ..Default::default()
1802 }),
1803 is_hidden,
1804 }
1805 }
1806
1807 #[test]
1808 fn test_sink_original_schema_fields_skips_hidden_columns() {
1809 let columns = vec![
1810 column("k", PbTypeName::Int32, false),
1811 column("v", PbTypeName::Varchar, false),
1812 column("_row_id", PbTypeName::Serial, true),
1813 ];
1814
1815 let fields = sink_original_schema_fields(&columns);
1816 let field_names = fields
1817 .iter()
1818 .map(|field| field.name.as_str())
1819 .collect::<Vec<_>>();
1820
1821 assert_eq!(field_names, ["k", "v"]);
1822 }
1823}