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