1use std::collections::HashMap;
16use std::sync::Arc;
17
18use anyhow::Context;
19use await_tree::span;
20use futures::future::join_all;
21use itertools::Itertools;
22use risingwave_common::bail;
23use risingwave_common::catalog::{DatabaseId, Field, FragmentTypeFlag, FragmentTypeMask, TableId};
24use risingwave_common::hash::VnodeCountCompat;
25use risingwave_common::id::{JobId, SinkId};
26use risingwave_connector::source::CdcTableSnapshotSplitRaw;
27use risingwave_meta_model::prelude::Fragment as FragmentModel;
28use risingwave_meta_model::{StreamingParallelism, WorkerId, fragment, streaming_job};
29use risingwave_pb::catalog::{CreateType, PbSink, PbTable, Subscription};
30use risingwave_pb::ddl_service::streaming_job_resource_type;
31use risingwave_pb::expr::PbExprNode;
32use risingwave_pb::plan_common::{PbColumnCatalog, PbField};
33use risingwave_pb::serverless_backfill_controller::{
34 ProvisionRequest, node_group_controller_service_client,
35};
36use risingwave_rpc_client::error::TonicStatusWrapper;
37use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect};
38use thiserror_ext::AsReport;
39use tokio::sync::{Mutex, OwnedSemaphorePermit, oneshot};
40use tracing::Instrument;
41
42use super::{
43 GlobalRefreshManagerRef, ParallelismPolicy, ReschedulePolicy, ScaleControllerRef,
44 StreamFragmentGraph, UserDefinedFragmentBackfillOrder,
45};
46use crate::barrier::{
47 BarrierScheduler, BatchRefreshInfo, Command, CreateStreamingJobCommandInfo,
48 CreateStreamingJobType, ReplaceStreamJobPlan, SinceEpochInfo, SnapshotBackfillInfo,
49};
50use crate::controller::catalog::DropTableConnectorContext;
51use crate::controller::fragment::{InflightActorInfo, InflightFragmentInfo};
52use crate::error::bail_invalid_parameter;
53use crate::hummock::HummockManagerRef;
54use crate::manager::iceberg_compaction::IcebergCompactionManagerRef;
55use crate::manager::{
56 MetaSrvEnv, MetadataManager, NotificationVersion, StreamingJob, StreamingJobType,
57};
58use crate::model::{
59 ActorId, DownstreamFragmentRelation, Fragment, FragmentDownstreamRelation, FragmentId,
60 FragmentReplaceUpstream, StreamActor, StreamContext, StreamJobFragments,
61 StreamJobFragmentsToCreate, SubscriptionId,
62};
63use crate::stream::{ReplaceJobSplitPlan, SourceManagerRef};
64use crate::{MetaError, MetaResult};
65
66pub type GlobalStreamManagerRef = Arc<GlobalStreamManager>;
67
68pub(crate) async fn cleanup_dropped_streaming_jobs(
69 refresh_manager: &GlobalRefreshManagerRef,
70 hummock_manager: &HummockManagerRef,
71 metadata_manager: &MetadataManager,
72 streaming_job_ids: impl IntoIterator<Item = JobId>,
73 state_table_ids: Vec<TableId>,
74 progress_status: &str,
75) -> MetaResult<()> {
76 for job_id in streaming_job_ids {
77 refresh_manager.remove_progress_tracker(job_id.as_mv_table_id(), progress_status);
78 }
79
80 if state_table_ids.is_empty() {
81 return Ok(());
82 }
83
84 hummock_manager
85 .unregister_table_ids(state_table_ids.clone())
86 .await?;
87 metadata_manager
88 .catalog_controller
89 .complete_dropped_tables(state_table_ids)
90 .await;
91 Ok(())
92}
93
94#[derive(Default)]
95pub struct CreateStreamingJobOption {
96 }
98
99#[derive(Debug, Clone)]
100pub struct UpstreamSinkInfo {
101 pub sink_id: SinkId,
102 pub sink_fragment_id: FragmentId,
103 pub sink_output_fields: Vec<PbField>,
104 pub sink_original_target_columns: Vec<PbColumnCatalog>,
106 pub project_exprs: Vec<PbExprNode>,
107 pub new_sink_downstream: DownstreamFragmentRelation,
108}
109
110pub struct CreateStreamingJobContext {
114 pub upstream_fragment_downstreams: FragmentDownstreamRelation,
116
117 pub database_resource_group: String,
119
120 pub definition: String,
122
123 pub create_type: CreateType,
124
125 pub job_type: StreamingJobType,
126
127 pub new_upstream_sink: Option<UpstreamSinkInfo>,
129
130 pub snapshot_backfill_info: Option<SnapshotBackfillInfo>,
131 pub cross_db_snapshot_backfill_info: SnapshotBackfillInfo,
132
133 pub cdc_table_snapshot_splits: Option<Vec<CdcTableSnapshotSplitRaw>>,
134
135 pub option: CreateStreamingJobOption,
136
137 pub streaming_job: StreamingJob,
138
139 pub fragment_backfill_ordering: UserDefinedFragmentBackfillOrder,
140
141 pub locality_fragment_state_table_mapping: HashMap<FragmentId, Vec<TableId>>,
142
143 pub is_serverless_backfill: bool,
144
145 pub resource_type: streaming_job_resource_type::ResourceType,
146
147 pub streaming_job_model: streaming_job::Model,
149
150 pub replace_sink: Option<SinkId>,
152
153 pub refresh_interval_sec: Option<u64>,
155
156 pub since_timestamp_epoch: Option<u64>,
157}
158
159struct StreamingJobExecution {
160 id: JobId,
161 shutdown_tx: Option<oneshot::Sender<oneshot::Sender<bool>>>,
162 _permit: OwnedSemaphorePermit,
163}
164
165impl StreamingJobExecution {
166 fn new(
167 id: JobId,
168 shutdown_tx: oneshot::Sender<oneshot::Sender<bool>>,
169 permit: OwnedSemaphorePermit,
170 ) -> Self {
171 Self {
172 id,
173 shutdown_tx: Some(shutdown_tx),
174 _permit: permit,
175 }
176 }
177}
178
179#[derive(Default)]
180struct CreatingStreamingJobInfo {
181 streaming_jobs: Mutex<HashMap<JobId, StreamingJobExecution>>,
182}
183
184impl CreatingStreamingJobInfo {
185 async fn add_job(&self, job: StreamingJobExecution) {
186 let mut jobs = self.streaming_jobs.lock().await;
187 jobs.insert(job.id, job);
188 }
189
190 async fn delete_job(&self, job_id: JobId) {
191 let mut jobs = self.streaming_jobs.lock().await;
192 jobs.remove(&job_id);
193 }
194
195 async fn cancel_jobs(
196 &self,
197 job_ids: Vec<JobId>,
198 ) -> MetaResult<(HashMap<JobId, oneshot::Receiver<bool>>, Vec<JobId>)> {
199 let mut jobs = self.streaming_jobs.lock().await;
200 let mut receivers = HashMap::new();
201 let mut background_job_ids = vec![];
202 for job_id in job_ids {
203 if let Some(job) = jobs.get_mut(&job_id) {
204 if let Some(shutdown_tx) = job.shutdown_tx.take() {
205 let (tx, rx) = oneshot::channel();
206 match shutdown_tx.send(tx) {
207 Ok(()) => {
208 receivers.insert(job_id, rx);
209 }
210 Err(_) => {
211 return Err(anyhow::anyhow!(
212 "failed to send shutdown signal for streaming job {}: receiver dropped",
213 job_id
214 )
215 .into());
216 }
217 }
218 }
219 } else {
220 background_job_ids.push(job_id);
222 }
223 }
224
225 Ok((receivers, background_job_ids))
226 }
227}
228
229type CreatingStreamingJobInfoRef = Arc<CreatingStreamingJobInfo>;
230
231#[derive(Debug, Clone)]
232pub struct AutoRefreshSchemaSinkContext {
233 pub tmp_sink_id: SinkId,
234 pub original_sink: PbSink,
235 pub original_fragment: Fragment,
236 pub new_schema: Vec<PbColumnCatalog>,
237 pub newly_add_fields: Vec<Field>,
238 pub removed_column_names: Vec<String>,
239 pub new_fragment: Fragment,
240 pub new_log_store_table: Option<Box<PbTable>>,
241 pub ctx: StreamContext,
243}
244
245impl AutoRefreshSchemaSinkContext {
246 pub fn new_fragment_info(
247 &self,
248 stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
249 actor_location: &HashMap<ActorId, WorkerId>,
250 ) -> InflightFragmentInfo {
251 InflightFragmentInfo {
252 fragment_id: self.new_fragment.fragment_id,
253 distribution_type: self.new_fragment.distribution_type.into(),
254 fragment_type_mask: self.new_fragment.fragment_type_mask,
255 vnode_count: self.new_fragment.vnode_count(),
256 nodes: self.new_fragment.nodes.clone(),
257 actors: stream_actors
258 .get(&self.new_fragment.fragment_id)
259 .into_iter()
260 .flatten()
261 .map(|actor| {
262 (
263 actor.actor_id,
264 InflightActorInfo {
265 worker_id: actor_location[&actor.actor_id],
266 vnode_bitmap: actor.vnode_bitmap.clone(),
267 splits: vec![],
268 },
269 )
270 })
271 .collect(),
272 state_table_ids: self.new_fragment.state_table_ids.iter().copied().collect(),
273 }
274 }
275}
276
277pub struct ReplaceStreamJobContext {
281 pub old_fragments: StreamJobFragments,
283
284 pub replace_upstream: FragmentReplaceUpstream,
286
287 pub upstream_fragment_downstreams: FragmentDownstreamRelation,
289
290 pub streaming_job: StreamingJob,
291
292 pub database_resource_group: String,
294
295 pub tmp_id: JobId,
296
297 pub drop_table_connector_ctx: Option<DropTableConnectorContext>,
299
300 pub auto_refresh_schema_sinks: Option<Vec<AutoRefreshSchemaSinkContext>>,
301
302 pub streaming_job_model: streaming_job::Model,
304}
305
306pub struct GlobalStreamManager {
308 pub env: MetaSrvEnv,
309
310 pub metadata_manager: MetadataManager,
311
312 pub barrier_scheduler: BarrierScheduler,
314
315 pub hummock_manager: HummockManagerRef,
316
317 pub source_manager: SourceManagerRef,
319
320 pub refresh_manager: GlobalRefreshManagerRef,
321
322 pub iceberg_compaction_manager: IcebergCompactionManagerRef,
323
324 creating_job_info: CreatingStreamingJobInfoRef,
326
327 pub scale_controller: ScaleControllerRef,
328}
329
330impl GlobalStreamManager {
331 pub fn new(
332 env: MetaSrvEnv,
333 metadata_manager: MetadataManager,
334 barrier_scheduler: BarrierScheduler,
335 hummock_manager: HummockManagerRef,
336 source_manager: SourceManagerRef,
337 refresh_manager: GlobalRefreshManagerRef,
338 iceberg_compaction_manager: IcebergCompactionManagerRef,
339 scale_controller: ScaleControllerRef,
340 ) -> MetaResult<Self> {
341 Ok(Self {
342 env,
343 metadata_manager,
344 barrier_scheduler,
345 hummock_manager,
346 source_manager,
347 refresh_manager,
348 iceberg_compaction_manager,
349 creating_job_info: Arc::new(CreatingStreamingJobInfo::default()),
350 scale_controller,
351 })
352 }
353
354 #[await_tree::instrument]
365 pub async fn create_streaming_job(
366 self: &Arc<Self>,
367 stream_job_fragments: StreamJobFragmentsToCreate,
368 ctx: CreateStreamingJobContext,
369 permit: OwnedSemaphorePermit,
370 ) -> MetaResult<NotificationVersion> {
371 let await_tree_key = format!("Create Streaming Job Worker ({})", ctx.streaming_job.id());
372 let await_tree_span = span!(
373 "{:?}({})",
374 ctx.streaming_job.job_type(),
375 ctx.streaming_job.name()
376 );
377
378 let job_id = stream_job_fragments.stream_job_id();
379 let database_id = ctx.streaming_job.database_id();
380
381 let (cancel_tx, cancel_rx) = oneshot::channel();
382 let execution = StreamingJobExecution::new(job_id, cancel_tx, permit);
383 self.creating_job_info.add_job(execution).await;
384
385 let stream_manager = self.clone();
386 let fut = async move {
387 let create_type = ctx.create_type;
388 let streaming_job = stream_manager
389 .run_create_streaming_job_command(stream_job_fragments, ctx)
390 .await?;
391 let version = match create_type {
392 CreateType::Background => {
393 stream_manager
394 .env
395 .notification_manager_ref()
396 .current_version()
397 .await
398 }
399 CreateType::Foreground => {
400 stream_manager
401 .metadata_manager
402 .wait_streaming_job_finished(database_id, streaming_job.id() as _)
403 .await?
404 }
405 CreateType::Unspecified => unreachable!(),
406 };
407
408 tracing::debug!(?streaming_job, "stream job finish");
409 Ok(version)
410 }
411 .in_current_span();
412
413 let create_fut = (self.env.await_tree_reg())
414 .register(await_tree_key, await_tree_span)
415 .instrument(Box::pin(fut));
416
417 let result = async {
418 tokio::select! {
419 biased;
420
421 res = create_fut => res,
422 notifier = cancel_rx => {
423 let notifier = notifier.expect("sender should not be dropped");
424 tracing::debug!(id=%job_id, "cancelling streaming job");
425
426 enum CancelResult {
427 Completed(MetaResult<NotificationVersion>),
428 Failed(MetaError),
429 Cancelled,
430 }
431
432 let cancel_res = if let Ok(job_fragments) =
433 self.metadata_manager.get_job_fragments_by_id(job_id).await
434 {
435 if self
437 .barrier_scheduler
438 .try_cancel_scheduled_create(database_id, job_id)
439 {
440 tracing::debug!(
441 id=%job_id,
442 "cancelling streaming job in buffer queue."
443 );
444 CancelResult::Cancelled
445 } else if !job_fragments.is_created() {
446 tracing::debug!(
447 id=%job_id,
448 "cancelling streaming job by issue cancel command."
449 );
450
451 let cancel_result: MetaResult<()> = async {
452 let Some((cancel_command, cleanup_state_table_ids)) = self
453 .metadata_manager
454 .catalog_controller
455 .build_cancel_command(job_id)
456 .await?
457 else {
458 return Ok(());
459 };
460 let abort_result = self.metadata_manager.catalog_controller
461 .try_abort_creating_streaming_job(job_id, true)
462 .await?;
463 self.iceberg_compaction_manager
464 .clear_maintenance_for_aborted_job(&abort_result);
465
466 self.barrier_scheduler
467 .run_command(database_id, cancel_command)
468 .await?;
469 cleanup_dropped_streaming_jobs(
470 &self.refresh_manager,
471 &self.hummock_manager,
472 &self.metadata_manager,
473 [job_id],
474 cleanup_state_table_ids,
475 "cancel_streaming_job",
476 )
477 .await?;
478 Ok(())
479 }
480 .await;
481
482 match cancel_result {
483 Ok(()) => CancelResult::Cancelled,
484 Err(err) => {
485 tracing::warn!(
486 error = ?err.as_report(),
487 id = %job_id,
488 "failed to run cancel command for creating streaming job"
489 );
490 CancelResult::Failed(err)
491 }
492 }
493 } else {
494 CancelResult::Completed(
496 self.metadata_manager
497 .wait_streaming_job_finished(database_id, job_id)
498 .await,
499 )
500 }
501 } else {
502 CancelResult::Cancelled
503 };
504
505 let (cancelled, result) = match cancel_res {
506 CancelResult::Completed(result) => (false, result),
507 CancelResult::Failed(err) => (false, Err(err)),
508 CancelResult::Cancelled => (true, Err(MetaError::cancelled("create"))),
509 };
510
511 let _ = notifier
512 .send(cancelled)
513 .inspect_err(|err| tracing::warn!("failed to notify cancellation result: {err}"));
514
515 result
516 }
517 }
518 }
519 .await;
520
521 tracing::debug!("cleaning creating job info: {}", job_id);
522 self.creating_job_info.delete_job(job_id).await;
523 result
524 }
525
526 async fn provision_serverless_backfill_resource_group(&self) -> MetaResult<String> {
527 let sbc_addr = &self.env.opts.serverless_backfill_controller_addr;
528 if sbc_addr.is_empty() {
529 bail_invalid_parameter!(
530 "Serverless Backfill is disabled. Use RisingWave cloud at https://cloud.risingwave.com/auth/signup to try this feature"
531 );
532 }
533
534 let request = tonic::Request::new(ProvisionRequest {});
535 let mut client =
536 node_group_controller_service_client::NodeGroupControllerServiceClient::connect(
537 sbc_addr.clone(),
538 )
539 .await
540 .with_context(|| {
541 format!(
542 "unable to reach serverless backfill controller at addr {}",
543 sbc_addr
544 )
545 })?;
546
547 match client.provision(request).await {
548 Ok(resp) => Ok(resp.into_inner().resource_group),
549 Err(e) => Err(anyhow::Error::new(TonicStatusWrapper::new(e))
550 .context("serverless backfill controller returned error")
551 .into()),
552 }
553 }
554
555 async fn finalize_create_streaming_job_resource_group(
556 &self,
557 resource_type: &streaming_job_resource_type::ResourceType,
558 streaming_job_model: &mut streaming_job::Model,
559 ) -> MetaResult<()> {
560 if !matches!(
561 resource_type,
562 streaming_job_resource_type::ResourceType::ServerlessBackfill(true)
563 ) {
564 return Ok(());
565 }
566
567 let group = self.provision_serverless_backfill_resource_group().await?;
568 tracing::info!(
569 resource_group = group,
570 "provisioning serverless backfill resource group"
571 );
572
573 self.metadata_manager
574 .catalog_controller
575 .update_streaming_job_resource_group(streaming_job_model.job_id, group.clone())
576 .await?;
577 streaming_job_model.specific_resource_group = Some(group);
578
579 Ok(())
580 }
581
582 #[await_tree::instrument]
585 async fn run_create_streaming_job_command(
586 &self,
587 stream_job_fragments: StreamJobFragmentsToCreate,
588 CreateStreamingJobContext {
589 streaming_job,
590 upstream_fragment_downstreams,
591 database_resource_group,
592 definition,
593 create_type,
594 job_type,
595 new_upstream_sink,
596 snapshot_backfill_info,
597 cross_db_snapshot_backfill_info,
598 fragment_backfill_ordering,
599 locality_fragment_state_table_mapping,
600 cdc_table_snapshot_splits,
601 is_serverless_backfill,
602 resource_type,
603 mut streaming_job_model,
604 replace_sink,
605 refresh_interval_sec,
606 since_timestamp_epoch,
607 ..
608 }: CreateStreamingJobContext,
609 ) -> MetaResult<StreamingJob> {
610 tracing::debug!(
611 table_id = %stream_job_fragments.stream_job_id(),
612 "built actors finished"
613 );
614
615 let init_split_assignment = self
620 .source_manager
621 .discover_splits(&stream_job_fragments)
622 .await?;
623
624 let fragment_backfill_ordering =
625 StreamFragmentGraph::extend_fragment_backfill_ordering_with_locality_backfill(
626 fragment_backfill_ordering,
627 &stream_job_fragments.downstreams,
628 || {
629 stream_job_fragments
630 .fragments
631 .iter()
632 .map(|(fragment_id, fragment)| {
633 (*fragment_id, fragment.fragment_type_mask, &fragment.nodes)
634 })
635 },
636 );
637
638 self.finalize_create_streaming_job_resource_group(&resource_type, &mut streaming_job_model)
639 .await?;
640
641 let info = CreateStreamingJobCommandInfo {
642 stream_job_fragments,
643 upstream_fragment_downstreams,
644 init_split_assignment,
645 definition: definition.clone(),
646 streaming_job: streaming_job.clone(),
647 job_type,
648 create_type,
649 database_resource_group,
650 fragment_backfill_ordering,
651 cdc_table_snapshot_splits,
652 locality_fragment_state_table_mapping,
653 is_serverless: is_serverless_backfill,
654 streaming_job_model,
655 replace_sink,
656 refresh_interval_sec,
657 };
658
659 let job_type = if let Some(refresh_interval_sec) = refresh_interval_sec {
660 if since_timestamp_epoch.is_some() {
661 bail!("since_timestamp should not be specified when no snapshot backfill");
662 }
663 let snapshot_backfill_info = snapshot_backfill_info.ok_or_else(|| {
664 anyhow::anyhow!(
665 "batch refresh materialized view must have snapshot backfill upstream"
666 )
667 })?;
668 for fragment in info.stream_job_fragments.inner.fragments.values() {
671 let mask = fragment.fragment_type_mask;
672 if mask.contains(FragmentTypeFlag::Source)
673 || mask.contains(FragmentTypeFlag::SourceScan)
674 {
675 bail!(
676 "batch refresh materialized views must not depend on sources directly; \
677 fragment {} has source/source-backfill nodes",
678 fragment.fragment_id
679 );
680 }
681 }
682 tracing::debug!(
683 ?snapshot_backfill_info,
684 refresh_interval_sec,
685 "sending Command::CreateBatchRefreshStreamingJob"
686 );
687 CreateStreamingJobType::BatchRefresh(BatchRefreshInfo {
688 snapshot_backfill_info,
689 refresh_interval_sec,
690 })
691 } else if let Some(snapshot_backfill_info) = snapshot_backfill_info {
692 tracing::debug!(
693 ?snapshot_backfill_info,
694 "sending Command::CreateSnapshotBackfillStreamingJob"
695 );
696 CreateStreamingJobType::SnapshotBackfill {
697 snapshot_backfill_info,
698 since_epoch: since_timestamp_epoch.map(|provided_since_epoch| SinceEpochInfo {
699 provided_since_epoch,
700 resolved: None,
701 }),
702 }
703 } else {
704 if since_timestamp_epoch.is_some() {
705 bail!("since_timestamp should not be specified when no snapshot backfill");
706 }
707 tracing::debug!("sending Command::CreateStreamingJob");
708 if let Some(new_upstream_sink) = new_upstream_sink {
709 CreateStreamingJobType::SinkIntoTable(new_upstream_sink)
710 } else {
711 CreateStreamingJobType::Normal
712 }
713 };
714
715 let command = Command::CreateStreamingJob {
716 info,
717 job_type,
718 cross_db_snapshot_backfill_info,
719 };
720
721 self.barrier_scheduler
722 .run_command(streaming_job.database_id(), command)
723 .await?;
724
725 tracing::debug!(?streaming_job, "first barrier collected for stream job");
726
727 Ok(streaming_job)
728 }
729
730 pub async fn replace_stream_job(
732 &self,
733 new_fragments: StreamJobFragmentsToCreate,
734 ReplaceStreamJobContext {
735 old_fragments,
736 replace_upstream,
737 upstream_fragment_downstreams,
738 tmp_id,
739 streaming_job,
740 drop_table_connector_ctx,
741 auto_refresh_schema_sinks,
742 streaming_job_model,
743 database_resource_group,
744 }: ReplaceStreamJobContext,
745 ) -> MetaResult<()> {
746 let split_plan = if streaming_job.is_source() {
751 match self
752 .source_manager
753 .discover_splits_for_replace_source(&new_fragments, &replace_upstream)
754 .await?
755 {
756 Some(discovered) => ReplaceJobSplitPlan::Discovered(discovered),
757 None => ReplaceJobSplitPlan::AlignFromPrevious,
758 }
759 } else {
760 let discovered = self.source_manager.discover_splits(&new_fragments).await?;
761 ReplaceJobSplitPlan::Discovered(discovered)
762 };
763 tracing::info!("replace_stream_job - split plan: {:?}", split_plan);
764
765 self.barrier_scheduler
766 .run_command(
767 streaming_job.database_id(),
768 Command::ReplaceStreamJob(ReplaceStreamJobPlan {
769 old_fragments,
770 new_fragments,
771 database_resource_group,
772 replace_upstream,
773 upstream_fragment_downstreams,
774 split_plan,
775 streaming_job,
776 streaming_job_model,
777 tmp_id,
778 to_drop_state_table_ids: {
779 if let Some(drop_table_connector_ctx) = &drop_table_connector_ctx {
780 vec![drop_table_connector_ctx.to_remove_state_table_id]
781 } else {
782 Vec::new()
783 }
784 },
785 auto_refresh_schema_sinks,
786 }),
787 )
788 .await?;
789
790 Ok(())
791 }
792
793 pub async fn drop_streaming_jobs(
797 &self,
798 database_id: DatabaseId,
799 streaming_job_ids: Vec<JobId>,
800 state_table_ids: Vec<TableId>,
801 dropped_sink_fragment_by_targets: HashMap<FragmentId, Vec<FragmentId>>,
802 ) {
803 if !streaming_job_ids.is_empty() || !state_table_ids.is_empty() {
804 let cleanup_streaming_job_ids = streaming_job_ids.clone();
805 let cleanup_state_table_ids = state_table_ids.clone();
806 let run_result = self
807 .barrier_scheduler
808 .run_command(
809 database_id,
810 Command::DropStreamingJobs {
811 streaming_job_ids: streaming_job_ids.into_iter().collect(),
812 unregistered_state_table_ids: state_table_ids.iter().copied().collect(),
813 dropped_sink_fragment_by_targets,
814 },
815 )
816 .await;
817 let result = match run_result {
818 Ok(()) => {
819 cleanup_dropped_streaming_jobs(
820 &self.refresh_manager,
821 &self.hummock_manager,
822 &self.metadata_manager,
823 cleanup_streaming_job_ids,
824 cleanup_state_table_ids,
825 "drop_streaming_jobs",
826 )
827 .await
828 }
829 Err(err) => Err(err),
830 };
831 let _ = result.inspect_err(|err| {
832 tracing::error!(error = ?err.as_report(), "failed to run drop command");
833 });
834 }
835 }
836
837 pub async fn cancel_streaming_jobs(&self, job_ids: Vec<JobId>) -> MetaResult<Vec<JobId>> {
843 if job_ids.is_empty() {
844 return Ok(vec![]);
845 }
846
847 let _reschedule_job_lock = self.reschedule_lock_read_guard().await;
848 let (receivers, background_job_ids) = self.creating_job_info.cancel_jobs(job_ids).await?;
849
850 let futures = receivers.into_iter().map(|(id, receiver)| async move {
851 if let Ok(cancelled) = receiver.await
852 && cancelled
853 {
854 tracing::info!("canceled streaming job {id}");
855 Ok(id)
856 } else {
857 Err(MetaError::from(anyhow::anyhow!(
858 "failed to cancel streaming job {id}"
859 )))
860 }
861 });
862 let mut cancelled_ids = join_all(futures)
863 .await
864 .into_iter()
865 .collect::<MetaResult<Vec<_>>>()?;
866
867 let futures = background_job_ids.into_iter().map(|id| async move {
870 let Some((cancel_command, cleanup_state_table_ids)) = self
871 .metadata_manager
872 .catalog_controller
873 .build_cancel_command(id)
874 .await?
875 else {
876 return Ok(None);
877 };
878
879 let abort_result = self
880 .metadata_manager
881 .catalog_controller
882 .try_abort_creating_streaming_job(id, true)
883 .await?;
884 self.iceberg_compaction_manager
885 .clear_maintenance_for_aborted_job(&abort_result);
886
887 if let Some(database_id) = abort_result.database_id {
888 self.barrier_scheduler
889 .run_command(database_id, cancel_command)
890 .await?;
891 cleanup_dropped_streaming_jobs(
892 &self.refresh_manager,
893 &self.hummock_manager,
894 &self.metadata_manager,
895 [id],
896 cleanup_state_table_ids,
897 "cancel_streaming_job",
898 )
899 .await?;
900 }
901
902 tracing::info!(?id, "cancelled background streaming job");
903 Ok(Some(id))
904 });
905 let cancelled_recovered_ids = join_all(futures)
906 .await
907 .into_iter()
908 .collect::<MetaResult<Vec<_>>>()?;
909
910 cancelled_ids.extend(cancelled_recovered_ids.into_iter().flatten());
911 Ok(cancelled_ids)
912 }
913
914 pub(crate) async fn reschedule_streaming_job(
915 &self,
916 job_id: JobId,
917 policy: ReschedulePolicy,
918 deferred: bool,
919 ) -> MetaResult<()> {
920 let _reschedule_job_lock = self.reschedule_lock_write_guard().await;
921
922 let background_jobs = self
923 .metadata_manager
924 .list_background_creating_jobs()
925 .await?;
926
927 if !background_jobs.is_empty() {
928 let blocked_jobs = self
929 .metadata_manager
930 .collect_reschedule_blocked_jobs_for_creating_jobs(&background_jobs, !deferred)
931 .await?;
932
933 if blocked_jobs.contains(&job_id) {
934 bail!(
935 "Cannot alter the job {} because it is blocked by creating unreschedulable backfill jobs",
936 job_id,
937 );
938 }
939 }
940
941 let commands = self
942 .scale_controller
943 .reschedule_inplace(HashMap::from([(job_id, policy)]))
944 .await?;
945
946 if !deferred {
947 let _source_pause_guard = self.source_manager.pause_tick().await;
948
949 for (database_id, command) in commands {
950 self.barrier_scheduler
951 .run_command(database_id, command)
952 .await?;
953 }
954 }
955
956 Ok(())
957 }
958
959 pub(crate) async fn reschedule_streaming_job_backfill_parallelism(
960 &self,
961 job_id: JobId,
962 parallelism: Option<ParallelismPolicy>,
963 deferred: bool,
964 ) -> MetaResult<()> {
965 let _reschedule_job_lock = self.reschedule_lock_write_guard().await;
966
967 let background_jobs = self
968 .metadata_manager
969 .list_background_creating_jobs()
970 .await?;
971
972 if !background_jobs.is_empty() {
973 let unreschedulable = self
974 .metadata_manager
975 .collect_unreschedulable_backfill_jobs(&background_jobs, !deferred)
976 .await?;
977
978 if unreschedulable.contains(&job_id) {
979 bail!(
980 "Cannot alter the job {} because it is a non-reschedulable background backfill job",
981 job_id,
982 );
983 }
984 }
985
986 let commands = self
987 .scale_controller
988 .reschedule_backfill_parallelism_inplace(HashMap::from([(job_id, parallelism)]))
989 .await?;
990
991 if !deferred {
992 let _source_pause_guard = self.source_manager.pause_tick().await;
993
994 for (database_id, command) in commands {
995 self.barrier_scheduler
996 .run_command(database_id, command)
997 .await?;
998 }
999 }
1000
1001 Ok(())
1002 }
1003
1004 pub(crate) async fn reschedule_cdc_table_backfill(
1006 &self,
1007 job_id: JobId,
1008 target: ReschedulePolicy,
1009 ) -> MetaResult<()> {
1010 let _reschedule_job_lock = self.reschedule_lock_write_guard().await;
1011
1012 let parallelism_policy = match target {
1013 ReschedulePolicy::Parallelism(policy)
1014 if matches!(policy.parallelism, StreamingParallelism::Fixed(_)) =>
1015 {
1016 policy
1017 }
1018 _ => bail_invalid_parameter!(
1019 "CDC backfill reschedule only supports fixed parallelism targets"
1020 ),
1021 };
1022
1023 let cdc_fragment_id = {
1024 let inner = self.metadata_manager.catalog_controller.inner.read().await;
1025 let fragments: Vec<(risingwave_meta_model::FragmentId, i32)> = FragmentModel::find()
1026 .select_only()
1027 .columns([
1028 fragment::Column::FragmentId,
1029 fragment::Column::FragmentTypeMask,
1030 ])
1031 .filter(fragment::Column::JobId.eq(job_id))
1032 .into_tuple()
1033 .all(&inner.db)
1034 .await?;
1035
1036 let cdc_fragments = fragments
1037 .into_iter()
1038 .filter_map(|(fragment_id, mask)| {
1039 FragmentTypeMask::from(mask)
1040 .contains(FragmentTypeFlag::StreamCdcScan)
1041 .then_some(fragment_id)
1042 })
1043 .collect_vec();
1044
1045 match cdc_fragments.len() {
1046 0 => bail_invalid_parameter!("no StreamCdcScan fragments found for job {}", job_id),
1047 1 => cdc_fragments[0],
1048 _ => bail_invalid_parameter!(
1049 "multiple StreamCdcScan fragments found for job {}; expected exactly one",
1050 job_id
1051 ),
1052 }
1053 };
1054
1055 let fragment_policy = HashMap::from([(
1056 cdc_fragment_id,
1057 Some(parallelism_policy.parallelism.clone()),
1058 )]);
1059
1060 let commands = self
1061 .scale_controller
1062 .reschedule_fragment_inplace(fragment_policy)
1063 .await?;
1064
1065 let _source_pause_guard = self.source_manager.pause_tick().await;
1066
1067 for (database_id, command) in commands {
1068 self.barrier_scheduler
1069 .run_command(database_id, command)
1070 .await?;
1071 }
1072
1073 Ok(())
1074 }
1075
1076 pub(crate) async fn reschedule_fragments(
1077 &self,
1078 fragment_targets: HashMap<FragmentId, Option<StreamingParallelism>>,
1079 ) -> MetaResult<()> {
1080 if fragment_targets.is_empty() {
1081 return Ok(());
1082 }
1083
1084 let _reschedule_job_lock = self.reschedule_lock_write_guard().await;
1085
1086 let fragment_policy = fragment_targets
1087 .into_iter()
1088 .map(|(fragment_id, parallelism)| (fragment_id as _, parallelism))
1089 .collect();
1090
1091 let commands = self
1092 .scale_controller
1093 .reschedule_fragment_inplace(fragment_policy)
1094 .await?;
1095
1096 let _source_pause_guard = self.source_manager.pause_tick().await;
1097
1098 for (database_id, command) in commands {
1099 self.barrier_scheduler
1100 .run_command(database_id, command)
1101 .await?;
1102 }
1103
1104 Ok(())
1105 }
1106
1107 pub async fn create_subscription(
1109 self: &Arc<Self>,
1110 subscription: &Subscription,
1111 ) -> MetaResult<()> {
1112 let command = Command::CreateSubscription {
1113 subscription_id: subscription.id,
1114 upstream_mv_table_id: subscription.dependent_table_id,
1115 retention_second: subscription.retention_seconds,
1116 };
1117
1118 tracing::debug!("sending Command::CreateSubscription");
1119 self.barrier_scheduler
1120 .run_command(subscription.database_id, command)
1121 .await?;
1122 Ok(())
1123 }
1124
1125 pub async fn drop_subscription(
1127 self: &Arc<Self>,
1128 database_id: DatabaseId,
1129 subscription_id: SubscriptionId,
1130 table_id: TableId,
1131 ) {
1132 let command = Command::DropSubscription {
1133 subscription_id,
1134 upstream_mv_table_id: table_id,
1135 };
1136
1137 tracing::debug!("sending Command::DropSubscriptions");
1138 let _ = self
1139 .barrier_scheduler
1140 .run_command(database_id, command)
1141 .await
1142 .inspect_err(|err| {
1143 tracing::error!(error = ?err.as_report(), "failed to run drop command");
1144 });
1145 }
1146
1147 pub async fn alter_subscription_retention(
1148 self: &Arc<Self>,
1149 database_id: DatabaseId,
1150 subscription_id: SubscriptionId,
1151 table_id: TableId,
1152 retention_second: u64,
1153 ) -> MetaResult<()> {
1154 let command = Command::AlterSubscriptionRetention {
1155 subscription_id,
1156 upstream_mv_table_id: table_id,
1157 retention_second,
1158 };
1159
1160 tracing::debug!("sending Command::AlterSubscriptionRetention");
1161 self.barrier_scheduler
1162 .run_command(database_id, command)
1163 .await?;
1164 Ok(())
1165 }
1166}