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