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 max_subscription_retention(&self) -> impl Iterator<Item = (TableId, u64)> + '_ {
798 self.jobs.iter().filter_map(|(job_id, info)| {
799 info.subscribers
800 .values()
801 .filter_map(|subscriber| match subscriber {
802 SubscriberType::Subscription(retention) => Some(*retention),
803 SubscriberType::SnapshotBackfill => None,
804 })
805 .max()
806 .map(|max_subscription| (job_id.as_mv_table_id(), max_subscription))
807 })
808 }
809
810 pub fn register_subscriber(
811 &mut self,
812 job_id: JobId,
813 subscriber_id: SubscriberId,
814 subscriber: SubscriberType,
815 ) {
816 self.jobs
817 .get_mut(&job_id)
818 .expect("should exist")
819 .subscribers
820 .try_insert(subscriber_id, subscriber)
821 .expect("non duplicate");
822 }
823
824 pub fn unregister_subscriber(
825 &mut self,
826 job_id: JobId,
827 subscriber_id: SubscriberId,
828 ) -> Option<SubscriberType> {
829 self.jobs
830 .get_mut(&job_id)
831 .expect("should exist")
832 .subscribers
833 .remove(&subscriber_id)
834 }
835
836 pub fn update_subscription_retention(
837 &mut self,
838 job_id: JobId,
839 subscriber_id: SubscriberId,
840 retention_second: u64,
841 ) {
842 let job = self.jobs.get_mut(&job_id).expect("should exist");
843 match job.subscribers.get_mut(&subscriber_id) {
844 Some(SubscriberType::Subscription(current_retention)) => {
845 *current_retention = retention_second;
846 }
847 Some(SubscriberType::SnapshotBackfill) => {
848 warn!(
849 %job_id,
850 %subscriber_id,
851 "cannot update retention for snapshot backfill subscriber"
852 );
853 }
854 None => {
855 warn!(%job_id, %subscriber_id, "subscription subscriber not found");
856 }
857 }
858 }
859
860 fn fragment_mut(&mut self, fragment_id: FragmentId) -> (&mut InflightFragmentInfo, JobId) {
861 let job_id = self.fragment_location[&fragment_id];
862 let fragment = self
863 .jobs
864 .get_mut(&job_id)
865 .expect("should exist")
866 .fragment_infos
867 .get_mut(&fragment_id)
868 .expect("should exist");
869 (fragment, job_id)
870 }
871
872 fn empty_inner(database_id: DatabaseId, shared_actor_infos: SharedActorInfos) -> Self {
873 Self {
874 database_id,
875 jobs: Default::default(),
876 fragment_location: Default::default(),
877 shared_actor_infos,
878 }
879 }
880
881 pub fn empty(database_id: DatabaseId, shared_actor_infos: SharedActorInfos) -> Self {
882 shared_actor_infos.remove_database(database_id);
884 Self::empty_inner(database_id, shared_actor_infos)
885 }
886
887 pub fn recover(
888 database_id: DatabaseId,
889 jobs: impl Iterator<Item = InflightStreamingJobInfo>,
890 shared_actor_infos: SharedActorInfos,
891 ) -> Self {
892 let mut info = Self::empty_inner(database_id, shared_actor_infos);
893 for job in jobs {
894 info.add_existing(job);
895 }
896 info
897 }
898
899 pub fn is_empty(&self) -> bool {
900 self.jobs.is_empty()
901 }
902
903 pub fn add_existing(&mut self, job: InflightStreamingJobInfo) {
904 let InflightStreamingJobInfo {
905 job_id,
906 fragment_infos,
907 subscribers,
908 status,
909 cdc_table_backfill_tracker,
910 } = job;
911 self.jobs
912 .try_insert(
913 job_id,
914 InflightStreamingJobInfo {
915 job_id,
916 subscribers,
917 fragment_infos: Default::default(), status,
919 cdc_table_backfill_tracker,
920 },
921 )
922 .expect("non-duplicate");
923 self.pre_apply_new_fragments(
924 fragment_infos
925 .into_iter()
926 .map(|(fragment_id, info)| (fragment_id, job_id, info)),
927 );
928 }
929
930 pub(crate) fn pre_apply_new_job(
932 &mut self,
933 job_id: JobId,
934 cdc_table_backfill_tracker: Option<CdcTableBackfillTracker>,
935 ) {
936 {
937 self.jobs
938 .try_insert(
939 job_id,
940 InflightStreamingJobInfo {
941 job_id,
942 fragment_infos: Default::default(),
943 subscribers: Default::default(), status: CreateStreamingJobStatus::Init,
945 cdc_table_backfill_tracker,
946 },
947 )
948 .expect("non-duplicate");
949 }
950 }
951
952 pub(crate) fn pre_apply_new_fragments(
954 &mut self,
955 fragments: impl IntoIterator<Item = (FragmentId, JobId, InflightFragmentInfo)>,
956 ) {
957 {
958 let shared_infos = self.shared_actor_infos.clone();
959 let mut shared_actor_writer = shared_infos.start_writer(self.database_id);
960 for (fragment_id, job_id, info) in fragments {
961 {
962 {
963 let fragment_infos = self.jobs.get_mut(&job_id).expect("should exist");
964 shared_actor_writer.upsert([(&info, job_id)]);
965 fragment_infos
966 .fragment_infos
967 .try_insert(fragment_id, info)
968 .expect("non duplicate");
969 self.fragment_location
970 .try_insert(fragment_id, job_id)
971 .expect("non duplicate");
972 }
973 }
974 }
975 shared_actor_writer.finish();
976 }
977 }
978
979 pub(crate) fn pre_apply_reschedule(
982 &mut self,
983 fragment_id: FragmentId,
984 new_actors: HashMap<ActorId, InflightActorInfo>,
985 actor_update_vnode_bitmap: HashMap<ActorId, Bitmap>,
986 actor_splits: HashMap<ActorId, Vec<SplitImpl>>,
987 ) {
988 {
989 {
990 {
991 {
992 let (info, _) = self.fragment_mut(fragment_id);
993 let actors = &mut info.actors;
994 for (actor_id, new_vnodes) in actor_update_vnode_bitmap {
995 actors
996 .get_mut(&actor_id)
997 .expect("should exist")
998 .vnode_bitmap = Some(new_vnodes);
999 }
1000 for (actor_id, actor) in new_actors {
1001 actors
1002 .try_insert(actor_id as _, actor)
1003 .expect("non-duplicate");
1004 }
1005 for (actor_id, splits) in actor_splits {
1006 actors.get_mut(&actor_id).expect("should exist").splits = splits;
1007 }
1008 }
1010 }
1011 }
1012 }
1013 }
1014
1015 pub(crate) fn pre_apply_replace_node_upstream(
1017 &mut self,
1018 fragment_id: FragmentId,
1019 replace_map: &HashMap<FragmentId, FragmentId>,
1020 ) {
1021 {
1022 {
1023 {
1024 {
1025 let mut remaining_fragment_ids: HashSet<_> =
1026 replace_map.keys().cloned().collect();
1027 let (info, _) = self.fragment_mut(fragment_id);
1028 visit_stream_node_mut(&mut info.nodes, |node| {
1029 if let NodeBody::Merge(m) = node
1030 && let Some(new_upstream_fragment_id) =
1031 replace_map.get(&m.upstream_fragment_id)
1032 {
1033 if !remaining_fragment_ids.remove(&m.upstream_fragment_id) {
1034 if cfg!(debug_assertions) {
1035 panic!(
1036 "duplicate upstream fragment: {:?} {:?}",
1037 m, replace_map
1038 );
1039 } else {
1040 warn!(?m, ?replace_map, "duplicate upstream fragment");
1041 }
1042 }
1043 m.upstream_fragment_id = *new_upstream_fragment_id;
1044 }
1045 });
1046 if cfg!(debug_assertions) {
1047 assert!(
1048 remaining_fragment_ids.is_empty(),
1049 "non-existing fragment to replace: {:?} {:?} {:?}",
1050 remaining_fragment_ids,
1051 info.nodes,
1052 replace_map
1053 );
1054 } else {
1055 warn!(?remaining_fragment_ids, node = ?info.nodes, ?replace_map, "non-existing fragment to replace");
1056 }
1057 }
1058 }
1059 }
1060 }
1061 }
1062
1063 pub(crate) fn pre_apply_add_node_upstream(
1065 &mut self,
1066 fragment_id: FragmentId,
1067 new_upstream_info: &PbUpstreamSinkInfo,
1068 ) {
1069 {
1070 {
1071 {
1072 {
1073 let (info, _) = self.fragment_mut(fragment_id);
1074 let mut injected = false;
1075 visit_stream_node_mut(&mut info.nodes, |node| {
1076 if let NodeBody::UpstreamSinkUnion(u) = node {
1077 if cfg!(debug_assertions) {
1078 let current_upstream_fragment_ids = u
1079 .init_upstreams
1080 .iter()
1081 .map(|upstream| upstream.upstream_fragment_id)
1082 .collect::<HashSet<_>>();
1083 if current_upstream_fragment_ids
1084 .contains(&new_upstream_info.upstream_fragment_id)
1085 {
1086 panic!(
1087 "duplicate upstream fragment: {:?} {:?}",
1088 u, new_upstream_info
1089 );
1090 }
1091 }
1092 u.init_upstreams.push(new_upstream_info.clone());
1093 injected = true;
1094 }
1095 });
1096 assert!(injected, "should inject upstream into UpstreamSinkUnion");
1097 }
1098 }
1099 }
1100 }
1101 }
1102
1103 pub(crate) fn pre_apply_drop_node_upstream(
1105 &mut self,
1106 fragment_id: FragmentId,
1107 drop_upstream_fragment_ids: &[FragmentId],
1108 ) {
1109 if !self.fragment_location.contains_key(&fragment_id) {
1110 warn!(
1111 target_fragment_id = %fragment_id,
1112 drop_upstream_fragment_ids = ?drop_upstream_fragment_ids,
1113 "skip dropping upstream sink fragments for non-existing target fragment"
1114 );
1115 return;
1116 }
1117 {
1118 {
1119 {
1120 {
1121 let (info, _) = self.fragment_mut(fragment_id);
1122 let mut removed = false;
1123 visit_stream_node_mut(&mut info.nodes, |node| {
1124 if let NodeBody::UpstreamSinkUnion(u) = node {
1125 if cfg!(debug_assertions) {
1126 let current_upstream_fragment_ids = u
1127 .init_upstreams
1128 .iter()
1129 .map(|upstream| upstream.upstream_fragment_id)
1130 .collect::<HashSet<FragmentId>>();
1131 for drop_fragment_id in drop_upstream_fragment_ids {
1132 if !current_upstream_fragment_ids.contains(drop_fragment_id)
1133 {
1134 panic!(
1135 "non-existing upstream fragment to drop: {:?} {:?} {:?}",
1136 u, drop_upstream_fragment_ids, drop_fragment_id
1137 );
1138 }
1139 }
1140 }
1141 u.init_upstreams.retain(|upstream| {
1142 !drop_upstream_fragment_ids
1143 .contains(&upstream.upstream_fragment_id)
1144 });
1145 removed = true;
1146 }
1147 });
1148 assert!(removed, "should remove upstream from UpstreamSinkUnion");
1149 }
1150 }
1151 }
1152 }
1153 }
1154
1155 pub(crate) fn pre_apply_throttle(
1157 &mut self,
1158 config: &mut ThrottleConfigMap,
1159 ) -> Option<Mutation> {
1160 extract_throttle_config(config, |fragment_id, stream_node| {
1161 if !self.fragment_location.contains_key(&fragment_id) {
1162 return false;
1163 }
1164 self.fragment_mut(fragment_id).0.nodes = stream_node.clone();
1165 true
1166 })
1167 }
1168
1169 pub(crate) fn pre_apply_split_assignments(
1171 &mut self,
1172 assignments: impl IntoIterator<Item = (FragmentId, HashMap<ActorId, Vec<SplitImpl>>)>,
1173 ) {
1174 {
1175 let shared_infos = self.shared_actor_infos.clone();
1176 let mut shared_actor_writer = shared_infos.start_writer(self.database_id);
1177 {
1178 {
1179 for (fragment_id, actor_splits) in assignments {
1180 let (info, job_id) = self.fragment_mut(fragment_id);
1181 let actors = &mut info.actors;
1182 for (actor_id, splits) in actor_splits {
1183 actors.get_mut(&actor_id).expect("should exist").splits = splits;
1184 }
1185 shared_actor_writer.upsert([(&*info, job_id)]);
1186 }
1187 }
1188 }
1189 shared_actor_writer.finish();
1190 }
1191 }
1192
1193 pub(super) fn build_edge(
1194 &self,
1195 info: Option<(&CreateStreamingJobCommandInfo, bool)>,
1196 replace_job: Option<&ReplaceStreamJobPlan>,
1197 new_upstream_sink: Option<&UpstreamSinkInfo>,
1198 control_stream_manager: &ControlStreamManager,
1199 stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
1200 actor_location: &HashMap<ActorId, WorkerId>,
1201 ) -> FragmentEdgeBuildResult {
1202 let existing_fragment_ids = info
1210 .into_iter()
1211 .flat_map(|(info, _)| info.upstream_fragment_downstreams.keys())
1212 .chain(replace_job.into_iter().flat_map(|replace_job| {
1213 replace_job
1214 .upstream_fragment_downstreams
1215 .keys()
1216 .filter(|fragment_id| {
1217 info.map(|(info, _)| {
1218 !info
1219 .stream_job_fragments
1220 .fragments
1221 .contains_key(*fragment_id)
1222 })
1223 .unwrap_or(true)
1224 })
1225 .chain(replace_job.replace_upstream.keys())
1226 }))
1227 .chain(
1228 new_upstream_sink
1229 .into_iter()
1230 .map(|ctx| &ctx.new_sink_downstream.downstream_fragment_id),
1231 )
1232 .cloned();
1233 let new_fragments = info
1235 .into_iter()
1236 .flat_map(|(info, is_snapshot_backfill)| {
1237 let partial_graph_id = to_partial_graph_id(
1238 self.database_id,
1239 is_snapshot_backfill.then_some(info.streaming_job.id()),
1240 );
1241 info.stream_job_fragments
1242 .fragments
1243 .values()
1244 .map(move |fragment| (partial_graph_id, fragment))
1245 })
1246 .chain(replace_job.into_iter().flat_map(|replace_job| {
1247 replace_job
1248 .new_fragments
1249 .fragments
1250 .values()
1251 .chain(
1252 replace_job
1253 .auto_refresh_schema_sinks
1254 .as_ref()
1255 .into_iter()
1256 .flat_map(move |sinks| sinks.iter().map(|sink| &sink.new_fragment)),
1257 )
1258 .map(|fragment| {
1259 (
1260 to_partial_graph_id(self.database_id, None),
1262 fragment,
1263 )
1264 })
1265 }));
1266
1267 let mut builder = FragmentEdgeBuilder::new(
1268 existing_fragment_ids
1270 .map(|fragment_id| {
1271 (
1272 fragment_id,
1273 EdgeBuilderFragmentInfo::from_inflight(
1274 self.fragment(fragment_id),
1275 to_partial_graph_id(self.database_id, None),
1276 control_stream_manager,
1277 ),
1278 )
1279 })
1280 .chain(new_fragments.map(|(partial_graph_id, fragment)| {
1282 (
1283 fragment.fragment_id,
1284 EdgeBuilderFragmentInfo::from_fragment(
1285 fragment,
1286 stream_actors,
1287 actor_location,
1288 partial_graph_id,
1289 control_stream_manager,
1290 ),
1291 )
1292 })),
1293 );
1294 if let Some((info, _)) = info {
1295 builder.add_relations(&info.upstream_fragment_downstreams);
1296 builder.add_relations(&info.stream_job_fragments.downstreams);
1297 }
1298 if let Some(replace_job) = replace_job {
1299 builder.add_relations(&replace_job.upstream_fragment_downstreams);
1300 builder.add_relations(&replace_job.new_fragments.downstreams);
1301 }
1302 if let Some(new_upstream_sink) = new_upstream_sink {
1303 let sink_fragment_id = new_upstream_sink.sink_fragment_id;
1304 let new_sink_downstream = &new_upstream_sink.new_sink_downstream;
1305 builder.add_edge(sink_fragment_id, new_sink_downstream);
1306 }
1307 if let Some(replace_job) = replace_job {
1308 for (fragment_id, fragment_replacement) in &replace_job.replace_upstream {
1309 for (original_upstream_fragment_id, new_upstream_fragment_id) in
1310 fragment_replacement
1311 {
1312 builder.replace_upstream(
1313 *fragment_id,
1314 *original_upstream_fragment_id,
1315 *new_upstream_fragment_id,
1316 );
1317 }
1318 }
1319 }
1320 builder.build()
1321 }
1322
1323 pub(crate) fn post_apply_reschedules(
1325 &mut self,
1326 reschedules: impl IntoIterator<Item = (FragmentId, HashSet<ActorId>)>,
1327 ) {
1328 let inner = self.shared_actor_infos.clone();
1329 let mut shared_actor_writer = inner.start_writer(self.database_id);
1330 {
1331 {
1332 {
1333 for (fragment_id, to_remove) in reschedules {
1334 let job_id = self.fragment_location[&fragment_id];
1335 let info = self
1336 .jobs
1337 .get_mut(&job_id)
1338 .expect("should exist")
1339 .fragment_infos
1340 .get_mut(&fragment_id)
1341 .expect("should exist");
1342 for actor_id in to_remove {
1343 assert!(info.actors.remove(&actor_id).is_some());
1344 }
1345 shared_actor_writer.upsert([(&*info, job_id)]);
1346 }
1347 }
1348 }
1349 }
1350 shared_actor_writer.finish();
1351 }
1352
1353 pub(crate) fn post_apply_remove_fragments(
1355 &mut self,
1356 fragment_ids: impl IntoIterator<Item = FragmentId>,
1357 ) {
1358 let inner = self.shared_actor_infos.clone();
1359 let mut shared_actor_writer = inner.start_writer(self.database_id);
1360 {
1361 {
1362 {
1363 for fragment_id in fragment_ids {
1364 let job_id = self
1365 .fragment_location
1366 .remove(&fragment_id)
1367 .expect("should exist");
1368 let job = self.jobs.get_mut(&job_id).expect("should exist");
1369 let fragment = job
1370 .fragment_infos
1371 .remove(&fragment_id)
1372 .expect("should exist");
1373 shared_actor_writer.remove(&fragment);
1374 if job.fragment_infos.is_empty() {
1375 self.jobs.remove(&job_id).expect("should exist");
1376 }
1377 }
1378 }
1379 }
1380 }
1381 shared_actor_writer.finish();
1382 }
1383
1384 pub(crate) fn post_apply_remove_job(
1385 &mut self,
1386 job_id: JobId,
1387 ) -> Option<InflightStreamingJobInfo> {
1388 let job = self.jobs.remove(&job_id)?;
1389 let inner = self.shared_actor_infos.clone();
1390 let mut shared_actor_writer = inner.start_writer(self.database_id);
1391 for (fragment_id, fragment) in &job.fragment_infos {
1392 self.fragment_location
1393 .remove(fragment_id)
1394 .expect("should exist");
1395 shared_actor_writer.remove(fragment);
1396 }
1397 shared_actor_writer.finish();
1398 Some(job)
1399 }
1400}
1401
1402impl InflightFragmentInfo {
1403 pub(crate) fn actor_ids_to_collect(
1405 infos: impl IntoIterator<Item = &Self>,
1406 ) -> HashMap<WorkerId, HashSet<ActorId>> {
1407 let mut ret: HashMap<_, HashSet<_>> = HashMap::new();
1408 for (actor_id, actor) in infos.into_iter().flat_map(|info| info.actors.iter()) {
1409 assert!(
1410 ret.entry(actor.worker_id)
1411 .or_default()
1412 .insert(*actor_id as _)
1413 )
1414 }
1415 ret
1416 }
1417
1418 pub fn existing_table_ids<'a>(
1419 infos: impl IntoIterator<Item = &'a Self> + 'a,
1420 ) -> impl Iterator<Item = TableId> + 'a {
1421 infos
1422 .into_iter()
1423 .flat_map(|info| info.state_table_ids.iter().cloned())
1424 }
1425
1426 pub fn workers<'a>(
1427 infos: impl IntoIterator<Item = &'a Self> + 'a,
1428 ) -> impl Iterator<Item = WorkerId> + 'a {
1429 infos
1430 .into_iter()
1431 .flat_map(|fragment| fragment.actors.values().map(|actor| actor.worker_id))
1432 }
1433
1434 pub fn contains_worker<'a>(
1435 infos: impl IntoIterator<Item = &'a Self> + 'a,
1436 worker_id: WorkerId,
1437 ) -> bool {
1438 Self::workers(infos).any(|existing_worker_id| existing_worker_id == worker_id)
1439 }
1440}
1441
1442impl InflightDatabaseInfo {
1443 pub fn contains_worker(&self, worker_id: WorkerId) -> bool {
1444 InflightFragmentInfo::contains_worker(self.fragment_infos(), worker_id)
1445 }
1446
1447 pub fn existing_table_ids(&self) -> impl Iterator<Item = TableId> + '_ {
1448 InflightFragmentInfo::existing_table_ids(self.fragment_infos())
1449 }
1450}