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::{
49 EdgeBuilderFragmentInfo, FragmentEdgeBuildResult, FragmentEdgeBuilder,
50};
51use crate::barrier::progress::{CreateMviewProgressTracker, StagingCommitInfo};
52use crate::barrier::rpc::{ControlStreamManager, to_partial_graph_id};
53use crate::barrier::{
54 BackfillProgress, BarrierKind, CreateStreamingJobType, FragmentBackfillProgress, TracedEpoch,
55};
56use crate::controller::fragment::{InflightActorInfo, InflightFragmentInfo};
57use crate::controller::utils::rebuild_fragment_mapping;
58use crate::manager::NotificationManagerRef;
59use crate::model::{ActorId, BackfillUpstreamType, FragmentId, StreamActor, StreamJobFragments};
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 snapshot_backfill_actor_ids(
421 fragment_infos: &HashMap<FragmentId, InflightFragmentInfo>,
422 ) -> impl Iterator<Item = ActorId> + '_ {
423 fragment_infos
424 .values()
425 .filter(|fragment| {
426 fragment
427 .fragment_type_mask
428 .contains(FragmentTypeFlag::SnapshotBackfillStreamScan)
429 })
430 .flat_map(|fragment| fragment.actors.keys().copied())
431 }
432
433 pub fn tracking_progress_actor_ids(
434 fragment_infos: &HashMap<FragmentId, InflightFragmentInfo>,
435 ) -> Vec<(ActorId, BackfillUpstreamType)> {
436 StreamJobFragments::tracking_progress_actor_ids_impl(
437 fragment_infos
438 .values()
439 .map(|fragment| (fragment.fragment_type_mask, fragment.actors.keys().copied())),
440 )
441 }
442}
443
444impl<'a> IntoIterator for &'a InflightStreamingJobInfo {
445 type Item = &'a InflightFragmentInfo;
446
447 type IntoIter = impl Iterator<Item = &'a InflightFragmentInfo> + 'a;
448
449 fn into_iter(self) -> Self::IntoIter {
450 self.fragment_infos()
451 }
452}
453
454#[derive(Debug)]
455pub struct InflightDatabaseInfo {
456 pub(super) database_id: DatabaseId,
457 jobs: HashMap<JobId, InflightStreamingJobInfo>,
458 fragment_location: HashMap<FragmentId, JobId>,
459 pub(super) shared_actor_infos: SharedActorInfos,
460}
461
462impl InflightDatabaseInfo {
463 pub fn fragment_infos(&self) -> impl Iterator<Item = &InflightFragmentInfo> + '_ {
464 self.jobs.values().flat_map(|job| job.fragment_infos())
465 }
466
467 pub fn contains_job(&self, job_id: JobId) -> bool {
468 self.jobs.contains_key(&job_id)
469 }
470
471 pub(super) fn job_id_by_fragment(&self, fragment_id: FragmentId) -> Option<JobId> {
472 self.fragment_location.get(&fragment_id).copied()
473 }
474
475 pub fn fragment(&self, fragment_id: FragmentId) -> &InflightFragmentInfo {
476 let job_id = self.fragment_location[&fragment_id];
477 self.jobs
478 .get(&job_id)
479 .expect("should exist")
480 .fragment_infos
481 .get(&fragment_id)
482 .expect("should exist")
483 }
484
485 pub(super) fn backfill_fragment_ids_for_job(
486 &self,
487 job_id: JobId,
488 ) -> MetaResult<HashSet<FragmentId>> {
489 let job = self
490 .jobs
491 .get(&job_id)
492 .ok_or_else(|| MetaError::invalid_parameter(format!("job {} not found", job_id)))?;
493 Ok(job
494 .fragment_infos
495 .iter()
496 .filter_map(|(fragment_id, fragment)| {
497 fragment
498 .fragment_type_mask
499 .contains_any([
500 FragmentTypeFlag::StreamScan,
501 FragmentTypeFlag::SourceScan,
502 FragmentTypeFlag::LocalityProvider,
503 ])
504 .then_some(*fragment_id)
505 })
506 .collect())
507 }
508
509 pub(super) fn is_backfill_fragment(&self, fragment_id: FragmentId) -> MetaResult<bool> {
510 let job_id = self.fragment_location.get(&fragment_id).ok_or_else(|| {
511 MetaError::invalid_parameter(format!("fragment {} not found", fragment_id))
512 })?;
513 let fragment = self
514 .jobs
515 .get(job_id)
516 .expect("should exist")
517 .fragment_infos
518 .get(&fragment_id)
519 .expect("should exist");
520 Ok(fragment.fragment_type_mask.contains_any([
521 FragmentTypeFlag::StreamScan,
522 FragmentTypeFlag::SourceScan,
523 FragmentTypeFlag::LocalityProvider,
524 ]))
525 }
526
527 pub fn gen_backfill_progress(&self) -> impl Iterator<Item = (JobId, BackfillProgress)> + '_ {
528 self.jobs
529 .iter()
530 .filter_map(|(job_id, job)| match &job.status {
531 CreateStreamingJobStatus::Init => None,
532 CreateStreamingJobStatus::Creating { tracker } => {
533 let progress = tracker.gen_backfill_progress();
534 Some((
535 *job_id,
536 BackfillProgress {
537 progress,
538 backfill_type: PbBackfillType::NormalBackfill,
539 },
540 ))
541 }
542 CreateStreamingJobStatus::Created => None,
543 })
544 }
545
546 pub fn gen_cdc_progress(&self) -> impl Iterator<Item = (JobId, CdcProgress)> + '_ {
547 self.jobs.iter().filter_map(|(job_id, job)| {
548 job.cdc_table_backfill_tracker
549 .as_ref()
550 .map(|tracker| (*job_id, tracker.gen_cdc_progress()))
551 })
552 }
553
554 pub fn gen_fragment_backfill_progress(&self) -> Vec<FragmentBackfillProgress> {
555 let mut result = Vec::new();
556 for job in self.jobs.values() {
557 let CreateStreamingJobStatus::Creating { tracker } = &job.status else {
558 continue;
559 };
560 let fragment_progress = tracker.collect_fragment_progress(&job.fragment_infos, true);
561 result.extend(fragment_progress);
562 }
563 result
564 }
565
566 pub(super) fn may_assign_fragment_cdc_backfill_splits(
567 &mut self,
568 fragment_id: FragmentId,
569 ) -> MetaResult<Option<HashMap<ActorId, PbCdcTableSnapshotSplits>>> {
570 let job_id = self.fragment_location[&fragment_id];
571 let job = self.jobs.get_mut(&job_id).expect("should exist");
572 if let Some(tracker) = &mut job.cdc_table_backfill_tracker {
573 let cdc_scan_fragment_id = tracker.cdc_scan_fragment_id();
574 if cdc_scan_fragment_id != fragment_id {
575 return Ok(None);
576 }
577 let actors = job.fragment_infos[&cdc_scan_fragment_id]
578 .actors
579 .keys()
580 .copied()
581 .collect();
582 tracker.reassign_splits(actors).map(Some)
583 } else {
584 Ok(None)
585 }
586 }
587
588 pub(super) fn assign_cdc_backfill_splits(
589 &mut self,
590 job_id: JobId,
591 ) -> MetaResult<Option<HashMap<ActorId, PbCdcTableSnapshotSplits>>> {
592 let job = self.jobs.get_mut(&job_id).expect("should exist");
593 if let Some(tracker) = &mut job.cdc_table_backfill_tracker {
594 let cdc_scan_fragment_id = tracker.cdc_scan_fragment_id();
595 let actors = job.fragment_infos[&cdc_scan_fragment_id]
596 .actors
597 .keys()
598 .copied()
599 .collect();
600 tracker.reassign_splits(actors).map(Some)
601 } else {
602 Ok(None)
603 }
604 }
605
606 pub(super) fn apply_collected_command(
607 &mut self,
608 command: &PostCollectCommand,
609 resps: &HashMap<WorkerId, BarrierCompleteResponse>,
610 version_stats: &HummockVersionStats,
611 ) {
612 if let PostCollectCommand::CreateStreamingJob { info, job_type, .. } = command {
613 match job_type {
614 CreateStreamingJobType::Normal | CreateStreamingJobType::SinkIntoTable(_) => {
615 let job_id = info.streaming_job.id();
616 if let Some(job_info) = self.jobs.get_mut(&job_id) {
617 let CreateStreamingJobStatus::Init = replace(
618 &mut job_info.status,
619 CreateStreamingJobStatus::Creating {
620 tracker: CreateMviewProgressTracker::new(
621 info,
622 version_stats,
623 &job_info.fragment_infos,
624 ),
625 },
626 ) else {
627 unreachable!("should be init before collect the first barrier")
628 };
629 } else {
630 info!(%job_id, "newly create job get cancelled before first barrier is collected")
631 }
632 }
633 CreateStreamingJobType::SnapshotBackfill { .. }
634 | CreateStreamingJobType::BatchRefresh(_) => {
635 }
637 }
638 }
639 if let PostCollectCommand::Reschedule { reschedules, .. } = command {
640 debug_assert!(
642 reschedules
643 .values()
644 .all(|reschedule| reschedule.vnode_bitmap_updates.is_empty()),
645 "Reschedule should not carry vnode bitmap updates when actors are rebuilt"
646 );
647
648 let related_job_ids = reschedules
650 .keys()
651 .filter_map(|fragment_id| self.fragment_location.get(fragment_id))
652 .cloned()
653 .collect::<HashSet<_>>();
654 for job_id in related_job_ids {
655 if let Some(job) = self.jobs.get_mut(&job_id)
656 && let CreateStreamingJobStatus::Creating { tracker, .. } = &mut job.status
657 {
658 tracker.refresh_after_reschedule(&job.fragment_infos, version_stats);
659 }
660 }
661 }
662 for progress in resps.values().flat_map(|resp| &resp.create_mview_progress) {
663 let Some(job_id) = self.fragment_location.get(&progress.fragment_id) else {
664 warn!(
665 "update the progress of an non-existent creating streaming job: {progress:?}, which could be cancelled"
666 );
667 continue;
668 };
669 let tracker = match &mut self.jobs.get_mut(job_id).expect("should exist").status {
670 CreateStreamingJobStatus::Init => {
671 continue;
672 }
673 CreateStreamingJobStatus::Creating { tracker, .. } => tracker,
674 CreateStreamingJobStatus::Created => {
675 if !progress.done {
676 warn!("update the progress of an created streaming job: {progress:?}");
677 }
678 continue;
679 }
680 };
681 tracker.apply_progress(progress, version_stats);
682 }
683 for progress in resps
684 .values()
685 .flat_map(|resp| &resp.cdc_table_backfill_progress)
686 {
687 let Some(job_id) = self.fragment_location.get(&progress.fragment_id) else {
688 warn!(
689 "update the cdc progress of an non-existent creating streaming job: {progress:?}, which could be cancelled"
690 );
691 continue;
692 };
693 let Some(tracker) = &mut self
694 .jobs
695 .get_mut(job_id)
696 .expect("should exist")
697 .cdc_table_backfill_tracker
698 else {
699 warn!("update the cdc progress of an created streaming job: {progress:?}");
700 continue;
701 };
702 tracker.update_split_progress(progress);
703 }
704 for cdc_offset_updated in resps
706 .values()
707 .flat_map(|resp| &resp.cdc_source_offset_updated)
708 {
709 let source_id = cdc_offset_updated.source_id;
710 let job_id = source_id.as_share_source_job_id();
711 if let Some(job) = self.jobs.get_mut(&job_id) {
712 if let CreateStreamingJobStatus::Creating { tracker, .. } = &mut job.status {
713 tracker.mark_cdc_source_finished();
714 }
715 } else {
716 warn!(
717 "update cdc source offset for non-existent creating streaming job: source_id={}, job_id={}",
718 cdc_offset_updated.source_id, job_id
719 );
720 }
721 }
722 }
723
724 fn iter_creating_job_tracker(&self) -> impl Iterator<Item = &CreateMviewProgressTracker> {
725 self.jobs.values().filter_map(|job| match &job.status {
726 CreateStreamingJobStatus::Init => None,
727 CreateStreamingJobStatus::Creating { tracker, .. } => Some(tracker),
728 CreateStreamingJobStatus::Created => None,
729 })
730 }
731
732 fn iter_mut_creating_job_tracker(
733 &mut self,
734 ) -> impl Iterator<Item = &mut CreateMviewProgressTracker> {
735 self.jobs
736 .values_mut()
737 .filter_map(|job| match &mut job.status {
738 CreateStreamingJobStatus::Init => None,
739 CreateStreamingJobStatus::Creating { tracker, .. } => Some(tracker),
740 CreateStreamingJobStatus::Created => None,
741 })
742 }
743
744 pub(super) fn has_pending_finished_jobs(&self) -> bool {
745 self.iter_creating_job_tracker()
746 .any(|tracker| tracker.is_finished())
747 }
748
749 pub(super) fn take_pending_backfill_nodes(&mut self) -> Vec<FragmentId> {
750 self.iter_mut_creating_job_tracker()
751 .flat_map(|tracker| tracker.take_pending_backfill_nodes())
752 .collect()
753 }
754
755 pub(super) fn take_staging_commit_info(&mut self) -> StagingCommitInfo {
756 let mut finished_jobs = vec![];
757 let mut table_ids_to_truncate = vec![];
758 let mut finished_cdc_table_backfill = vec![];
759 for (job_id, job) in &mut self.jobs {
760 if let CreateStreamingJobStatus::Creating { tracker, .. } = &mut job.status {
761 let (is_finished, truncate_table_ids) = tracker.collect_staging_commit_info();
762 table_ids_to_truncate.extend(truncate_table_ids);
763 if is_finished {
764 let CreateStreamingJobStatus::Creating { tracker, .. } =
765 replace(&mut job.status, CreateStreamingJobStatus::Created)
766 else {
767 unreachable!()
768 };
769 finished_jobs.push(tracker.into_tracking_job());
770 }
771 }
772 if let Some(tracker) = &mut job.cdc_table_backfill_tracker
773 && tracker.take_pre_completed()
774 {
775 finished_cdc_table_backfill.push(*job_id);
776 }
777 }
778 StagingCommitInfo {
779 finished_jobs,
780 table_ids_to_truncate,
781 finished_cdc_table_backfill,
782 }
783 }
784
785 pub fn fragment_subscribers(
786 &self,
787 fragment_id: FragmentId,
788 ) -> impl Iterator<Item = SubscriberId> + '_ {
789 let job_id = self.fragment_location[&fragment_id];
790 self.jobs[&job_id].subscribers.keys().copied()
791 }
792
793 pub fn job_subscribers(&self, job_id: JobId) -> impl Iterator<Item = SubscriberId> + '_ {
794 self.jobs[&job_id].subscribers.keys().copied()
795 }
796
797 pub fn subscribed_tables(&self) -> impl Iterator<Item = TableId> + '_ {
798 self.jobs.iter().filter_map(|(job_id, info)| {
799 info.subscribers
800 .values()
801 .any(|subscriber| matches!(subscriber, SubscriberType::Subscription(_)))
802 .then_some(job_id.as_mv_table_id())
803 })
804 }
805
806 pub fn register_subscriber(
807 &mut self,
808 job_id: JobId,
809 subscriber_id: SubscriberId,
810 subscriber: SubscriberType,
811 ) {
812 self.jobs
813 .get_mut(&job_id)
814 .expect("should exist")
815 .subscribers
816 .try_insert(subscriber_id, subscriber)
817 .expect("non duplicate");
818 }
819
820 pub fn unregister_subscriber(
821 &mut self,
822 job_id: JobId,
823 subscriber_id: SubscriberId,
824 ) -> Option<SubscriberType> {
825 self.jobs
826 .get_mut(&job_id)
827 .expect("should exist")
828 .subscribers
829 .remove(&subscriber_id)
830 }
831
832 pub fn update_subscription_retention(
833 &mut self,
834 job_id: JobId,
835 subscriber_id: SubscriberId,
836 retention_second: u64,
837 ) {
838 let job = self.jobs.get_mut(&job_id).expect("should exist");
839 match job.subscribers.get_mut(&subscriber_id) {
840 Some(SubscriberType::Subscription(current_retention)) => {
841 *current_retention = retention_second;
842 }
843 Some(SubscriberType::SnapshotBackfill) => {
844 warn!(
845 %job_id,
846 %subscriber_id,
847 "cannot update retention for snapshot backfill subscriber"
848 );
849 }
850 None => {
851 warn!(%job_id, %subscriber_id, "subscription subscriber not found");
852 }
853 }
854 }
855
856 fn fragment_mut(&mut self, fragment_id: FragmentId) -> (&mut InflightFragmentInfo, JobId) {
857 let job_id = self.fragment_location[&fragment_id];
858 let fragment = self
859 .jobs
860 .get_mut(&job_id)
861 .expect("should exist")
862 .fragment_infos
863 .get_mut(&fragment_id)
864 .expect("should exist");
865 (fragment, job_id)
866 }
867
868 fn empty_inner(database_id: DatabaseId, shared_actor_infos: SharedActorInfos) -> Self {
869 Self {
870 database_id,
871 jobs: Default::default(),
872 fragment_location: Default::default(),
873 shared_actor_infos,
874 }
875 }
876
877 pub fn empty(database_id: DatabaseId, shared_actor_infos: SharedActorInfos) -> Self {
878 shared_actor_infos.remove_database(database_id);
880 Self::empty_inner(database_id, shared_actor_infos)
881 }
882
883 pub fn recover(
884 database_id: DatabaseId,
885 jobs: impl Iterator<Item = InflightStreamingJobInfo>,
886 shared_actor_infos: SharedActorInfos,
887 ) -> Self {
888 let mut info = Self::empty_inner(database_id, shared_actor_infos);
889 for job in jobs {
890 info.add_existing(job);
891 }
892 info
893 }
894
895 pub fn is_empty(&self) -> bool {
896 self.jobs.is_empty()
897 }
898
899 pub fn add_existing(&mut self, job: InflightStreamingJobInfo) {
900 let InflightStreamingJobInfo {
901 job_id,
902 fragment_infos,
903 subscribers,
904 status,
905 cdc_table_backfill_tracker,
906 } = job;
907 self.jobs
908 .try_insert(
909 job_id,
910 InflightStreamingJobInfo {
911 job_id,
912 subscribers,
913 fragment_infos: Default::default(), status,
915 cdc_table_backfill_tracker,
916 },
917 )
918 .expect("non-duplicate");
919 self.pre_apply_new_fragments(
920 fragment_infos
921 .into_iter()
922 .map(|(fragment_id, info)| (fragment_id, job_id, info)),
923 );
924 }
925
926 pub(crate) fn pre_apply_new_job(
928 &mut self,
929 job_id: JobId,
930 cdc_table_backfill_tracker: Option<CdcTableBackfillTracker>,
931 ) {
932 {
933 self.jobs
934 .try_insert(
935 job_id,
936 InflightStreamingJobInfo {
937 job_id,
938 fragment_infos: Default::default(),
939 subscribers: Default::default(), status: CreateStreamingJobStatus::Init,
941 cdc_table_backfill_tracker,
942 },
943 )
944 .expect("non-duplicate");
945 }
946 }
947
948 pub(crate) fn pre_apply_new_fragments(
950 &mut self,
951 fragments: impl IntoIterator<Item = (FragmentId, JobId, InflightFragmentInfo)>,
952 ) {
953 {
954 let shared_infos = self.shared_actor_infos.clone();
955 let mut shared_actor_writer = shared_infos.start_writer(self.database_id);
956 for (fragment_id, job_id, info) in fragments {
957 {
958 {
959 let fragment_infos = self.jobs.get_mut(&job_id).expect("should exist");
960 shared_actor_writer.upsert([(&info, job_id)]);
961 fragment_infos
962 .fragment_infos
963 .try_insert(fragment_id, info)
964 .expect("non duplicate");
965 self.fragment_location
966 .try_insert(fragment_id, job_id)
967 .expect("non duplicate");
968 }
969 }
970 }
971 shared_actor_writer.finish();
972 }
973 }
974
975 pub(crate) fn pre_apply_reschedule(
978 &mut self,
979 fragment_id: FragmentId,
980 new_actors: HashMap<ActorId, InflightActorInfo>,
981 actor_update_vnode_bitmap: HashMap<ActorId, Bitmap>,
982 actor_splits: HashMap<ActorId, Vec<SplitImpl>>,
983 ) {
984 {
985 {
986 {
987 {
988 let (info, _) = self.fragment_mut(fragment_id);
989 let actors = &mut info.actors;
990 for (actor_id, new_vnodes) in actor_update_vnode_bitmap {
991 actors
992 .get_mut(&actor_id)
993 .expect("should exist")
994 .vnode_bitmap = Some(new_vnodes);
995 }
996 for (actor_id, actor) in new_actors {
997 actors
998 .try_insert(actor_id as _, actor)
999 .expect("non-duplicate");
1000 }
1001 for (actor_id, splits) in actor_splits {
1002 actors.get_mut(&actor_id).expect("should exist").splits = splits;
1003 }
1004 }
1006 }
1007 }
1008 }
1009 }
1010
1011 pub(crate) fn pre_apply_replace_node_upstream(
1013 &mut self,
1014 fragment_id: FragmentId,
1015 replace_map: &HashMap<FragmentId, FragmentId>,
1016 ) {
1017 {
1018 {
1019 {
1020 {
1021 let mut remaining_fragment_ids: HashSet<_> =
1022 replace_map.keys().cloned().collect();
1023 let (info, _) = self.fragment_mut(fragment_id);
1024 visit_stream_node_mut(&mut info.nodes, |node| {
1025 if let NodeBody::Merge(m) = node
1026 && let Some(new_upstream_fragment_id) =
1027 replace_map.get(&m.upstream_fragment_id)
1028 {
1029 if !remaining_fragment_ids.remove(&m.upstream_fragment_id) {
1030 if cfg!(debug_assertions) {
1031 panic!(
1032 "duplicate upstream fragment: {:?} {:?}",
1033 m, replace_map
1034 );
1035 } else {
1036 warn!(?m, ?replace_map, "duplicate upstream fragment");
1037 }
1038 }
1039 m.upstream_fragment_id = *new_upstream_fragment_id;
1040 }
1041 });
1042 if cfg!(debug_assertions) {
1043 assert!(
1044 remaining_fragment_ids.is_empty(),
1045 "non-existing fragment to replace: {:?} {:?} {:?}",
1046 remaining_fragment_ids,
1047 info.nodes,
1048 replace_map
1049 );
1050 } else {
1051 warn!(?remaining_fragment_ids, node = ?info.nodes, ?replace_map, "non-existing fragment to replace");
1052 }
1053 }
1054 }
1055 }
1056 }
1057 }
1058
1059 pub(crate) fn pre_apply_add_node_upstream(
1061 &mut self,
1062 fragment_id: FragmentId,
1063 new_upstream_info: &PbUpstreamSinkInfo,
1064 ) {
1065 {
1066 {
1067 {
1068 {
1069 let (info, _) = self.fragment_mut(fragment_id);
1070 let mut injected = false;
1071 visit_stream_node_mut(&mut info.nodes, |node| {
1072 if let NodeBody::UpstreamSinkUnion(u) = node {
1073 if cfg!(debug_assertions) {
1074 let current_upstream_fragment_ids = u
1075 .init_upstreams
1076 .iter()
1077 .map(|upstream| upstream.upstream_fragment_id)
1078 .collect::<HashSet<_>>();
1079 if current_upstream_fragment_ids
1080 .contains(&new_upstream_info.upstream_fragment_id)
1081 {
1082 panic!(
1083 "duplicate upstream fragment: {:?} {:?}",
1084 u, new_upstream_info
1085 );
1086 }
1087 }
1088 u.init_upstreams.push(new_upstream_info.clone());
1089 injected = true;
1090 }
1091 });
1092 assert!(injected, "should inject upstream into UpstreamSinkUnion");
1093 }
1094 }
1095 }
1096 }
1097 }
1098
1099 pub(crate) fn pre_apply_drop_node_upstream(
1101 &mut self,
1102 fragment_id: FragmentId,
1103 drop_upstream_fragment_ids: &[FragmentId],
1104 ) {
1105 if !self.fragment_location.contains_key(&fragment_id) {
1106 warn!(
1107 target_fragment_id = %fragment_id,
1108 drop_upstream_fragment_ids = ?drop_upstream_fragment_ids,
1109 "skip dropping upstream sink fragments for non-existing target fragment"
1110 );
1111 return;
1112 }
1113 {
1114 {
1115 {
1116 {
1117 let (info, _) = self.fragment_mut(fragment_id);
1118 let mut removed = false;
1119 visit_stream_node_mut(&mut info.nodes, |node| {
1120 if let NodeBody::UpstreamSinkUnion(u) = node {
1121 if cfg!(debug_assertions) {
1122 let current_upstream_fragment_ids = u
1123 .init_upstreams
1124 .iter()
1125 .map(|upstream| upstream.upstream_fragment_id)
1126 .collect::<HashSet<FragmentId>>();
1127 for drop_fragment_id in drop_upstream_fragment_ids {
1128 if !current_upstream_fragment_ids.contains(drop_fragment_id)
1129 {
1130 panic!(
1131 "non-existing upstream fragment to drop: {:?} {:?} {:?}",
1132 u, drop_upstream_fragment_ids, drop_fragment_id
1133 );
1134 }
1135 }
1136 }
1137 u.init_upstreams.retain(|upstream| {
1138 !drop_upstream_fragment_ids
1139 .contains(&upstream.upstream_fragment_id)
1140 });
1141 removed = true;
1142 }
1143 });
1144 assert!(removed, "should remove upstream from UpstreamSinkUnion");
1145 }
1146 }
1147 }
1148 }
1149 }
1150
1151 pub(crate) fn pre_apply_throttle(
1153 &mut self,
1154 config: &mut ThrottleConfigMap,
1155 ) -> Option<Mutation> {
1156 extract_throttle_config(config, |fragment_id, stream_node| {
1157 if !self.fragment_location.contains_key(&fragment_id) {
1158 return false;
1159 }
1160 self.fragment_mut(fragment_id).0.nodes = stream_node.clone();
1161 true
1162 })
1163 }
1164
1165 pub(crate) fn pre_apply_split_assignments(
1167 &mut self,
1168 assignments: impl IntoIterator<Item = (FragmentId, HashMap<ActorId, Vec<SplitImpl>>)>,
1169 ) {
1170 {
1171 let shared_infos = self.shared_actor_infos.clone();
1172 let mut shared_actor_writer = shared_infos.start_writer(self.database_id);
1173 {
1174 {
1175 for (fragment_id, actor_splits) in assignments {
1176 let (info, job_id) = self.fragment_mut(fragment_id);
1177 let actors = &mut info.actors;
1178 for (actor_id, splits) in actor_splits {
1179 actors.get_mut(&actor_id).expect("should exist").splits = splits;
1180 }
1181 shared_actor_writer.upsert([(&*info, job_id)]);
1182 }
1183 }
1184 }
1185 shared_actor_writer.finish();
1186 }
1187 }
1188
1189 pub(super) fn build_edge(
1190 &self,
1191 info: Option<(&CreateStreamingJobCommandInfo, bool)>,
1192 replace_job: Option<&ReplaceStreamJobPlan>,
1193 new_upstream_sink: Option<&UpstreamSinkInfo>,
1194 control_stream_manager: &ControlStreamManager,
1195 stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
1196 actor_location: &HashMap<ActorId, WorkerId>,
1197 ) -> FragmentEdgeBuildResult {
1198 let existing_fragment_ids = info
1206 .into_iter()
1207 .flat_map(|(info, _)| info.upstream_fragment_downstreams.keys())
1208 .chain(replace_job.into_iter().flat_map(|replace_job| {
1209 replace_job
1210 .upstream_fragment_downstreams
1211 .keys()
1212 .filter(|fragment_id| {
1213 info.map(|(info, _)| {
1214 !info
1215 .stream_job_fragments
1216 .fragments
1217 .contains_key(*fragment_id)
1218 })
1219 .unwrap_or(true)
1220 })
1221 .chain(replace_job.replace_upstream.keys())
1222 }))
1223 .chain(
1224 new_upstream_sink
1225 .into_iter()
1226 .map(|ctx| &ctx.new_sink_downstream.downstream_fragment_id),
1227 )
1228 .cloned();
1229 let new_fragments = info
1231 .into_iter()
1232 .flat_map(|(info, is_snapshot_backfill)| {
1233 let partial_graph_id = to_partial_graph_id(
1234 self.database_id,
1235 is_snapshot_backfill.then_some(info.streaming_job.id()),
1236 );
1237 info.stream_job_fragments
1238 .fragments
1239 .values()
1240 .map(move |fragment| (partial_graph_id, fragment))
1241 })
1242 .chain(replace_job.into_iter().flat_map(|replace_job| {
1243 replace_job
1244 .new_fragments
1245 .fragments
1246 .values()
1247 .chain(
1248 replace_job
1249 .auto_refresh_schema_sinks
1250 .as_ref()
1251 .into_iter()
1252 .flat_map(move |sinks| sinks.iter().map(|sink| &sink.new_fragment)),
1253 )
1254 .map(|fragment| {
1255 (
1256 to_partial_graph_id(self.database_id, None),
1258 fragment,
1259 )
1260 })
1261 }));
1262
1263 let mut builder = FragmentEdgeBuilder::new(
1264 existing_fragment_ids
1266 .map(|fragment_id| {
1267 (
1268 fragment_id,
1269 EdgeBuilderFragmentInfo::from_inflight(
1270 self.fragment(fragment_id),
1271 to_partial_graph_id(self.database_id, None),
1272 control_stream_manager,
1273 ),
1274 )
1275 })
1276 .chain(new_fragments.map(|(partial_graph_id, fragment)| {
1278 (
1279 fragment.fragment_id,
1280 EdgeBuilderFragmentInfo::from_fragment(
1281 fragment,
1282 stream_actors,
1283 actor_location,
1284 partial_graph_id,
1285 control_stream_manager,
1286 ),
1287 )
1288 })),
1289 );
1290 if let Some((info, _)) = info {
1291 builder.add_relations(&info.upstream_fragment_downstreams);
1292 builder.add_relations(&info.stream_job_fragments.downstreams);
1293 }
1294 if let Some(replace_job) = replace_job {
1295 builder.add_relations(&replace_job.upstream_fragment_downstreams);
1296 builder.add_relations(&replace_job.new_fragments.downstreams);
1297 }
1298 if let Some(new_upstream_sink) = new_upstream_sink {
1299 let sink_fragment_id = new_upstream_sink.sink_fragment_id;
1300 let new_sink_downstream = &new_upstream_sink.new_sink_downstream;
1301 builder.add_edge(sink_fragment_id, new_sink_downstream);
1302 }
1303 if let Some(replace_job) = replace_job {
1304 for (fragment_id, fragment_replacement) in &replace_job.replace_upstream {
1305 for (original_upstream_fragment_id, new_upstream_fragment_id) in
1306 fragment_replacement
1307 {
1308 builder.replace_upstream(
1309 *fragment_id,
1310 *original_upstream_fragment_id,
1311 *new_upstream_fragment_id,
1312 );
1313 }
1314 }
1315 }
1316 builder.build()
1317 }
1318
1319 pub(crate) fn post_apply_reschedules(
1321 &mut self,
1322 reschedules: impl IntoIterator<Item = (FragmentId, HashSet<ActorId>)>,
1323 ) {
1324 let inner = self.shared_actor_infos.clone();
1325 let mut shared_actor_writer = inner.start_writer(self.database_id);
1326 {
1327 {
1328 {
1329 for (fragment_id, to_remove) in reschedules {
1330 let job_id = self.fragment_location[&fragment_id];
1331 let info = self
1332 .jobs
1333 .get_mut(&job_id)
1334 .expect("should exist")
1335 .fragment_infos
1336 .get_mut(&fragment_id)
1337 .expect("should exist");
1338 for actor_id in to_remove {
1339 assert!(info.actors.remove(&actor_id).is_some());
1340 }
1341 shared_actor_writer.upsert([(&*info, job_id)]);
1342 }
1343 }
1344 }
1345 }
1346 shared_actor_writer.finish();
1347 }
1348
1349 pub(crate) fn post_apply_remove_fragments(
1351 &mut self,
1352 fragment_ids: impl IntoIterator<Item = FragmentId>,
1353 ) {
1354 let inner = self.shared_actor_infos.clone();
1355 let mut shared_actor_writer = inner.start_writer(self.database_id);
1356 {
1357 {
1358 {
1359 for fragment_id in fragment_ids {
1360 let job_id = self
1361 .fragment_location
1362 .remove(&fragment_id)
1363 .expect("should exist");
1364 let job = self.jobs.get_mut(&job_id).expect("should exist");
1365 let fragment = job
1366 .fragment_infos
1367 .remove(&fragment_id)
1368 .expect("should exist");
1369 shared_actor_writer.remove(&fragment);
1370 if job.fragment_infos.is_empty() {
1371 self.jobs.remove(&job_id).expect("should exist");
1372 }
1373 }
1374 }
1375 }
1376 }
1377 shared_actor_writer.finish();
1378 }
1379
1380 pub(crate) fn post_apply_remove_job(
1381 &mut self,
1382 job_id: JobId,
1383 ) -> Option<InflightStreamingJobInfo> {
1384 let job = self.jobs.remove(&job_id)?;
1385 let inner = self.shared_actor_infos.clone();
1386 let mut shared_actor_writer = inner.start_writer(self.database_id);
1387 for (fragment_id, fragment) in &job.fragment_infos {
1388 self.fragment_location
1389 .remove(fragment_id)
1390 .expect("should exist");
1391 shared_actor_writer.remove(fragment);
1392 }
1393 shared_actor_writer.finish();
1394 Some(job)
1395 }
1396}
1397
1398impl InflightFragmentInfo {
1399 pub(crate) fn actor_ids_to_collect(
1401 infos: impl IntoIterator<Item = &Self>,
1402 ) -> HashMap<WorkerId, HashSet<ActorId>> {
1403 let mut ret: HashMap<_, HashSet<_>> = HashMap::new();
1404 for (actor_id, actor) in infos.into_iter().flat_map(|info| info.actors.iter()) {
1405 assert!(
1406 ret.entry(actor.worker_id)
1407 .or_default()
1408 .insert(*actor_id as _)
1409 )
1410 }
1411 ret
1412 }
1413
1414 pub fn existing_table_ids<'a>(
1415 infos: impl IntoIterator<Item = &'a Self> + 'a,
1416 ) -> impl Iterator<Item = TableId> + 'a {
1417 infos
1418 .into_iter()
1419 .flat_map(|info| info.state_table_ids.iter().cloned())
1420 }
1421
1422 pub fn workers<'a>(
1423 infos: impl IntoIterator<Item = &'a Self> + 'a,
1424 ) -> impl Iterator<Item = WorkerId> + 'a {
1425 infos
1426 .into_iter()
1427 .flat_map(|fragment| fragment.actors.values().map(|actor| actor.worker_id))
1428 }
1429
1430 pub fn contains_worker<'a>(
1431 infos: impl IntoIterator<Item = &'a Self> + 'a,
1432 worker_id: WorkerId,
1433 ) -> bool {
1434 Self::workers(infos).any(|existing_worker_id| existing_worker_id == worker_id)
1435 }
1436}
1437
1438impl InflightDatabaseInfo {
1439 pub fn contains_worker(&self, worker_id: WorkerId) -> bool {
1440 InflightFragmentInfo::contains_worker(self.fragment_infos(), worker_id)
1441 }
1442
1443 pub fn existing_table_ids(&self) -> impl Iterator<Item = TableId> + '_ {
1444 InflightFragmentInfo::existing_table_ids(self.fragment_infos())
1445 }
1446}