1use std::collections::hash_map::Entry;
16use std::collections::{HashMap, HashSet};
17use std::mem::replace;
18use std::sync::Arc;
19
20use itertools::Itertools;
21use parking_lot::RawRwLock;
22use parking_lot::lock_api::RwLockReadGuard;
23use risingwave_common::bitmap::Bitmap;
24use risingwave_common::catalog::{DatabaseId, FragmentTypeFlag, FragmentTypeMask, TableId};
25use risingwave_common::id::JobId;
26use risingwave_common::util::epoch::EpochPair;
27use risingwave_common::util::stream_graph_visitor::visit_stream_node_mut;
28use risingwave_connector::source::{SplitImpl, SplitMetaData};
29use risingwave_meta_model::WorkerId;
30use risingwave_meta_model::fragment::DistributionType;
31use risingwave_pb::ddl_service::PbBackfillType;
32use risingwave_pb::hummock::HummockVersionStats;
33use risingwave_pb::id::SubscriberId;
34use risingwave_pb::meta::PbFragmentWorkerSlotMapping;
35use risingwave_pb::meta::subscribe_response::Operation;
36use risingwave_pb::source::PbCdcTableSnapshotSplits;
37use risingwave_pb::stream_plan::PbUpstreamSinkInfo;
38use risingwave_pb::stream_plan::barrier_mutation::Mutation;
39use risingwave_pb::stream_plan::stream_node::NodeBody;
40use risingwave_pb::stream_service::BarrierCompleteResponse;
41use tracing::{info, warn};
42
43use crate::barrier::cdc_progress::{CdcProgress, CdcTableBackfillTracker};
44use crate::barrier::command::{
45 CreateStreamingJobCommandInfo, PostCollectCommand, ReplaceStreamJobPlan, ThrottleConfigMap,
46 extract_throttle_config,
47};
48use crate::barrier::edge_builder::{FragmentEdgeBuildResult, FragmentEdgeBuilder};
49use crate::barrier::progress::{CreateMviewProgressTracker, StagingCommitInfo};
50use crate::barrier::rpc::{ControlStreamManager, to_partial_graph_id};
51use crate::barrier::{
52 BackfillProgress, BarrierKind, CreateStreamingJobType, FragmentBackfillProgress, TracedEpoch,
53};
54use crate::controller::fragment::{InflightActorInfo, InflightFragmentInfo};
55use crate::controller::utils::rebuild_fragment_mapping;
56use crate::manager::NotificationManagerRef;
57use crate::model::{
58 ActorId, ActorNewNoShuffle, BackfillUpstreamType, FragmentId, StreamActor, StreamJobFragments,
59};
60use crate::stream::UpstreamSinkInfo;
61use crate::{MetaError, MetaResult};
62
63#[derive(Debug, Clone)]
64pub struct SharedActorInfo {
65 pub worker_id: WorkerId,
66 pub vnode_bitmap: Option<Bitmap>,
67 pub splits: Vec<SplitImpl>,
68}
69
70impl From<&InflightActorInfo> for SharedActorInfo {
71 fn from(value: &InflightActorInfo) -> Self {
72 Self {
73 worker_id: value.worker_id,
74 vnode_bitmap: value.vnode_bitmap.clone(),
75 splits: value.splits.clone(),
76 }
77 }
78}
79
80#[derive(Debug, Clone)]
81pub struct SharedFragmentInfo {
82 pub fragment_id: FragmentId,
83 pub job_id: JobId,
84 pub distribution_type: DistributionType,
85 pub actors: HashMap<ActorId, SharedActorInfo>,
86 pub vnode_count: usize,
87 pub fragment_type_mask: FragmentTypeMask,
88 pub state_table_ids: HashSet<TableId>,
89}
90
91impl From<(&InflightFragmentInfo, JobId)> for SharedFragmentInfo {
92 fn from(pair: (&InflightFragmentInfo, JobId)) -> Self {
93 let (info, job_id) = pair;
94
95 let InflightFragmentInfo {
96 fragment_id,
97 distribution_type,
98 fragment_type_mask,
99 actors,
100 vnode_count,
101 state_table_ids,
102 ..
103 } = info;
104
105 Self {
106 fragment_id: *fragment_id,
107 job_id,
108 distribution_type: *distribution_type,
109 fragment_type_mask: *fragment_type_mask,
110 actors: actors
111 .iter()
112 .map(|(actor_id, actor)| (*actor_id, actor.into()))
113 .collect(),
114 vnode_count: *vnode_count,
115 state_table_ids: state_table_ids.clone(),
116 }
117 }
118}
119
120#[derive(Default, Debug)]
121pub struct SharedActorInfosInner {
122 info: HashMap<DatabaseId, HashMap<FragmentId, SharedFragmentInfo>>,
123}
124
125impl SharedActorInfosInner {
126 pub fn get_fragment(&self, fragment_id: FragmentId) -> Option<&SharedFragmentInfo> {
127 self.info
128 .values()
129 .find_map(|database| database.get(&fragment_id))
130 }
131
132 pub fn iter_over_fragments(&self) -> impl Iterator<Item = (&FragmentId, &SharedFragmentInfo)> {
133 self.info.values().flatten()
134 }
135}
136
137#[derive(Clone, educe::Educe)]
138#[educe(Debug)]
139pub struct SharedActorInfos {
140 inner: Arc<parking_lot::RwLock<SharedActorInfosInner>>,
141 #[educe(Debug(ignore))]
142 notification_manager: NotificationManagerRef,
143}
144
145impl SharedActorInfos {
146 pub fn read_guard(&self) -> RwLockReadGuard<'_, RawRwLock, SharedActorInfosInner> {
147 self.inner.read()
148 }
149
150 pub fn list_assignments(&self) -> HashMap<ActorId, Vec<SplitImpl>> {
151 let core = self.inner.read();
152 core.iter_over_fragments()
153 .flat_map(|(_, fragment)| {
154 fragment
155 .actors
156 .iter()
157 .map(|(actor_id, info)| (*actor_id, info.splits.clone()))
158 })
159 .collect()
160 }
161
162 pub fn migrate_splits_for_source_actors(
168 &self,
169 fragment_id: FragmentId,
170 prev_actor_ids: &[ActorId],
171 curr_actor_ids: &[ActorId],
172 ) -> MetaResult<HashMap<ActorId, Vec<SplitImpl>>> {
173 let guard = self.read_guard();
174
175 let prev_splits = prev_actor_ids
176 .iter()
177 .flat_map(|actor_id| {
178 guard
180 .get_fragment(fragment_id)
181 .and_then(|info| info.actors.get(actor_id))
182 .map(|actor| actor.splits.clone())
183 .unwrap_or_default()
184 })
185 .map(|split| (split.id(), split))
186 .collect();
187
188 let empty_actor_splits = curr_actor_ids
189 .iter()
190 .map(|actor_id| (*actor_id, vec![]))
191 .collect();
192
193 let diff = crate::stream::source_manager::reassign_splits(
194 fragment_id,
195 empty_actor_splits,
196 &prev_splits,
197 std::default::Default::default(),
199 )
200 .unwrap_or_default();
201
202 Ok(diff)
203 }
204}
205
206impl SharedActorInfos {
207 pub(crate) fn new(notification_manager: NotificationManagerRef) -> Self {
208 Self {
209 inner: Arc::new(Default::default()),
210 notification_manager,
211 }
212 }
213
214 pub(super) fn remove_database(&self, database_id: DatabaseId) {
215 if let Some(database) = self.inner.write().info.remove(&database_id) {
216 let mapping = database
217 .into_values()
218 .map(|fragment| rebuild_fragment_mapping(&fragment))
219 .collect_vec();
220 if !mapping.is_empty() {
221 self.notification_manager
222 .notify_streaming_fragment_mapping(Operation::Delete, mapping);
223 }
224 }
225 }
226
227 pub(super) fn retain_databases(&self, database_ids: impl IntoIterator<Item = DatabaseId>) {
228 let database_ids: HashSet<_> = database_ids.into_iter().collect();
229
230 let mut mapping = Vec::new();
231 for fragment in self
232 .inner
233 .write()
234 .info
235 .extract_if(|database_id, _| !database_ids.contains(database_id))
236 .flat_map(|(_, fragments)| fragments.into_values())
237 {
238 mapping.push(rebuild_fragment_mapping(&fragment));
239 }
240 if !mapping.is_empty() {
241 self.notification_manager
242 .notify_streaming_fragment_mapping(Operation::Delete, mapping);
243 }
244 }
245
246 pub(super) fn recover_database(
247 &self,
248 database_id: DatabaseId,
249 fragments: impl Iterator<Item = (&InflightFragmentInfo, JobId)>,
250 ) {
251 let mut remaining_fragments: HashMap<_, _> = fragments
252 .map(|info @ (fragment, _)| (fragment.fragment_id, info))
253 .collect();
254 let mut writer = self.start_writer(database_id);
256 let database = writer.write_guard.info.entry(database_id).or_default();
257 for (_, fragment) in database.extract_if(|fragment_id, fragment_infos| {
258 if let Some(info) = remaining_fragments.remove(fragment_id) {
259 let info = info.into();
260 writer
261 .updated_fragment_mapping
262 .get_or_insert_default()
263 .push(rebuild_fragment_mapping(&info));
264 *fragment_infos = info;
265 false
266 } else {
267 true
268 }
269 }) {
270 writer
271 .deleted_fragment_mapping
272 .get_or_insert_default()
273 .push(rebuild_fragment_mapping(&fragment));
274 }
275 for (fragment_id, info) in remaining_fragments {
276 let info = info.into();
277 writer
278 .added_fragment_mapping
279 .get_or_insert_default()
280 .push(rebuild_fragment_mapping(&info));
281 database.insert(fragment_id, info);
282 }
283 writer.finish();
284 }
285
286 pub(super) fn upsert(
287 &self,
288 database_id: DatabaseId,
289 infos: impl IntoIterator<Item = (&InflightFragmentInfo, JobId)>,
290 ) {
291 let mut writer = self.start_writer(database_id);
292 writer.upsert(infos);
293 writer.finish();
294 }
295
296 pub(super) fn start_writer(&self, database_id: DatabaseId) -> SharedActorInfoWriter<'_> {
297 SharedActorInfoWriter {
298 database_id,
299 write_guard: self.inner.write(),
300 notification_manager: &self.notification_manager,
301 added_fragment_mapping: None,
302 updated_fragment_mapping: None,
303 deleted_fragment_mapping: None,
304 }
305 }
306}
307
308pub(super) struct SharedActorInfoWriter<'a> {
309 database_id: DatabaseId,
310 write_guard: parking_lot::RwLockWriteGuard<'a, SharedActorInfosInner>,
311 notification_manager: &'a NotificationManagerRef,
312 added_fragment_mapping: Option<Vec<PbFragmentWorkerSlotMapping>>,
313 updated_fragment_mapping: Option<Vec<PbFragmentWorkerSlotMapping>>,
314 deleted_fragment_mapping: Option<Vec<PbFragmentWorkerSlotMapping>>,
315}
316
317impl SharedActorInfoWriter<'_> {
318 pub(super) fn upsert(
319 &mut self,
320 infos: impl IntoIterator<Item = (&InflightFragmentInfo, JobId)>,
321 ) {
322 let database = self.write_guard.info.entry(self.database_id).or_default();
323 for info @ (fragment, _) in infos {
324 match database.entry(fragment.fragment_id) {
325 Entry::Occupied(mut entry) => {
326 let info = info.into();
327 self.updated_fragment_mapping
328 .get_or_insert_default()
329 .push(rebuild_fragment_mapping(&info));
330 entry.insert(info);
331 }
332 Entry::Vacant(entry) => {
333 let info = info.into();
334 self.added_fragment_mapping
335 .get_or_insert_default()
336 .push(rebuild_fragment_mapping(&info));
337 entry.insert(info);
338 }
339 }
340 }
341 }
342
343 pub(super) fn remove(&mut self, info: &InflightFragmentInfo) {
344 if let Some(database) = self.write_guard.info.get_mut(&self.database_id)
345 && let Some(fragment) = database.remove(&info.fragment_id)
346 {
347 self.deleted_fragment_mapping
348 .get_or_insert_default()
349 .push(rebuild_fragment_mapping(&fragment));
350 }
351 }
352
353 pub(super) fn finish(self) {
354 if let Some(mapping) = self.added_fragment_mapping {
355 self.notification_manager
356 .notify_streaming_fragment_mapping(Operation::Add, mapping);
357 }
358 if let Some(mapping) = self.updated_fragment_mapping {
359 self.notification_manager
360 .notify_streaming_fragment_mapping(Operation::Update, mapping);
361 }
362 if let Some(mapping) = self.deleted_fragment_mapping {
363 self.notification_manager
364 .notify_streaming_fragment_mapping(Operation::Delete, mapping);
365 }
366 }
367}
368
369#[derive(Debug, Clone)]
370pub(super) struct BarrierInfo {
371 pub prev_epoch: TracedEpoch,
372 pub curr_epoch: TracedEpoch,
373 pub kind: BarrierKind,
374}
375
376impl BarrierInfo {
377 pub(super) fn prev_epoch(&self) -> u64 {
378 self.prev_epoch.value().0
379 }
380
381 pub(super) fn curr_epoch(&self) -> u64 {
382 self.curr_epoch.value().0
383 }
384
385 pub(super) fn epoch(&self) -> EpochPair {
386 EpochPair {
387 curr: self.curr_epoch(),
388 prev: self.prev_epoch(),
389 }
390 }
391}
392
393#[derive(Clone, Debug)]
394pub enum SubscriberType {
395 Subscription(u64),
396 SnapshotBackfill,
397}
398
399#[derive(Debug)]
400pub(super) enum CreateStreamingJobStatus {
401 Init,
402 Creating { tracker: CreateMviewProgressTracker },
403 Created,
404}
405
406#[derive(Debug)]
407pub(super) struct InflightStreamingJobInfo {
408 pub job_id: JobId,
409 pub fragment_infos: HashMap<FragmentId, InflightFragmentInfo>,
410 pub subscribers: HashMap<SubscriberId, SubscriberType>,
411 pub status: CreateStreamingJobStatus,
412 pub cdc_table_backfill_tracker: Option<CdcTableBackfillTracker>,
413}
414
415impl InflightStreamingJobInfo {
416 pub fn fragment_infos(&self) -> impl Iterator<Item = &InflightFragmentInfo> + '_ {
417 self.fragment_infos.values()
418 }
419
420 pub fn tracking_progress_actor_ids(
421 fragment_infos: &HashMap<FragmentId, InflightFragmentInfo>,
422 ) -> Vec<(ActorId, BackfillUpstreamType)> {
423 StreamJobFragments::tracking_progress_actor_ids_impl(fragment_infos.values().map(
424 |fragment| {
425 (
426 fragment.fragment_type_mask,
427 &fragment.nodes,
428 fragment.actors.keys().copied(),
429 )
430 },
431 ))
432 }
433}
434
435impl<'a> IntoIterator for &'a InflightStreamingJobInfo {
436 type Item = &'a InflightFragmentInfo;
437
438 type IntoIter = impl Iterator<Item = &'a InflightFragmentInfo> + 'a;
439
440 fn into_iter(self) -> Self::IntoIter {
441 self.fragment_infos()
442 }
443}
444
445#[derive(Debug)]
446pub struct InflightDatabaseInfo {
447 pub(super) database_id: DatabaseId,
448 jobs: HashMap<JobId, InflightStreamingJobInfo>,
449 fragment_location: HashMap<FragmentId, JobId>,
450 pub(super) shared_actor_infos: SharedActorInfos,
451}
452
453impl InflightDatabaseInfo {
454 pub(super) fn job_ids(&self) -> impl Iterator<Item = JobId> + '_ {
455 self.jobs.keys().copied()
456 }
457
458 pub fn fragment_infos(&self) -> impl Iterator<Item = &InflightFragmentInfo> + '_ {
459 self.jobs.values().flat_map(|job| job.fragment_infos())
460 }
461
462 pub fn contains_job(&self, job_id: JobId) -> bool {
463 self.jobs.contains_key(&job_id)
464 }
465
466 pub fn job_fragment_infos(
468 &self,
469 job_id: JobId,
470 ) -> impl Iterator<Item = &InflightFragmentInfo> + '_ {
471 self.jobs
472 .get(&job_id)
473 .into_iter()
474 .flat_map(|job| job.fragment_infos())
475 }
476
477 pub(super) fn job_id_by_fragment(&self, fragment_id: FragmentId) -> Option<JobId> {
478 self.fragment_location.get(&fragment_id).copied()
479 }
480
481 pub fn fragment(&self, fragment_id: FragmentId) -> &InflightFragmentInfo {
482 let job_id = self.fragment_location[&fragment_id];
483 self.jobs
484 .get(&job_id)
485 .expect("should exist")
486 .fragment_infos
487 .get(&fragment_id)
488 .expect("should exist")
489 }
490
491 pub(super) fn backfill_fragment_ids_for_job(
492 &self,
493 job_id: JobId,
494 ) -> MetaResult<HashSet<FragmentId>> {
495 let job = self
496 .jobs
497 .get(&job_id)
498 .ok_or_else(|| MetaError::invalid_parameter(format!("job {} not found", job_id)))?;
499 Ok(job
500 .fragment_infos
501 .iter()
502 .filter_map(|(fragment_id, fragment)| {
503 fragment
504 .fragment_type_mask
505 .contains_any([
506 FragmentTypeFlag::StreamScan,
507 FragmentTypeFlag::SourceScan,
508 FragmentTypeFlag::LocalityProvider,
509 ])
510 .then_some(*fragment_id)
511 })
512 .collect())
513 }
514
515 pub(super) fn is_backfill_fragment(&self, fragment_id: FragmentId) -> MetaResult<bool> {
516 let job_id = self.fragment_location.get(&fragment_id).ok_or_else(|| {
517 MetaError::invalid_parameter(format!("fragment {} not found", fragment_id))
518 })?;
519 let fragment = self
520 .jobs
521 .get(job_id)
522 .expect("should exist")
523 .fragment_infos
524 .get(&fragment_id)
525 .expect("should exist");
526 Ok(fragment.fragment_type_mask.contains_any([
527 FragmentTypeFlag::StreamScan,
528 FragmentTypeFlag::SourceScan,
529 FragmentTypeFlag::LocalityProvider,
530 ]))
531 }
532
533 pub fn gen_backfill_progress(&self) -> impl Iterator<Item = (JobId, BackfillProgress)> + '_ {
534 self.jobs
535 .iter()
536 .filter_map(|(job_id, job)| match &job.status {
537 CreateStreamingJobStatus::Init => None,
538 CreateStreamingJobStatus::Creating { tracker } => {
539 let progress = tracker.gen_backfill_progress();
540 Some((
541 *job_id,
542 BackfillProgress {
543 progress,
544 backfill_type: PbBackfillType::NormalBackfill,
545 },
546 ))
547 }
548 CreateStreamingJobStatus::Created => None,
549 })
550 }
551
552 pub fn gen_cdc_progress(&self) -> impl Iterator<Item = (JobId, CdcProgress)> + '_ {
553 self.jobs.iter().filter_map(|(job_id, job)| {
554 job.cdc_table_backfill_tracker
555 .as_ref()
556 .map(|tracker| (*job_id, tracker.gen_cdc_progress()))
557 })
558 }
559
560 pub fn gen_fragment_backfill_progress(&self) -> Vec<FragmentBackfillProgress> {
561 let mut result = Vec::new();
562 for job in self.jobs.values() {
563 let CreateStreamingJobStatus::Creating { tracker } = &job.status else {
564 continue;
565 };
566 let fragment_progress = tracker.collect_fragment_progress(&job.fragment_infos, true);
567 result.extend(fragment_progress);
568 }
569 result
570 }
571
572 pub(super) fn may_assign_fragment_cdc_backfill_splits(
573 &mut self,
574 fragment_id: FragmentId,
575 ) -> MetaResult<Option<HashMap<ActorId, PbCdcTableSnapshotSplits>>> {
576 let job_id = self.fragment_location[&fragment_id];
577 let job = self.jobs.get_mut(&job_id).expect("should exist");
578 if let Some(tracker) = &mut job.cdc_table_backfill_tracker {
579 let cdc_scan_fragment_id = tracker.cdc_scan_fragment_id();
580 if cdc_scan_fragment_id != fragment_id {
581 return Ok(None);
582 }
583 let actors = job.fragment_infos[&cdc_scan_fragment_id]
584 .actors
585 .keys()
586 .copied()
587 .collect();
588 tracker.reassign_splits(actors).map(Some)
589 } else {
590 Ok(None)
591 }
592 }
593
594 pub(super) fn assign_cdc_backfill_splits(
595 &mut self,
596 job_id: JobId,
597 ) -> MetaResult<Option<HashMap<ActorId, PbCdcTableSnapshotSplits>>> {
598 let job = self.jobs.get_mut(&job_id).expect("should exist");
599 if let Some(tracker) = &mut job.cdc_table_backfill_tracker {
600 let cdc_scan_fragment_id = tracker.cdc_scan_fragment_id();
601 let actors = job.fragment_infos[&cdc_scan_fragment_id]
602 .actors
603 .keys()
604 .copied()
605 .collect();
606 tracker.reassign_splits(actors).map(Some)
607 } else {
608 Ok(None)
609 }
610 }
611
612 pub(super) fn apply_collected_command(
613 &mut self,
614 command: &PostCollectCommand,
615 resps: &HashMap<WorkerId, BarrierCompleteResponse>,
616 version_stats: &HummockVersionStats,
617 ) {
618 if let PostCollectCommand::CreateStreamingJob { info, job_type, .. } = command {
619 match job_type {
620 CreateStreamingJobType::Normal | CreateStreamingJobType::SinkIntoTable(_) => {
621 let job_id = info.streaming_job.id();
622 if let Some(job_info) = self.jobs.get_mut(&job_id) {
623 let CreateStreamingJobStatus::Init = replace(
624 &mut job_info.status,
625 CreateStreamingJobStatus::Creating {
626 tracker: CreateMviewProgressTracker::new(
627 info,
628 version_stats,
629 &job_info.fragment_infos,
630 ),
631 },
632 ) else {
633 unreachable!("should be init before collect the first barrier")
634 };
635 } else {
636 info!(%job_id, "newly create job get cancelled before first barrier is collected")
637 }
638 }
639 CreateStreamingJobType::Independent { .. } => {
640 }
642 }
643 }
644 if let PostCollectCommand::Reschedule { reschedules, .. } = command {
645 debug_assert!(
647 reschedules
648 .values()
649 .all(|reschedule| reschedule.vnode_bitmap_updates.is_empty()),
650 "Reschedule should not carry vnode bitmap updates when actors are rebuilt"
651 );
652
653 let related_job_ids = reschedules
655 .keys()
656 .filter_map(|fragment_id| self.fragment_location.get(fragment_id))
657 .cloned()
658 .collect::<HashSet<_>>();
659 for job_id in related_job_ids {
660 if let Some(job) = self.jobs.get_mut(&job_id)
661 && let CreateStreamingJobStatus::Creating { tracker, .. } = &mut job.status
662 {
663 tracker.refresh_after_reschedule(&job.fragment_infos, version_stats);
664 }
665 }
666 }
667 for progress in resps.values().flat_map(|resp| &resp.create_mview_progress) {
668 let Some(job_id) = self.fragment_location.get(&progress.fragment_id) else {
669 warn!(
670 "update the progress of an non-existent creating streaming job: {progress:?}, which could be cancelled"
671 );
672 continue;
673 };
674 let tracker = match &mut self.jobs.get_mut(job_id).expect("should exist").status {
675 CreateStreamingJobStatus::Init => {
676 continue;
677 }
678 CreateStreamingJobStatus::Creating { tracker, .. } => tracker,
679 CreateStreamingJobStatus::Created => {
680 if !progress.done {
681 warn!("update the progress of an created streaming job: {progress:?}");
682 }
683 continue;
684 }
685 };
686 tracker.apply_progress(progress, version_stats);
687 }
688 for progress in resps
689 .values()
690 .flat_map(|resp| &resp.cdc_table_backfill_progress)
691 {
692 let Some(job_id) = self.fragment_location.get(&progress.fragment_id) else {
693 warn!(
694 "update the cdc progress of an non-existent creating streaming job: {progress:?}, which could be cancelled"
695 );
696 continue;
697 };
698 let Some(tracker) = &mut self
699 .jobs
700 .get_mut(job_id)
701 .expect("should exist")
702 .cdc_table_backfill_tracker
703 else {
704 warn!("update the cdc progress of an created streaming job: {progress:?}");
705 continue;
706 };
707 tracker.update_split_progress(progress);
708 }
709 for cdc_offset_updated in resps
711 .values()
712 .flat_map(|resp| &resp.cdc_source_offset_updated)
713 {
714 let source_id = cdc_offset_updated.source_id;
715 let job_id = source_id.as_share_source_job_id();
716 if let Some(job) = self.jobs.get_mut(&job_id) {
717 if let CreateStreamingJobStatus::Creating { tracker, .. } = &mut job.status {
718 tracker.mark_cdc_source_finished();
719 }
720 } else {
721 warn!(
722 "update cdc source offset for non-existent creating streaming job: source_id={}, job_id={}",
723 cdc_offset_updated.source_id, job_id
724 );
725 }
726 }
727 }
728
729 fn iter_creating_job_tracker(&self) -> impl Iterator<Item = &CreateMviewProgressTracker> {
730 self.jobs.values().filter_map(|job| match &job.status {
731 CreateStreamingJobStatus::Init => None,
732 CreateStreamingJobStatus::Creating { tracker, .. } => Some(tracker),
733 CreateStreamingJobStatus::Created => None,
734 })
735 }
736
737 fn iter_mut_creating_job_tracker(
738 &mut self,
739 ) -> impl Iterator<Item = &mut CreateMviewProgressTracker> {
740 self.jobs
741 .values_mut()
742 .filter_map(|job| match &mut job.status {
743 CreateStreamingJobStatus::Init => None,
744 CreateStreamingJobStatus::Creating { tracker, .. } => Some(tracker),
745 CreateStreamingJobStatus::Created => None,
746 })
747 }
748
749 pub(super) fn has_pending_finished_jobs(&self) -> bool {
750 self.iter_creating_job_tracker()
751 .any(|tracker| tracker.is_finished())
752 }
753
754 pub(super) fn take_pending_backfill_nodes(&mut self) -> Vec<FragmentId> {
755 self.iter_mut_creating_job_tracker()
756 .flat_map(|tracker| tracker.take_pending_backfill_nodes())
757 .collect()
758 }
759
760 pub(super) fn take_staging_commit_info(&mut self) -> StagingCommitInfo {
761 let mut finished_jobs = vec![];
762 let mut table_ids_to_truncate = vec![];
763 let mut finished_cdc_table_backfill = vec![];
764 for (job_id, job) in &mut self.jobs {
765 if let CreateStreamingJobStatus::Creating { tracker, .. } = &mut job.status {
766 let (is_finished, truncate_table_ids) = tracker.collect_staging_commit_info();
767 table_ids_to_truncate.extend(truncate_table_ids);
768 if is_finished {
769 let CreateStreamingJobStatus::Creating { tracker, .. } =
770 replace(&mut job.status, CreateStreamingJobStatus::Created)
771 else {
772 unreachable!()
773 };
774 finished_jobs.push(tracker.into_tracking_job());
775 }
776 }
777 if let Some(tracker) = &mut job.cdc_table_backfill_tracker
778 && tracker.take_pre_completed()
779 {
780 finished_cdc_table_backfill.push(*job_id);
781 }
782 }
783 StagingCommitInfo {
784 finished_jobs,
785 table_ids_to_truncate,
786 finished_cdc_table_backfill,
787 }
788 }
789
790 pub fn fragment_subscribers(
791 &self,
792 fragment_id: FragmentId,
793 ) -> impl Iterator<Item = SubscriberId> + '_ {
794 let job_id = self.fragment_location[&fragment_id];
795 self.jobs[&job_id].subscribers.keys().copied()
796 }
797
798 pub fn job_subscribers(&self, job_id: JobId) -> impl Iterator<Item = SubscriberId> + '_ {
799 self.jobs[&job_id].subscribers.keys().copied()
800 }
801
802 pub fn subscribed_tables(&self) -> impl Iterator<Item = TableId> + '_ {
803 self.jobs.iter().filter_map(|(job_id, info)| {
804 info.subscribers
805 .values()
806 .any(|subscriber| matches!(subscriber, SubscriberType::Subscription(_)))
807 .then_some(job_id.as_mv_table_id())
808 })
809 }
810
811 pub fn register_subscriber(
812 &mut self,
813 job_id: JobId,
814 subscriber_id: SubscriberId,
815 subscriber: SubscriberType,
816 ) {
817 self.jobs
818 .get_mut(&job_id)
819 .expect("should exist")
820 .subscribers
821 .try_insert(subscriber_id, subscriber)
822 .expect("non duplicate");
823 }
824
825 pub fn unregister_subscriber(
826 &mut self,
827 job_id: JobId,
828 subscriber_id: SubscriberId,
829 ) -> Option<SubscriberType> {
830 self.jobs
831 .get_mut(&job_id)
832 .expect("should exist")
833 .subscribers
834 .remove(&subscriber_id)
835 }
836
837 pub fn update_subscription_retention(
838 &mut self,
839 job_id: JobId,
840 subscriber_id: SubscriberId,
841 retention_second: u64,
842 ) {
843 let job = self.jobs.get_mut(&job_id).expect("should exist");
844 match job.subscribers.get_mut(&subscriber_id) {
845 Some(SubscriberType::Subscription(current_retention)) => {
846 *current_retention = retention_second;
847 }
848 Some(SubscriberType::SnapshotBackfill) => {
849 warn!(
850 %job_id,
851 %subscriber_id,
852 "cannot update retention for snapshot backfill subscriber"
853 );
854 }
855 None => {
856 warn!(%job_id, %subscriber_id, "subscription subscriber not found");
857 }
858 }
859 }
860
861 fn fragment_mut(&mut self, fragment_id: FragmentId) -> (&mut InflightFragmentInfo, JobId) {
862 let job_id = self.fragment_location[&fragment_id];
863 let fragment = self
864 .jobs
865 .get_mut(&job_id)
866 .expect("should exist")
867 .fragment_infos
868 .get_mut(&fragment_id)
869 .expect("should exist");
870 (fragment, job_id)
871 }
872
873 fn empty_inner(database_id: DatabaseId, shared_actor_infos: SharedActorInfos) -> Self {
874 Self {
875 database_id,
876 jobs: Default::default(),
877 fragment_location: Default::default(),
878 shared_actor_infos,
879 }
880 }
881
882 pub fn empty(database_id: DatabaseId, shared_actor_infos: SharedActorInfos) -> Self {
883 shared_actor_infos.remove_database(database_id);
885 Self::empty_inner(database_id, shared_actor_infos)
886 }
887
888 pub fn recover(
889 database_id: DatabaseId,
890 jobs: impl Iterator<Item = InflightStreamingJobInfo>,
891 shared_actor_infos: SharedActorInfos,
892 ) -> Self {
893 let mut info = Self::empty_inner(database_id, shared_actor_infos);
894 for job in jobs {
895 info.add_existing(job);
896 }
897 info
898 }
899
900 pub fn is_empty(&self) -> bool {
901 self.jobs.is_empty()
902 }
903
904 pub fn add_existing(&mut self, job: InflightStreamingJobInfo) {
905 let InflightStreamingJobInfo {
906 job_id,
907 fragment_infos,
908 subscribers,
909 status,
910 cdc_table_backfill_tracker,
911 } = job;
912 self.jobs
913 .try_insert(
914 job_id,
915 InflightStreamingJobInfo {
916 job_id,
917 subscribers,
918 fragment_infos: Default::default(), status,
920 cdc_table_backfill_tracker,
921 },
922 )
923 .expect("non-duplicate");
924 self.pre_apply_new_fragments(
925 fragment_infos
926 .into_iter()
927 .map(|(fragment_id, info)| (fragment_id, job_id, info)),
928 );
929 }
930
931 pub(crate) fn pre_apply_new_job(
933 &mut self,
934 job_id: JobId,
935 cdc_table_backfill_tracker: Option<CdcTableBackfillTracker>,
936 ) {
937 {
938 self.jobs
939 .try_insert(
940 job_id,
941 InflightStreamingJobInfo {
942 job_id,
943 fragment_infos: Default::default(),
944 subscribers: Default::default(), status: CreateStreamingJobStatus::Init,
946 cdc_table_backfill_tracker,
947 },
948 )
949 .expect("non-duplicate");
950 }
951 }
952
953 pub(crate) fn pre_apply_new_fragments(
955 &mut self,
956 fragments: impl IntoIterator<Item = (FragmentId, JobId, InflightFragmentInfo)>,
957 ) {
958 {
959 let shared_infos = self.shared_actor_infos.clone();
960 let mut shared_actor_writer = shared_infos.start_writer(self.database_id);
961 for (fragment_id, job_id, info) in fragments {
962 {
963 {
964 let fragment_infos = self.jobs.get_mut(&job_id).expect("should exist");
965 shared_actor_writer.upsert([(&info, job_id)]);
966 fragment_infos
967 .fragment_infos
968 .try_insert(fragment_id, info)
969 .expect("non duplicate");
970 self.fragment_location
971 .try_insert(fragment_id, job_id)
972 .expect("non duplicate");
973 }
974 }
975 }
976 shared_actor_writer.finish();
977 }
978 }
979
980 pub(crate) fn pre_apply_reschedule(
983 &mut self,
984 fragment_id: FragmentId,
985 new_actors: HashMap<ActorId, InflightActorInfo>,
986 actor_update_vnode_bitmap: HashMap<ActorId, Bitmap>,
987 actor_splits: HashMap<ActorId, Vec<SplitImpl>>,
988 ) {
989 {
990 {
991 {
992 {
993 let (info, _) = self.fragment_mut(fragment_id);
994 let actors = &mut info.actors;
995 for (actor_id, new_vnodes) in actor_update_vnode_bitmap {
996 actors
997 .get_mut(&actor_id)
998 .expect("should exist")
999 .vnode_bitmap = Some(new_vnodes);
1000 }
1001 for (actor_id, actor) in new_actors {
1002 actors
1003 .try_insert(actor_id as _, actor)
1004 .expect("non-duplicate");
1005 }
1006 for (actor_id, splits) in actor_splits {
1007 actors.get_mut(&actor_id).expect("should exist").splits = splits;
1008 }
1009 }
1011 }
1012 }
1013 }
1014 }
1015
1016 pub(crate) fn pre_apply_replace_node_upstream(
1018 &mut self,
1019 fragment_id: FragmentId,
1020 replace_map: &HashMap<FragmentId, FragmentId>,
1021 ) {
1022 {
1023 {
1024 {
1025 {
1026 let mut remaining_fragment_ids: HashSet<_> =
1027 replace_map.keys().cloned().collect();
1028 let (info, _) = self.fragment_mut(fragment_id);
1029 visit_stream_node_mut(&mut info.nodes, |node| {
1030 if let NodeBody::Merge(m) = node
1031 && let Some(new_upstream_fragment_id) =
1032 replace_map.get(&m.upstream_fragment_id)
1033 {
1034 if !remaining_fragment_ids.remove(&m.upstream_fragment_id) {
1035 if cfg!(debug_assertions) {
1036 panic!(
1037 "duplicate upstream fragment: {:?} {:?}",
1038 m, replace_map
1039 );
1040 } else {
1041 warn!(?m, ?replace_map, "duplicate upstream fragment");
1042 }
1043 }
1044 m.upstream_fragment_id = *new_upstream_fragment_id;
1045 }
1046 });
1047 if cfg!(debug_assertions) {
1048 assert!(
1049 remaining_fragment_ids.is_empty(),
1050 "non-existing fragment to replace: {:?} {:?} {:?}",
1051 remaining_fragment_ids,
1052 info.nodes,
1053 replace_map
1054 );
1055 } else {
1056 warn!(?remaining_fragment_ids, node = ?info.nodes, ?replace_map, "non-existing fragment to replace");
1057 }
1058 }
1059 }
1060 }
1061 }
1062 }
1063
1064 pub(crate) fn pre_apply_add_node_upstream(
1066 &mut self,
1067 fragment_id: FragmentId,
1068 new_upstream_info: &PbUpstreamSinkInfo,
1069 ) {
1070 {
1071 {
1072 {
1073 {
1074 let (info, _) = self.fragment_mut(fragment_id);
1075 let mut injected = false;
1076 visit_stream_node_mut(&mut info.nodes, |node| {
1077 if let NodeBody::UpstreamSinkUnion(u) = node {
1078 if cfg!(debug_assertions) {
1079 let current_upstream_fragment_ids = u
1080 .init_upstreams
1081 .iter()
1082 .map(|upstream| upstream.upstream_fragment_id)
1083 .collect::<HashSet<_>>();
1084 if current_upstream_fragment_ids
1085 .contains(&new_upstream_info.upstream_fragment_id)
1086 {
1087 panic!(
1088 "duplicate upstream fragment: {:?} {:?}",
1089 u, new_upstream_info
1090 );
1091 }
1092 }
1093 u.init_upstreams.push(new_upstream_info.clone());
1094 injected = true;
1095 }
1096 });
1097 assert!(injected, "should inject upstream into UpstreamSinkUnion");
1098 }
1099 }
1100 }
1101 }
1102 }
1103
1104 pub(crate) fn pre_apply_drop_node_upstream(
1106 &mut self,
1107 fragment_id: FragmentId,
1108 drop_upstream_fragment_ids: &[FragmentId],
1109 ) {
1110 if !self.fragment_location.contains_key(&fragment_id) {
1111 warn!(
1112 target_fragment_id = %fragment_id,
1113 drop_upstream_fragment_ids = ?drop_upstream_fragment_ids,
1114 "skip dropping upstream sink fragments for non-existing target fragment"
1115 );
1116 return;
1117 }
1118 {
1119 {
1120 {
1121 {
1122 let (info, _) = self.fragment_mut(fragment_id);
1123 let mut removed = false;
1124 visit_stream_node_mut(&mut info.nodes, |node| {
1125 if let NodeBody::UpstreamSinkUnion(u) = node {
1126 if cfg!(debug_assertions) {
1127 let current_upstream_fragment_ids = u
1128 .init_upstreams
1129 .iter()
1130 .map(|upstream| upstream.upstream_fragment_id)
1131 .collect::<HashSet<FragmentId>>();
1132 for drop_fragment_id in drop_upstream_fragment_ids {
1133 if !current_upstream_fragment_ids.contains(drop_fragment_id)
1134 {
1135 panic!(
1136 "non-existing upstream fragment to drop: {:?} {:?} {:?}",
1137 u, drop_upstream_fragment_ids, drop_fragment_id
1138 );
1139 }
1140 }
1141 }
1142 u.init_upstreams.retain(|upstream| {
1143 !drop_upstream_fragment_ids
1144 .contains(&upstream.upstream_fragment_id)
1145 });
1146 removed = true;
1147 }
1148 });
1149 assert!(removed, "should remove upstream from UpstreamSinkUnion");
1150 }
1151 }
1152 }
1153 }
1154 }
1155
1156 pub(crate) fn pre_apply_throttle(
1158 &mut self,
1159 config: &mut ThrottleConfigMap,
1160 ) -> Option<Mutation> {
1161 extract_throttle_config(config, |fragment_id, stream_node| {
1162 if !self.fragment_location.contains_key(&fragment_id) {
1163 return false;
1164 }
1165 self.fragment_mut(fragment_id).0.nodes = stream_node.clone();
1166 true
1167 })
1168 }
1169
1170 pub(crate) fn pre_apply_split_assignments(
1172 &mut self,
1173 assignments: impl IntoIterator<Item = (FragmentId, HashMap<ActorId, Vec<SplitImpl>>)>,
1174 ) {
1175 {
1176 let shared_infos = self.shared_actor_infos.clone();
1177 let mut shared_actor_writer = shared_infos.start_writer(self.database_id);
1178 {
1179 {
1180 for (fragment_id, actor_splits) in assignments {
1181 let (info, job_id) = self.fragment_mut(fragment_id);
1182 let actors = &mut info.actors;
1183 for (actor_id, splits) in actor_splits {
1184 actors.get_mut(&actor_id).expect("should exist").splits = splits;
1185 }
1186 shared_actor_writer.upsert([(&*info, job_id)]);
1187 }
1188 }
1189 }
1190 shared_actor_writer.finish();
1191 }
1192 }
1193
1194 pub(super) fn build_edge(
1195 &self,
1196 info: Option<&CreateStreamingJobCommandInfo>,
1197 replace_job: Option<&ReplaceStreamJobPlan>,
1198 new_upstream_sink: Option<&UpstreamSinkInfo>,
1199 control_stream_manager: &ControlStreamManager,
1200 stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
1201 actor_location: &HashMap<ActorId, WorkerId>,
1202 ) -> MetaResult<(FragmentEdgeBuildResult, ActorNewNoShuffle)> {
1203 let existing_fragment_ids = info
1211 .into_iter()
1212 .flat_map(|info| info.upstream_fragment_downstreams.keys())
1213 .chain(replace_job.into_iter().flat_map(|replace_job| {
1214 replace_job
1215 .upstream_fragment_downstreams
1216 .keys()
1217 .filter(|fragment_id| {
1218 info.map(|info| {
1219 !info
1220 .stream_job_fragments
1221 .fragments
1222 .contains_key(*fragment_id)
1223 })
1224 .unwrap_or(true)
1225 })
1226 .chain(replace_job.replace_upstream.keys())
1227 }))
1228 .chain(
1229 new_upstream_sink
1230 .into_iter()
1231 .map(|ctx| &ctx.new_sink_downstream.downstream_fragment_id),
1232 )
1233 .cloned();
1234 let new_fragments = info
1236 .into_iter()
1237 .flat_map(|info| {
1238 let partial_graph_id = to_partial_graph_id(self.database_id, None);
1239 info.stream_job_fragments
1240 .fragments
1241 .values()
1242 .map(move |fragment| (partial_graph_id, fragment))
1243 })
1244 .chain(replace_job.into_iter().flat_map(|replace_job| {
1245 replace_job
1246 .new_fragments
1247 .fragments
1248 .values()
1249 .chain(
1250 replace_job
1251 .auto_refresh_schema_sinks
1252 .as_ref()
1253 .into_iter()
1254 .flat_map(move |sinks| sinks.iter().map(|sink| &sink.new_fragment)),
1255 )
1256 .map(|fragment| {
1257 (
1258 to_partial_graph_id(self.database_id, None),
1260 fragment,
1261 )
1262 })
1263 }));
1264
1265 let database_partial_graph_id = to_partial_graph_id(self.database_id, None);
1266 let mut builder = FragmentEdgeBuilder::new()
1267 .add_existing_fragments(
1268 existing_fragment_ids.map(|fragment_id| self.fragment(fragment_id)),
1269 database_partial_graph_id,
1270 control_stream_manager,
1271 )
1272 .add_new_logical_fragments(
1273 new_fragments,
1274 stream_actors,
1275 actor_location,
1276 control_stream_manager,
1277 )
1278 .finish_fragments();
1279 if let Some(info) = info {
1280 builder = builder
1281 .add_relations(&info.upstream_fragment_downstreams)?
1282 .add_relations(&info.stream_job_fragments.downstreams)?;
1283 }
1284 if let Some(replace_job) = replace_job {
1285 builder = builder
1286 .add_relations(&replace_job.upstream_fragment_downstreams)?
1287 .add_relations(&replace_job.new_fragments.downstreams)?;
1288 }
1289 if let Some(new_upstream_sink) = new_upstream_sink {
1290 let sink_fragment_id = new_upstream_sink.sink_fragment_id;
1291 let new_sink_downstream = &new_upstream_sink.new_sink_downstream;
1292 builder = builder.add_edge(sink_fragment_id, new_sink_downstream)?;
1293 }
1294 if let Some(replace_job) = replace_job {
1295 for (fragment_id, fragment_replacement) in &replace_job.replace_upstream {
1296 for (original_upstream_fragment_id, new_upstream_fragment_id) in
1297 fragment_replacement
1298 {
1299 builder = builder.replace_upstream(
1300 *fragment_id,
1301 *original_upstream_fragment_id,
1302 *new_upstream_fragment_id,
1303 );
1304 }
1305 }
1306 }
1307 Ok(builder.build())
1308 }
1309
1310 pub(crate) fn post_apply_reschedules(
1312 &mut self,
1313 reschedules: impl IntoIterator<Item = (FragmentId, HashSet<ActorId>)>,
1314 ) {
1315 let inner = self.shared_actor_infos.clone();
1316 let mut shared_actor_writer = inner.start_writer(self.database_id);
1317 {
1318 {
1319 {
1320 for (fragment_id, to_remove) in reschedules {
1321 let job_id = self.fragment_location[&fragment_id];
1322 let info = self
1323 .jobs
1324 .get_mut(&job_id)
1325 .expect("should exist")
1326 .fragment_infos
1327 .get_mut(&fragment_id)
1328 .expect("should exist");
1329 for actor_id in to_remove {
1330 assert!(info.actors.remove(&actor_id).is_some());
1331 }
1332 shared_actor_writer.upsert([(&*info, job_id)]);
1333 }
1334 }
1335 }
1336 }
1337 shared_actor_writer.finish();
1338 }
1339
1340 pub(crate) fn post_apply_remove_fragments(
1342 &mut self,
1343 fragment_ids: impl IntoIterator<Item = FragmentId>,
1344 ) {
1345 let inner = self.shared_actor_infos.clone();
1346 let mut shared_actor_writer = inner.start_writer(self.database_id);
1347 {
1348 {
1349 {
1350 for fragment_id in fragment_ids {
1351 let job_id = self
1352 .fragment_location
1353 .remove(&fragment_id)
1354 .expect("should exist");
1355 let job = self.jobs.get_mut(&job_id).expect("should exist");
1356 let fragment = job
1357 .fragment_infos
1358 .remove(&fragment_id)
1359 .expect("should exist");
1360 shared_actor_writer.remove(&fragment);
1361 if job.fragment_infos.is_empty() {
1362 self.jobs.remove(&job_id).expect("should exist");
1363 }
1364 }
1365 }
1366 }
1367 }
1368 shared_actor_writer.finish();
1369 }
1370
1371 pub(crate) fn post_apply_remove_job(
1372 &mut self,
1373 job_id: JobId,
1374 ) -> Option<InflightStreamingJobInfo> {
1375 let job = self.jobs.remove(&job_id)?;
1376 let inner = self.shared_actor_infos.clone();
1377 let mut shared_actor_writer = inner.start_writer(self.database_id);
1378 for (fragment_id, fragment) in &job.fragment_infos {
1379 self.fragment_location
1380 .remove(fragment_id)
1381 .expect("should exist");
1382 shared_actor_writer.remove(fragment);
1383 }
1384 shared_actor_writer.finish();
1385 Some(job)
1386 }
1387}
1388
1389impl InflightFragmentInfo {
1390 pub(crate) fn actor_ids_to_collect(
1392 infos: impl IntoIterator<Item = &Self>,
1393 ) -> HashMap<WorkerId, HashSet<ActorId>> {
1394 let mut ret: HashMap<_, HashSet<_>> = HashMap::new();
1395 for (actor_id, actor) in infos.into_iter().flat_map(|info| info.actors.iter()) {
1396 assert!(
1397 ret.entry(actor.worker_id)
1398 .or_default()
1399 .insert(*actor_id as _)
1400 )
1401 }
1402 ret
1403 }
1404
1405 pub fn existing_table_ids<'a>(
1406 infos: impl IntoIterator<Item = &'a Self> + 'a,
1407 ) -> impl Iterator<Item = TableId> + 'a {
1408 infos
1409 .into_iter()
1410 .flat_map(|info| info.state_table_ids.iter().cloned())
1411 }
1412
1413 pub fn workers<'a>(
1414 infos: impl IntoIterator<Item = &'a Self> + 'a,
1415 ) -> impl Iterator<Item = WorkerId> + 'a {
1416 infos
1417 .into_iter()
1418 .flat_map(|fragment| fragment.actors.values().map(|actor| actor.worker_id))
1419 }
1420
1421 pub fn contains_worker<'a>(
1422 infos: impl IntoIterator<Item = &'a Self> + 'a,
1423 worker_id: WorkerId,
1424 ) -> bool {
1425 Self::workers(infos).any(|existing_worker_id| existing_worker_id == worker_id)
1426 }
1427}
1428
1429impl InflightDatabaseInfo {
1430 pub fn contains_worker(&self, worker_id: WorkerId) -> bool {
1431 InflightFragmentInfo::contains_worker(self.fragment_infos(), worker_id)
1432 }
1433
1434 pub fn existing_table_ids(&self) -> impl Iterator<Item = TableId> + '_ {
1435 InflightFragmentInfo::existing_table_ids(self.fragment_infos())
1436 }
1437}