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