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