Skip to main content

risingwave_meta/barrier/
worker.rs

1// Copyright 2024 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::hash_map::Entry;
16use std::collections::{HashMap, HashSet};
17use std::mem::{replace, take};
18use std::pin::pin;
19use std::sync::Arc;
20use std::time::Duration;
21
22use anyhow::anyhow;
23use arc_swap::ArcSwap;
24use futures::{TryFutureExt, pin_mut};
25use itertools::Itertools;
26use risingwave_common::catalog::DatabaseId;
27use risingwave_common::id::JobId;
28use risingwave_common::system_param::PAUSE_ON_NEXT_BOOTSTRAP_KEY;
29use risingwave_common::system_param::reader::SystemParamsRead;
30use risingwave_meta_model::WorkerId;
31use risingwave_pb::common::WorkerNode;
32use risingwave_pb::meta::Recovery;
33use risingwave_pb::meta::subscribe_response::{Info, Operation};
34use thiserror_ext::AsReport;
35use tokio::select;
36use tokio::sync::mpsc;
37use tokio::sync::oneshot::{Receiver, Sender};
38use tokio::task::JoinHandle;
39use tonic::Status;
40use tracing::{Instrument, debug, error, info, warn};
41
42use crate::barrier::checkpoint::{CheckpointControl, CheckpointControlEvent};
43use crate::barrier::complete_task::{BarrierCompleteOutput, CompletingTask};
44use crate::barrier::context::recovery::{RenderedDatabaseRuntimeInfo, render_runtime_info};
45use crate::barrier::context::{GlobalBarrierWorkerContext, GlobalBarrierWorkerContextImpl};
46use crate::barrier::info::InflightDatabaseInfo;
47use crate::barrier::rpc::{
48    DatabaseInitialBarrierCollector, database_partial_graphs, from_partial_graph_id,
49    merge_node_rpc_errors,
50};
51use crate::barrier::schedule::{MarkReadyOptions, PeriodicBarriers};
52use crate::barrier::{
53    BarrierManagerRequest, BarrierManagerStatus, BarrierWorkerRuntimeInfoSnapshot, Command,
54    CreateStreamingJobType, RecoveryReason, RescheduleContext, UpdateDatabaseBarrierRequest,
55    schedule,
56};
57use crate::controller::scale::{materialize_actor_assignments, preview_actor_assignments};
58use crate::error::MetaErrorInner;
59use crate::hummock::HummockManagerRef;
60use crate::manager::iceberg_compaction::IcebergCompactionManagerRef;
61use crate::manager::iceberg_pk_index_sink::IcebergPkIndexSinkManager;
62use crate::manager::sink_coordination::SinkCoordinatorManager;
63use crate::manager::{
64    ActiveStreamingWorkerChange, ActiveStreamingWorkerNodes, LocalNotification, MetaSrvEnv,
65    MetadataManager,
66};
67use crate::rpc::metrics::GLOBAL_META_METRICS;
68use crate::stream::{
69    GlobalRefreshManagerRef, ScaleControllerRef, SourceManagerRef, build_reschedule_commands,
70    rendered_layout_matches_current,
71};
72use crate::{MetaError, MetaResult};
73
74/// [`crate::barrier::worker::GlobalBarrierWorker`] sends barriers to all registered compute nodes and
75/// collect them, with monotonic increasing epoch numbers. On compute nodes, `LocalBarrierManager`
76/// in `risingwave_stream` crate will serve these requests and dispatch them to source actors.
77///
78/// Configuration change in our system is achieved by the mutation in the barrier. Thus,
79/// [`crate::barrier::worker::GlobalBarrierWorker`] provides a set of interfaces like a state machine,
80/// accepting [`crate::barrier::command::Command`] that carries info to build `Mutation`. To keep the consistency between
81/// barrier manager and meta store, some actions like "drop materialized view" or "create mv on mv"
82/// must be done in barrier manager transactional using [`crate::barrier::command::Command`].
83pub(super) struct GlobalBarrierWorker<C> {
84    /// Enable recovery or not when failover.
85    enable_recovery: bool,
86
87    /// The queue of scheduled barriers.
88    periodic_barriers: PeriodicBarriers,
89
90    /// Whether per database failure isolation is enabled in system parameters.
91    system_enable_per_database_isolation: bool,
92
93    pub(super) context: Arc<C>,
94
95    env: MetaSrvEnv,
96
97    checkpoint_control: CheckpointControl,
98
99    /// Command that has been collected but is still completing.
100    /// The join handle of the completing future is stored.
101    completing_task: CompletingTask,
102
103    request_rx: mpsc::UnboundedReceiver<BarrierManagerRequest>,
104
105    active_streaming_nodes: ActiveStreamingWorkerNodes,
106
107    partial_graph_manager: PartialGraphManager,
108}
109
110#[cfg(test)]
111mod tests {
112    use std::collections::HashMap;
113
114    use tokio::sync::oneshot;
115
116    use super::*;
117    use crate::barrier::RescheduleContext;
118    use crate::barrier::notifier::Notifier;
119
120    #[tokio::test]
121    async fn test_reschedule_intent_without_workers_notifies_start_failed() {
122        let env = MetaSrvEnv::for_test().await;
123        let database_id = DatabaseId::new(1);
124        let database_info =
125            InflightDatabaseInfo::empty(database_id, env.shared_actor_infos().clone());
126        let (started_tx, started_rx) = oneshot::channel();
127        let (_collected_tx, _collected_rx) = oneshot::channel();
128
129        let notifier = Notifier {
130            started: Some(started_tx),
131            collected: Some(_collected_tx),
132        };
133
134        let new_barrier = schedule::NewBarrier {
135            database_id,
136            command: Some((
137                Command::RescheduleIntent {
138                    context: RescheduleContext::empty(),
139                    reschedule_plan: None,
140                },
141                vec![notifier],
142            )),
143            span: tracing::Span::none(),
144            checkpoint: false,
145        };
146
147        let result =
148            resolve_reschedule_intent(env, HashMap::new(), Some(&database_info), new_barrier);
149
150        assert!(matches!(result, Ok(None)));
151        let started = started_rx.await.expect("started notifier dropped");
152        assert!(started.is_err());
153    }
154}
155
156impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
157    pub(super) async fn new_inner(
158        env: MetaSrvEnv,
159        request_rx: mpsc::UnboundedReceiver<BarrierManagerRequest>,
160        context: Arc<C>,
161    ) -> Self {
162        let enable_recovery = env.opts.enable_recovery;
163
164        let active_streaming_nodes = ActiveStreamingWorkerNodes::uninitialized();
165
166        let partial_graph_manager = PartialGraphManager::uninitialized(env.clone());
167
168        let reader = env.system_params_reader().await;
169        let system_enable_per_database_isolation = reader.per_database_isolation();
170        // Load config will be performed in bootstrap phase.
171        let periodic_barriers = PeriodicBarriers::default();
172
173        let checkpoint_control = CheckpointControl::new(env.clone());
174        Self {
175            enable_recovery,
176            periodic_barriers,
177            system_enable_per_database_isolation,
178            context,
179            env,
180            checkpoint_control,
181            completing_task: CompletingTask::None,
182            request_rx,
183            active_streaming_nodes,
184            partial_graph_manager,
185        }
186    }
187}
188
189fn resolve_reschedule_intent(
190    env: MetaSrvEnv,
191    worker_nodes: HashMap<WorkerId, WorkerNode>,
192    database_info: Option<&InflightDatabaseInfo>,
193    mut new_barrier: schedule::NewBarrier,
194) -> MetaResult<Option<schedule::NewBarrier>> {
195    let Some((command, notifiers)) = new_barrier.command.take() else {
196        return Ok(Some(new_barrier));
197    };
198
199    match command {
200        Command::RescheduleIntent {
201            context,
202            reschedule_plan,
203        } => {
204            if let Some(reschedule_plan) = reschedule_plan {
205                new_barrier.command = Some((
206                    Command::RescheduleIntent {
207                        context,
208                        reschedule_plan: Some(reschedule_plan),
209                    },
210                    notifiers,
211                ));
212                return Ok(Some(new_barrier));
213            }
214            let span = tracing::info_span!(
215                "resolve_reschedule_intent",
216                database_id = %new_barrier.database_id
217            );
218            let reschedule_plan = {
219                let _guard = span.enter();
220                build_reschedule_from_context(
221                    &env,
222                    worker_nodes,
223                    new_barrier.database_id,
224                    context,
225                    database_info.ok_or_else(|| {
226                        anyhow!(
227                            "database {} not found when resolving reschedule intent",
228                            new_barrier.database_id
229                        )
230                    })?,
231                )
232            };
233            match reschedule_plan {
234                Ok(Some(reschedule_plan)) => {
235                    new_barrier.command = Some((
236                        Command::RescheduleIntent {
237                            context: RescheduleContext::empty(),
238                            reschedule_plan: Some(reschedule_plan),
239                        },
240                        notifiers,
241                    ));
242                    Ok(Some(new_barrier))
243                }
244                Ok(None) => {
245                    // No-op intent: notify to unblock callers even though no barrier is injected.
246                    for mut notifier in notifiers {
247                        notifier.notify_started();
248                        notifier.notify_collected();
249                    }
250                    Ok(None)
251                }
252                Err(err) => {
253                    for notifier in notifiers {
254                        notifier.notify_start_failed(err.clone());
255                    }
256                    Ok(None)
257                }
258            }
259        }
260        _ => {
261            new_barrier.command = Some((command, notifiers));
262            Ok(Some(new_barrier))
263        }
264    }
265}
266
267fn build_reschedule_from_context(
268    env: &MetaSrvEnv,
269    worker_nodes: HashMap<WorkerId, WorkerNode>,
270    database_id: DatabaseId,
271    context: RescheduleContext,
272    database_info: &InflightDatabaseInfo,
273) -> MetaResult<Option<crate::barrier::ReschedulePlan>> {
274    if worker_nodes.is_empty() {
275        return Err(anyhow!("no active streaming workers for reschedule").into());
276    }
277
278    if context.is_empty() {
279        return Ok(None);
280    }
281
282    // Barrier worker resolves this intent against a stable in-flight snapshot.
283    // Reuse the same fragment view for preview comparison and command building.
284    let all_prev_fragments = database_info
285        .fragment_infos()
286        .map(|fragment| (fragment.fragment_id, fragment))
287        .collect();
288
289    let previewed = preview_actor_assignments(&worker_nodes, &context.loaded)?;
290
291    if rendered_layout_matches_current(&previewed.fragments, &all_prev_fragments)? {
292        return Ok(None);
293    }
294
295    let actor_id_counter = env.actor_id_generator();
296    // Materialization only replaces preview actor ids with real ids. Worker
297    // placement, vnode ownership, and split assignment remain unchanged.
298    let rendered = materialize_actor_assignments(actor_id_counter, previewed);
299    let mut commands = build_reschedule_commands(rendered.fragments, context, all_prev_fragments)?;
300    Ok(commands.remove(&database_id))
301}
302
303impl GlobalBarrierWorker<GlobalBarrierWorkerContextImpl> {
304    /// Create a new [`crate::barrier::worker::GlobalBarrierWorker`].
305    #[expect(clippy::too_many_arguments)]
306    pub async fn new(
307        scheduled_barriers: schedule::ScheduledBarriers,
308        env: MetaSrvEnv,
309        metadata_manager: MetadataManager,
310        hummock_manager: HummockManagerRef,
311        source_manager: SourceManagerRef,
312        sink_manager: SinkCoordinatorManager,
313        iceberg_pk_index_sink_manager: IcebergPkIndexSinkManager,
314        iceberg_compaction_manager: IcebergCompactionManagerRef,
315        scale_controller: ScaleControllerRef,
316        request_rx: mpsc::UnboundedReceiver<BarrierManagerRequest>,
317        barrier_scheduler: schedule::BarrierScheduler,
318        refresh_manager: GlobalRefreshManagerRef,
319    ) -> Self {
320        let status = Arc::new(ArcSwap::new(Arc::new(BarrierManagerStatus::Starting)));
321
322        let context = Arc::new(GlobalBarrierWorkerContextImpl::new(
323            scheduled_barriers,
324            status,
325            metadata_manager,
326            hummock_manager,
327            source_manager,
328            scale_controller,
329            env.clone(),
330            barrier_scheduler,
331            refresh_manager,
332            sink_manager,
333            iceberg_pk_index_sink_manager,
334            iceberg_compaction_manager,
335        ));
336
337        Self::new_inner(env, request_rx, context).await
338    }
339
340    pub fn start(self) -> (JoinHandle<()>, Sender<()>) {
341        let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
342        let fut = (self.env.await_tree_reg())
343            .register_derived_root("Global Barrier Worker")
344            .instrument(self.run(shutdown_rx));
345        let join_handle = tokio::spawn(fut);
346
347        (join_handle, shutdown_tx)
348    }
349
350    /// Check whether we should pause on bootstrap from the system parameter and reset it.
351    async fn take_pause_on_bootstrap(&mut self) -> MetaResult<bool> {
352        let paused = self
353            .env
354            .system_params_reader()
355            .await
356            .pause_on_next_bootstrap()
357            || self.env.opts.pause_on_next_bootstrap_offline;
358
359        if paused {
360            warn!(
361                "The cluster will bootstrap with all data sources paused as specified by the system parameter `{}`. \
362                 It will now be reset to `false`. \
363                 To resume the data sources, either restart the cluster again or use `risectl meta resume`.",
364                PAUSE_ON_NEXT_BOOTSTRAP_KEY
365            );
366            self.env
367                .system_params_manager_impl_ref()
368                .set_param(PAUSE_ON_NEXT_BOOTSTRAP_KEY, Some("false".to_owned()))
369                .await?;
370        }
371        Ok(paused)
372    }
373
374    /// Start an infinite loop to take scheduled barriers and send them.
375    async fn run(mut self, shutdown_rx: Receiver<()>) {
376        tracing::info!(
377            "Starting barrier manager with: enable_recovery={}, in_flight_barrier_nums={}",
378            self.enable_recovery,
379            self.checkpoint_control.in_flight_barrier_nums,
380        );
381
382        if !self.enable_recovery {
383            let job_exist = self
384                .context
385                .metadata_manager
386                .catalog_controller
387                .has_any_streaming_jobs()
388                .await
389                .unwrap();
390            if job_exist {
391                panic!(
392                    "Some streaming jobs already exist in meta, please start with recovery enabled \
393                or clean up the metadata using `./risedev clean-data`"
394                );
395            }
396        }
397
398        {
399            // Bootstrap recovery. Here we simply trigger a recovery process to achieve the
400            // consistency.
401            // Even if there's no actor to recover, we still go through the recovery process to
402            // inject the first `Initial` barrier.
403            let span = tracing::info_span!("bootstrap_recovery");
404            crate::telemetry::report_event(
405                risingwave_pb::telemetry::TelemetryEventStage::Recovery,
406                "normal_recovery",
407                0,
408                None,
409                None,
410                None,
411            );
412
413            let paused = self.take_pause_on_bootstrap().await.unwrap_or(false);
414
415            self.recovery(paused, RecoveryReason::Bootstrap)
416                .instrument(span)
417                .await;
418        }
419
420        Box::pin(self.run_inner(shutdown_rx)).await
421    }
422}
423
424impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
425    fn enable_per_database_isolation(&self) -> bool {
426        self.system_enable_per_database_isolation && {
427            if let Err(e) =
428                risingwave_common::license::Feature::DatabaseFailureIsolation.check_available()
429            {
430                warn!(error = %e.as_report(), "DatabaseFailureIsolation disabled by license");
431                false
432            } else {
433                true
434            }
435        }
436    }
437
438    async fn resolve_since_timestamp_snapshot_backfill(
439        &mut self,
440        new_barrier: &mut schedule::NewBarrier,
441    ) -> MetaResult<bool> {
442        let Some((
443            Command::CreateStreamingJob {
444                job_type:
445                    CreateStreamingJobType::SnapshotBackfill {
446                        snapshot_backfill_info,
447                        since_epoch: Some(since_epoch),
448                    },
449                ..
450            },
451            notifiers,
452        )) = &mut new_barrier.command
453        else {
454            return Ok(true);
455        };
456        let since_timestamp_epoch = since_epoch.provided_since_epoch;
457
458        // Complete any inflight command first so the committed upstream epoch and
459        // table changelog view used for since_timestamp resolution cannot be stale.
460        match self.completing_task.wait_completing_task().await {
461            Ok(Some(output)) => self
462                .checkpoint_control
463                .ack_completed(&mut self.partial_graph_manager, output),
464            Ok(None) => {}
465            Err(err) => {
466                error!(
467                    err = %err.as_report(),
468                    "failed to wait completing task before resolving since_timestamp"
469                );
470                return Err(err);
471            }
472        }
473
474        match self
475            .context
476            .resolve_log_store_epoch(
477                snapshot_backfill_info
478                    .upstream_mv_table_id_to_backfill_epoch
479                    .keys()
480                    .copied(),
481                since_timestamp_epoch,
482            )
483            .await
484        {
485            Ok(upstream_log_epochs) => {
486                since_epoch.resolved = Some(upstream_log_epochs);
487                Ok(true)
488            }
489            Err(err) => {
490                error!(
491                    err = %err.as_report(),
492                    "failed to resolve log store epoch for since_timestamp"
493                );
494                for notifier in take(notifiers) {
495                    notifier.notify_start_failed(err.clone());
496                }
497                Ok(false)
498            }
499        }
500    }
501
502    pub(super) async fn run_inner(mut self, mut shutdown_rx: Receiver<()>) {
503        let (local_notification_tx, mut local_notification_rx) =
504            tokio::sync::mpsc::unbounded_channel();
505        self.env
506            .notification_manager()
507            .insert_local_sender(local_notification_tx);
508
509        // Start the event loop.
510        loop {
511            tokio::select! {
512                biased;
513
514                // Shutdown
515                _ = &mut shutdown_rx => {
516                    tracing::info!("Barrier manager is stopped");
517                    break;
518                }
519
520                request = self.request_rx.recv() => {
521                    if let Some(request) = request {
522                        match request {
523                            BarrierManagerRequest::GetBackfillProgress(result_tx) => {
524                                let progress = self.checkpoint_control.gen_backfill_progress();
525                                if result_tx.send(Ok(progress)).is_err() {
526                                    error!("failed to send get ddl progress");
527                                }
528                            }
529                            BarrierManagerRequest::GetFragmentBackfillProgress(result_tx) => {
530                                let progress =
531                                    self.checkpoint_control.gen_fragment_backfill_progress();
532                                if result_tx.send(Ok(progress)).is_err() {
533                                    error!("failed to send get fragment backfill progress");
534                                }
535                            }
536                            BarrierManagerRequest::GetCdcProgress(result_tx) => {
537                                let progress = self.checkpoint_control.gen_cdc_progress();
538                                if result_tx.send(Ok(progress)).is_err() {
539                                    error!("failed to send get ddl progress");
540                                }
541                            }
542                            // Handle adhoc recovery triggered by user.
543                            BarrierManagerRequest::AdhocRecovery(sender) => {
544                                self.adhoc_recovery().await;
545                                if sender.send(()).is_err() {
546                                    warn!("failed to notify finish of adhoc recovery");
547                                }
548                            }
549                            BarrierManagerRequest::UpdateDatabaseBarrier( UpdateDatabaseBarrierRequest {
550                                database_id,
551                                barrier_interval_ms,
552                                checkpoint_frequency,
553                                sender,
554                            }) => {
555                                self.periodic_barriers
556                                    .update_database_barrier(
557                                        database_id,
558                                        barrier_interval_ms,
559                                        checkpoint_frequency,
560                                    );
561                                if sender.send(()).is_err() {
562                                    warn!("failed to notify finish of update database barrier");
563                                }
564                            }
565                            BarrierManagerRequest::MayHaveCreatingJob(tx) => {
566                                if tx.send(self.checkpoint_control.may_have_creating_jobs()).is_err() {
567                                    warn!("failed to check whether there may be creating jobs");
568                                }
569                            }
570                        }
571                    } else {
572                        tracing::info!("end of request stream. meta node may be shutting down. Stop global barrier manager");
573                        return;
574                    }
575                }
576
577                changed_worker = self.active_streaming_nodes.changed() => {
578                    #[cfg(debug_assertions)]
579                    {
580                        self.active_streaming_nodes.validate_change().await;
581                    }
582
583                    info!(?changed_worker, "worker changed");
584
585                    match changed_worker {
586                        ActiveStreamingWorkerChange::Add(node)
587                        | ActiveStreamingWorkerChange::Update(node) => {
588                            self.partial_graph_manager
589                                .add_worker(node, self.context.clone())
590                                .await;
591                        }
592                        ActiveStreamingWorkerChange::Remove(node) => {
593                            self.partial_graph_manager.remove_worker(node);
594                        }
595                    }
596                }
597
598                notification = local_notification_rx.recv() => {
599                    let notification = notification.unwrap();
600                    if let LocalNotification::SystemParamsChange(p) = notification {
601                        {
602                            self.periodic_barriers.set_sys_barrier_interval(Duration::from_millis(p.barrier_interval_ms() as u64));
603                            self.periodic_barriers
604                                .set_sys_checkpoint_frequency(p.checkpoint_frequency());
605                            self.system_enable_per_database_isolation = p.per_database_isolation();
606                        }
607                    }
608                }
609                complete_result = self
610                    .completing_task
611                    .next_completed_barrier(
612                        &mut self.periodic_barriers,
613                        &mut self.checkpoint_control,
614                        &mut self.partial_graph_manager,
615                        &self.context,
616                        &self.env,
617                ) => {
618                    match complete_result {
619                        Ok(output) => {
620                            self.checkpoint_control.ack_completed(&mut self.partial_graph_manager, output);
621                        }
622                        Err(e) => {
623                            self.failure_recovery(e).await;
624                        }
625                    }
626                },
627                event = self.checkpoint_control.next_event() => {
628                    let result: MetaResult<()> = try {
629                        match event {
630                            CheckpointControlEvent::EnteringInitializing(entering_initializing) => {
631                                let database_id = entering_initializing.database_id();
632                                let error = merge_node_rpc_errors(&format!("database {} reset", database_id), entering_initializing.action.0.iter().filter_map(|(worker_id, resp)| {
633                                    resp.root_err.as_ref().map(|root_err| {
634                                        (*worker_id, ScoredError {
635                                            error: Status::internal(&root_err.err_msg),
636                                            score: Score(root_err.score)
637                                        })
638                                    })
639                                }));
640                                Self::report_collect_failure(&self.env, &error);
641                                self.context.notify_creating_job_failed(Some(database_id), format!("{}", error.as_report())).await;
642                                let result: MetaResult<_> = try {
643                                    let runtime_info = self.context.reload_database_runtime_info(database_id).await.inspect_err(|err| {
644                                        warn!(%database_id, err = %err.as_report(), "reload runtime info failed");
645                                    })?;
646                                    let rendered_info = render_runtime_info(
647                                        self.env.actor_id_generator(),
648                                        &self.active_streaming_nodes,
649                                        &runtime_info.recovery_context,
650                                        database_id,
651                                    )
652                                    .inspect_err(|err: &MetaError| {
653                                        warn!(%database_id, err = %err.as_report(), "render runtime info failed");
654                                    })?;
655                                    if let Some(rendered_info) = rendered_info {
656                                        BarrierWorkerRuntimeInfoSnapshot::validate_database_info(
657                                            database_id,
658                                            &rendered_info.job_infos,
659                                            &self.active_streaming_nodes,
660                                            &rendered_info.stream_actors,
661                                            &runtime_info.state_table_committed_epochs,
662                                        )
663                                        .inspect_err(|err| {
664                                            warn!(%database_id, err = ?err.as_report(), "database runtime info failed validation");
665                                        })?;
666                                        Some((runtime_info, rendered_info))
667                                    } else {
668                                        None
669                                    }
670                                };
671                                match result {
672                                    Ok(Some((runtime_info, rendered_info))) => {
673                                        entering_initializing.enter(
674                                            runtime_info,
675                                            rendered_info,
676                                            &mut self.partial_graph_manager,
677                                        );
678                                    }
679                                    Ok(None) => {
680                                        info!(%database_id, "database removed after reloading empty runtime info");
681                                        // mark ready to unblock subsequent request
682                                        self.context.mark_ready(MarkReadyOptions::Database(database_id));
683                                        entering_initializing.remove();
684                                    }
685                                    Err(e) => {
686                                        entering_initializing.fail_reload_runtime_info(e);
687                                    }
688                                }
689                            }
690                            CheckpointControlEvent::EnteringRunning(entering_running) => {
691                                self.context.mark_ready(MarkReadyOptions::Database(entering_running.database_id()));
692                                entering_running.enter();
693                            }
694                            CheckpointControlEvent::BatchRefreshTrigger { database_id, job_id } => {
695                                self.handle_batch_refresh_trigger(database_id, job_id).await?;
696                            }
697                        }
698                    };
699                    if let Err(e) = result {
700                        self.failure_recovery(e).await;
701                    }
702                }
703                event = self.partial_graph_manager.next_event(&self.context) => {
704                    let result: MetaResult<()> = try {
705                        match event {
706                            PartialGraphManagerEvent::Worker(_worker_id, WorkerEvent::WorkerConnected) => {
707                                // no handling on new worker connected event yet
708                            }
709                            PartialGraphManagerEvent::Worker(worker_id, WorkerEvent::WorkerError { err, affected_partial_graphs }) => {
710                                let failed_databases = self
711                                    .checkpoint_control
712                                    .databases_failed_at_worker_err(worker_id)
713                                    .chain(
714                                        affected_partial_graphs
715                                        .into_iter()
716                                        .map(|partial_graph_id| {
717                                            let (database_id, _) = from_partial_graph_id(partial_graph_id);
718                                            database_id
719                                        })
720                                    )
721                                    .collect::<HashSet<_>>();
722                                if !failed_databases.is_empty() {
723                                    if !self.enable_recovery {
724                                        panic!("control stream to worker {} failed but recovery not enabled: {}", worker_id, err.as_report());
725                                    }
726                                    if !self.enable_per_database_isolation() {
727                                        Err(err.clone())?;
728                                    }
729                                    Self::report_collect_failure(&self.env, &err);
730                                    for database_id in failed_databases {
731                                        if let Some(entering_recovery) = self.checkpoint_control.on_report_failure(database_id, &mut self.partial_graph_manager) {
732                                            warn!(%worker_id, %database_id, "database entering recovery on node failure");
733                                            self.context.abort_and_mark_blocked(Some(database_id), RecoveryReason::Failover(anyhow!("reset database: {}", database_id).into()));
734                                            self.context.notify_creating_job_failed(Some(database_id), format!("database {} reset due to node {} failure: {}", database_id, worker_id, err.as_report())).await;
735                                            // TODO: add log on blocking time
736                                            let output = self.completing_task.wait_completing_task().await?;
737                                            entering_recovery.enter(output, &mut self.partial_graph_manager);
738                                        }
739                                    }
740                                }  else {
741                                    warn!(%worker_id, "no barrier to collect from worker, ignore err");
742                                }
743                                continue;
744                            }
745                            PartialGraphManagerEvent::PartialGraph(partial_graph_id, event) => {
746                                let (database_id, _creating_job_id) = from_partial_graph_id(partial_graph_id);
747                                match event {
748                                    PartialGraphEvent::BarrierCollected(collected_barrier) => {
749                                        self.checkpoint_control.barrier_collected(partial_graph_id, collected_barrier, &mut self.periodic_barriers)?;
750                                    }
751                                    PartialGraphEvent::Error(worker_id) => {
752                                        if !self.enable_recovery {
753                                            panic!("database {database_id} failure reported from {worker_id} but recovery not enabled")
754                                        }
755                                        if !self.enable_per_database_isolation() {
756                                                Err(MetaError::from(anyhow!("database {database_id} report failure from {worker_id}")))?;
757                                            }
758                                        if let Some(entering_recovery) = self.checkpoint_control.on_report_failure(database_id, &mut self.partial_graph_manager) {
759                                            warn!(%database_id, "database entering recovery");
760                                            self.context.abort_and_mark_blocked(Some(database_id), RecoveryReason::Failover(anyhow!("reset database: {}", database_id).into()));
761                                            // TODO: add log on blocking time
762                                            let output = self.completing_task.wait_completing_task().await?;
763                                            entering_recovery.enter(output, &mut self.partial_graph_manager);
764                                        }
765                                    }
766                                    PartialGraphEvent::Reset(reset_resps) => {
767                                        self.checkpoint_control.on_partial_graph_reset(partial_graph_id, reset_resps);
768                                    }
769                                    PartialGraphEvent::Initialized => {
770                                        self.checkpoint_control.on_partial_graph_initialized(
771                                            partial_graph_id,
772                                            &mut self.partial_graph_manager,
773                                        )?;
774                                    }
775                                }
776                            }
777                        };
778                    };
779                    if let Err(e) = result {
780                        self.failure_recovery(e).await;
781                    }
782                }
783                new_barrier = self.periodic_barriers.next_barrier(&*self.context) => {
784                    let database_id = new_barrier.database_id;
785                    let mut new_barrier = if matches!(
786                        new_barrier.command,
787                        Some((Command::RescheduleIntent { .. }, _))
788                    ) {
789                        let env = self.env.clone();
790                        let worker_nodes = self
791                            .active_streaming_nodes
792                            .current()
793                            .iter()
794                            .map(|(worker_id, worker)| (*worker_id, worker.clone()))
795                            .collect();
796                        let database_info = self.checkpoint_control.database_info(database_id);
797                        match resolve_reschedule_intent(
798                            env,
799                            worker_nodes,
800                            database_info,
801                            new_barrier,
802                        ) {
803                            Ok(Some(new_barrier)) => new_barrier,
804                            Ok(None) => continue,
805                            Err(err) => {
806                                self.failure_recovery(err).await;
807                                continue;
808                            }
809                        }
810                    } else {
811                        new_barrier
812                    };
813                    match self
814                        .resolve_since_timestamp_snapshot_backfill(&mut new_barrier)
815                        .await
816                    {
817                        Ok(true) => {}
818                        Ok(false) => continue,
819                        Err(err) => {
820                            self.failure_recovery(err).await;
821                            continue;
822                        }
823                    }
824                    if let Err(e) = self.checkpoint_control.handle_new_barrier(
825                        new_barrier,
826                        &mut self.partial_graph_manager,
827                        self.active_streaming_nodes.current()
828                    ) {
829                        if !self.enable_recovery {
830                            panic!(
831                                "failed to inject barrier to some databases but recovery not enabled: {:?}", (
832                                    database_id,
833                                    e.as_report()
834                                )
835                            );
836                        }
837                        let result: MetaResult<_> = try {
838                            if !self.enable_per_database_isolation() {
839                                let err = anyhow!("failed to inject barrier to databases: {:?}", (database_id, e.as_report()));
840                                Err(MetaError::from(err))?;
841                            } else if let Some(entering_recovery) = self.checkpoint_control.on_report_failure(database_id, &mut self.partial_graph_manager) {
842                                warn!(%database_id, e = %e.as_report(),"database entering recovery on inject failure");
843                                self.context.abort_and_mark_blocked(Some(database_id), RecoveryReason::Failover(anyhow!(e).context("inject barrier failure").into()));
844                                // TODO: add log on blocking time
845                                let output = self.completing_task.wait_completing_task().await?;
846                                entering_recovery.enter(output, &mut self.partial_graph_manager);
847                            }
848                        };
849                        if let Err(e) = result {
850                            self.failure_recovery(e).await;
851                        }
852                    }
853                }
854            }
855        }
856    }
857}
858
859impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
860    /// We need to make sure there are no changes when doing recovery
861    pub async fn clear_on_err(&mut self, err: &MetaError) {
862        // join spawned completing command to finish no matter it succeeds or not.
863        match replace(&mut self.completing_task, CompletingTask::None) {
864            CompletingTask::None | CompletingTask::Err(_) => {}
865            CompletingTask::Completing {
866                epochs_to_ack,
867                join_handle,
868                ..
869            } => {
870                info!("waiting for completing command to finish in recovery");
871                match join_handle.await {
872                    Err(e) => {
873                        warn!(err = %e.as_report(), "failed to join completing task");
874                    }
875                    Ok(Err(e)) => {
876                        warn!(
877                            err = %e.as_report(),
878                            "failed to complete barrier during clear"
879                        );
880                    }
881                    Ok(Ok(hummock_version_stats)) => {
882                        self.checkpoint_control.ack_completed(
883                            &mut self.partial_graph_manager,
884                            BarrierCompleteOutput {
885                                epochs_to_ack,
886                                hummock_version_stats,
887                            },
888                        );
889                    }
890                }
891            }
892        };
893        self.partial_graph_manager.notify_all_err(err);
894    }
895}
896
897impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
898    /// Handle a batch refresh trigger: load metadata + log epochs, then start a logstore
899    /// consumption run for the given batch refresh job.
900    async fn handle_batch_refresh_trigger(
901        &mut self,
902        database_id: DatabaseId,
903        job_id: JobId,
904    ) -> MetaResult<()> {
905        // 1. Get the last committed epoch for this job (read-only).
906        let last_committed_epoch = self
907            .checkpoint_control
908            .get_batch_refresh_trigger_info(database_id, job_id);
909
910        // 2. Load context metadata + resolve log epochs asynchronously.
911        let context = self
912            .context
913            .load_batch_refresh_trigger_context(job_id, database_id, last_committed_epoch)
914            .await?;
915
916        // 3. Start the refresh run.
917        let started = self.checkpoint_control.start_batch_refresh_run(
918            database_id,
919            job_id,
920            &context,
921            self.active_streaming_nodes.current(),
922            self.env.actor_id_generator(),
923            &mut self.partial_graph_manager,
924        )?;
925
926        // 5. Update shared_actor_infos with the new fragment infos.
927        if started {
928            self.checkpoint_control
929                .apply_batch_refresh_fragment_infos(database_id, job_id);
930        }
931
932        Ok(())
933    }
934}
935
936impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
937    /// Set barrier manager status.
938    async fn failure_recovery(&mut self, err: MetaError) {
939        self.clear_on_err(&err).await;
940
941        if self.enable_recovery {
942            let span = tracing::info_span!(
943                "failure_recovery",
944                error = %err.as_report(),
945            );
946
947            crate::telemetry::report_event(
948                risingwave_pb::telemetry::TelemetryEventStage::Recovery,
949                "failure_recovery",
950                0,
951                None,
952                None,
953                None,
954            );
955
956            let reason = RecoveryReason::Failover(err);
957
958            // No need to clean dirty tables for barrier recovery,
959            // The foreground stream job should cleanup their own tables.
960            self.recovery(false, reason).instrument(span).await;
961        } else {
962            panic!(
963                "a streaming error occurred while recovery is disabled, aborting: {:?}",
964                err.as_report()
965            );
966        }
967    }
968
969    async fn adhoc_recovery(&mut self) {
970        let err = MetaErrorInner::AdhocRecovery.into();
971        self.clear_on_err(&err).await;
972
973        let span = tracing::info_span!(
974            "adhoc_recovery",
975            error = %err.as_report(),
976        );
977
978        crate::telemetry::report_event(
979            risingwave_pb::telemetry::TelemetryEventStage::Recovery,
980            "adhoc_recovery",
981            0,
982            None,
983            None,
984            None,
985        );
986
987        // No need to clean dirty tables for barrier recovery,
988        // The foreground stream job should cleanup their own tables.
989        self.recovery(false, RecoveryReason::Adhoc)
990            .instrument(span)
991            .await;
992    }
993}
994
995impl<C> GlobalBarrierWorker<C> {
996    /// Send barrier-complete-rpc and wait for responses from all CNs
997    pub(super) fn report_collect_failure(env: &MetaSrvEnv, error: &MetaError) {
998        // Record failure in event log.
999        use risingwave_pb::meta::event_log;
1000        let event = event_log::EventCollectBarrierFail {
1001            error: error.to_report_string(),
1002        };
1003        env.event_log_manager_ref()
1004            .add_event_logs(vec![event_log::Event::CollectBarrierFail(event)]);
1005    }
1006}
1007
1008mod retry_strategy {
1009    use std::time::Duration;
1010
1011    use tokio_retry::strategy::{ExponentialBackoff, jitter};
1012
1013    // Retry base interval in milliseconds.
1014    const RECOVERY_RETRY_BASE_INTERVAL: u64 = 20;
1015    // Retry max interval.
1016    const RECOVERY_RETRY_MAX_INTERVAL: Duration = Duration::from_secs(5);
1017
1018    // MrCroxx: Use concrete type here to prevent unsolved compiler issue.
1019    // Feel free to replace the concrete type with TAIT after fixed.
1020
1021    // mod retry_backoff_future {
1022    //     use std::future::Future;
1023    //     use std::time::Duration;
1024    //
1025    //     use tokio::time::sleep;
1026    //
1027    //     pub(crate) type RetryBackoffFuture = impl Future<Output = ()> + Unpin + Send + 'static;
1028    //
1029    //     #[define_opaque(RetryBackoffFuture)]
1030    //     pub(super) fn get_retry_backoff_future(duration: Duration) -> RetryBackoffFuture {
1031    //         Box::pin(sleep(duration))
1032    //     }
1033    // }
1034    // pub(crate) use retry_backoff_future::*;
1035
1036    pub(crate) type RetryBackoffFuture = std::pin::Pin<Box<tokio::time::Sleep>>;
1037
1038    pub(crate) fn get_retry_backoff_future(duration: Duration) -> RetryBackoffFuture {
1039        Box::pin(tokio::time::sleep(duration))
1040    }
1041
1042    pub(crate) type RetryBackoffStrategy =
1043        impl Iterator<Item = RetryBackoffFuture> + Send + 'static;
1044
1045    /// Initialize a retry strategy for operation in recovery.
1046    #[inline(always)]
1047    pub(crate) fn get_retry_strategy() -> impl Iterator<Item = Duration> + Send + 'static {
1048        ExponentialBackoff::from_millis(RECOVERY_RETRY_BASE_INTERVAL)
1049            .max_delay(RECOVERY_RETRY_MAX_INTERVAL)
1050            .map(jitter)
1051    }
1052
1053    #[define_opaque(RetryBackoffStrategy)]
1054    pub(crate) fn get_retry_backoff_strategy() -> RetryBackoffStrategy {
1055        get_retry_strategy().map(get_retry_backoff_future)
1056    }
1057}
1058
1059pub(crate) use retry_strategy::*;
1060use risingwave_common::error::tonic::extra::{Score, ScoredError};
1061use risingwave_pb::meta::event_log::{Event, EventRecovery};
1062
1063use crate::barrier::partial_graph::{
1064    PartialGraphEvent, PartialGraphManager, PartialGraphManagerEvent, WorkerEvent,
1065};
1066
1067impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
1068    /// Recovery the whole cluster from the latest epoch.
1069    ///
1070    /// If `paused_reason` is `Some`, all data sources (including connectors and DMLs) will be
1071    /// immediately paused after recovery, until the user manually resume them either by restarting
1072    /// the cluster or `risectl` command. Used for debugging purpose.
1073    ///
1074    /// Returns the new state of the barrier manager after recovery.
1075    pub async fn recovery(&mut self, is_paused: bool, recovery_reason: RecoveryReason) {
1076        // Clear all control streams to release resources (connections to compute nodes) first.
1077        self.partial_graph_manager.clear_worker();
1078
1079        let reason_str = match &recovery_reason {
1080            RecoveryReason::Bootstrap => "bootstrap".to_owned(),
1081            RecoveryReason::Failover(err) => {
1082                format!("failed over: {}", err.as_report())
1083            }
1084            RecoveryReason::Adhoc => "adhoc recovery".to_owned(),
1085        };
1086        self.context.abort_and_mark_blocked(None, recovery_reason);
1087
1088        self.recovery_inner(is_paused, reason_str).await;
1089        self.context.mark_ready(MarkReadyOptions::Global {
1090            blocked_databases: self.checkpoint_control.recovering_databases().collect(),
1091        });
1092    }
1093
1094    #[await_tree::instrument("recovery({recovery_reason})")]
1095    async fn recovery_inner(&mut self, is_paused: bool, recovery_reason: String) {
1096        let event_log_manager_ref = self.env.event_log_manager_ref();
1097
1098        tracing::info!("recovery start!");
1099        event_log_manager_ref.add_event_logs(vec![Event::Recovery(
1100            EventRecovery::global_recovery_start(recovery_reason.clone()),
1101        )]);
1102
1103        let retry_strategy = get_retry_strategy();
1104
1105        // We take retry into consideration because this is the latency user sees for a cluster to
1106        // get recovered.
1107        let recovery_timer = GLOBAL_META_METRICS
1108            .recovery_latency
1109            .with_label_values(&["global"])
1110            .start_timer();
1111
1112        let enable_per_database_isolation = self.enable_per_database_isolation();
1113
1114        let recovery_future = tokio_retry::Retry::spawn(retry_strategy, || async {
1115            self.env.stream_client_pool().invalidate_all();
1116            // We need to notify_creating_job_failed in every recovery retry, because in outer create_streaming_job handler,
1117            // it holds the reschedule_read_lock and wait for creating job to finish, and caused the following scale_actor fail
1118            // to acquire the reschedule_write_lock, and then keep recovering, and then deadlock.
1119            // TODO: refactor and fix this hacky implementation.
1120            self.context
1121                .notify_creating_job_failed(None, recovery_reason.clone())
1122                .await;
1123
1124            let runtime_info_snapshot = self
1125                .context
1126                .reload_runtime_info()
1127                .await?;
1128            let BarrierWorkerRuntimeInfoSnapshot {
1129                active_streaming_nodes,
1130                recovery_context,
1131                mut state_table_committed_epochs,
1132                mut state_table_log_epochs,
1133                mut mv_depended_subscriptions,
1134                mut background_jobs,
1135                hummock_version_stats,
1136                database_infos,
1137                mut cdc_table_snapshot_splits,
1138            } = runtime_info_snapshot;
1139
1140            let mut partial_graph_manager = PartialGraphManager::recover(
1141                    self.env.clone(),
1142                    active_streaming_nodes.current(),
1143                    self.context.clone(),
1144                )
1145                .await;
1146            {
1147                let mut empty_databases = HashSet::new();
1148                let mut collected_databases = HashMap::new();
1149                let mut collecting_databases = HashMap::new();
1150                let mut failed_databases = HashMap::new();
1151                for &database_id in recovery_context.fragment_context.database_map.keys() {
1152                    let mut recoverer = partial_graph_manager.start_recover();
1153                    let result: MetaResult<_> = try {
1154                        let Some(rendered_info) = render_runtime_info(
1155                            self.env.actor_id_generator(),
1156                            &active_streaming_nodes,
1157                            &recovery_context,
1158                            database_id,
1159                        )
1160                            .inspect_err(|err: &MetaError| {
1161                                warn!(%database_id, err = %err.as_report(), "render runtime info failed");
1162                            })? else {
1163                            empty_databases.insert(database_id);
1164                            continue;
1165                        };
1166                        BarrierWorkerRuntimeInfoSnapshot::validate_database_info(
1167                            database_id,
1168                            &rendered_info.job_infos,
1169                            &active_streaming_nodes,
1170                            &rendered_info.stream_actors,
1171                            &state_table_committed_epochs,
1172                        )
1173                        .inspect_err(|err| {
1174                            warn!(%database_id, err = %err.as_report(), "rendered runtime info failed validation");
1175                        })?;
1176                        let RenderedDatabaseRuntimeInfo {
1177                            job_infos,
1178                            stream_actors,
1179                            mut source_splits,
1180                            batch_refresh,
1181                        } = rendered_info;
1182                        recoverer.inject_database_initial_barrier(
1183                            database_id,
1184                            job_infos,
1185                            &recovery_context.job_extra_info,
1186                            &mut state_table_committed_epochs,
1187                            &mut state_table_log_epochs,
1188                            &recovery_context.fragment_relations,
1189                            &stream_actors,
1190                            &mut source_splits,
1191                            &mut background_jobs,
1192                            &mut mv_depended_subscriptions,
1193                            is_paused,
1194                            &hummock_version_stats,
1195                            &mut cdc_table_snapshot_splits,
1196                            batch_refresh,
1197                        )?
1198                    };
1199                    let collector = match result {
1200                        Ok(database) => {
1201                            DatabaseInitialBarrierCollector {
1202                                database_id,
1203                                initializing_partial_graphs: recoverer.all_initializing(),
1204                                database,
1205                            }
1206                        }
1207                        Err(e) => {
1208                            warn!(%database_id, e = %e.as_report(), "failed to inject database initial barrier");
1209                            assert!(failed_databases.insert(database_id, recoverer.failed()).is_none(), "non-duplicate");
1210                            continue;
1211                        }
1212                    };
1213                    if !collector.is_collected() {
1214                        assert!(collecting_databases.insert(database_id, collector).is_none());
1215                    } else {
1216                        warn!(%database_id, "database has no node to inject initial barrier");
1217                        assert!(collected_databases.insert(database_id, collector.finish()).is_none());
1218                    }
1219                }
1220                if !empty_databases.is_empty() {
1221                    info!(?empty_databases, "empty database in global recovery");
1222                }
1223                while !collecting_databases.is_empty() {
1224                    match partial_graph_manager.next_event(&self.context).await {
1225                        PartialGraphManagerEvent::Worker(_, WorkerEvent::WorkerConnected) => {
1226                            // not handle WorkerConnected yet
1227                        }
1228                        PartialGraphManagerEvent::Worker(worker_id, WorkerEvent::WorkerError { err, affected_partial_graphs }) => {
1229                            let affected_databases: HashSet<_> = affected_partial_graphs.into_iter().map(|partial_graph_id| {
1230                                let (database_id, _) = from_partial_graph_id(partial_graph_id);
1231                                database_id
1232                            }).collect();
1233                            warn!(%worker_id, err = %err.as_report(), "worker node failure during recovery");
1234                            for (failed_database_id, collector) in collecting_databases.extract_if(|database_id, collector| {
1235                                !collector.is_valid_after_worker_err(worker_id) || affected_databases.contains(database_id)
1236                            }) {
1237                                warn!(%failed_database_id, %worker_id, "database failed to recovery in global recovery due to worker node err");
1238                                let resetting_partial_graphs: HashSet<_> = collector.all_partial_graphs().collect();
1239                                partial_graph_manager.reset_partial_graphs(resetting_partial_graphs.iter().copied());
1240                                assert!(failed_databases.insert(failed_database_id, resetting_partial_graphs).is_none());
1241                            }
1242                        }
1243                        PartialGraphManagerEvent::PartialGraph(partial_graph_id, event) => {
1244                            match event {
1245                                PartialGraphEvent::BarrierCollected(_) => {
1246                                    unreachable!("no barrier collected event on initializing")
1247                                }
1248                                PartialGraphEvent::Reset(_) => {
1249                                    // Reset responses only carry diagnostic root errors. Recovery
1250                                    // itself only needs to track the partial graphs still resetting.
1251                                    let (database_id, _) =
1252                                        from_partial_graph_id(partial_graph_id);
1253                                    let resetting_partial_graphs = failed_databases
1254                                        .get_mut(&database_id)
1255                                        .expect("reset partial graph should belong to a failed database");
1256                                    assert!(
1257                                        resetting_partial_graphs.remove(&partial_graph_id),
1258                                        "partial graph {partial_graph_id} should be resetting"
1259                                    );
1260                                }
1261                                PartialGraphEvent::Error(worker_id) => {
1262                                    let (database_id, _) = from_partial_graph_id(partial_graph_id);
1263                                    if let Some(collector) = collecting_databases.remove(&database_id) {
1264                                        warn!(%database_id, %worker_id, "database reset during global recovery");
1265                                        let resetting_partial_graphs: HashSet<_> = collector.all_partial_graphs().collect();
1266                                        partial_graph_manager.reset_partial_graphs(resetting_partial_graphs.iter().copied());
1267                                        assert!(failed_databases.insert(database_id, resetting_partial_graphs).is_none());
1268                                    } else if let Some(database) = collected_databases.remove(&database_id) {
1269                                        warn!(%database_id, %worker_id, "database initialized but later reset during global recovery");
1270                                        let resetting_partial_graphs: HashSet<_> = database_partial_graphs(database_id, database.independent_checkpoint_job_controls.keys().copied()).collect();
1271                                        partial_graph_manager.reset_partial_graphs(resetting_partial_graphs.iter().copied());
1272                                        assert!(failed_databases.insert(database_id, resetting_partial_graphs).is_none());
1273                                    } else {
1274                                        assert!(failed_databases.contains_key(&database_id));
1275                                    }
1276                                }
1277                                PartialGraphEvent::Initialized => {
1278                                    let (database_id, _) = from_partial_graph_id(partial_graph_id);
1279                                    if failed_databases.contains_key(&database_id) {
1280                                        assert!(!collecting_databases.contains_key(&database_id));
1281                                        // ignore the lately initialized partial graph of failed database
1282                                        continue;
1283                                    }
1284                                    let Entry::Occupied(mut entry) = collecting_databases.entry(database_id) else {
1285                                        unreachable!("should exist")
1286                                    };
1287                                    let collector = entry.get_mut();
1288                                    collector.partial_graph_initialized(partial_graph_id);
1289                                    if collector.is_collected() {
1290                                        let collector = entry.remove();
1291                                        assert!(collected_databases.insert(database_id, collector.finish()).is_none());
1292                                    }
1293                                }
1294                            }
1295                        }
1296                    }
1297                }
1298                debug!("collected initial barrier");
1299                if !background_jobs.is_empty() {
1300                    warn!(job_ids = ?background_jobs.iter().collect_vec(), "unused recovered background mview in recovery");
1301                }
1302                if !mv_depended_subscriptions.is_empty() {
1303                    warn!(?mv_depended_subscriptions, "unused subscription infos in recovery");
1304                }
1305                if !state_table_committed_epochs.is_empty() {
1306                    warn!(?state_table_committed_epochs, "unused state table committed epoch in recovery");
1307                }
1308                if !enable_per_database_isolation && !failed_databases.is_empty() {
1309                    return Err(anyhow!(
1310                        "global recovery failed due to failure of databases {:?}",
1311                        failed_databases.keys().collect_vec()).into()
1312                    );
1313                }
1314                let checkpoint_control = CheckpointControl::recover(
1315                    collected_databases,
1316                    failed_databases,
1317                    hummock_version_stats,
1318                    self.env.clone(),
1319                );
1320
1321                let reader = self.env.system_params_reader().await;
1322                let checkpoint_frequency = reader.checkpoint_frequency();
1323                let barrier_interval = Duration::from_millis(reader.barrier_interval_ms() as u64);
1324                let periodic_barriers = PeriodicBarriers::new(
1325                    barrier_interval,
1326                    checkpoint_frequency,
1327                    database_infos,
1328                );
1329
1330                Ok((
1331                    active_streaming_nodes,
1332                    partial_graph_manager,
1333                    checkpoint_control,
1334                    periodic_barriers,
1335                ))
1336            }
1337        }.inspect_err(|err: &MetaError| {
1338            tracing::error!(error = %err.as_report(), "recovery failed");
1339            event_log_manager_ref.add_event_logs(vec![Event::Recovery(
1340                EventRecovery::global_recovery_failure(recovery_reason.clone(), err.to_report_string()),
1341            )]);
1342            GLOBAL_META_METRICS.recovery_failure_cnt.with_label_values(&["global"]).inc();
1343        }))
1344        .instrument(tracing::info_span!("recovery_attempt"));
1345
1346        let mut recover_txs = vec![];
1347        let mut update_barrier_requests = vec![];
1348        pin_mut!(recovery_future);
1349        let mut request_rx_closed = false;
1350        let new_state = loop {
1351            select! {
1352                biased;
1353                new_state = &mut recovery_future => {
1354                    break new_state.expect("Retry until recovery success.");
1355                }
1356                request = pin!(self.request_rx.recv()), if !request_rx_closed => {
1357                    let Some(request) = request else {
1358                        warn!("request rx channel closed during recovery");
1359                        request_rx_closed = true;
1360                        continue;
1361                    };
1362                    match request {
1363                        BarrierManagerRequest::GetBackfillProgress(tx) => {
1364                            let _ = tx.send(Err(anyhow!("cluster under recovery[{}]", recovery_reason).into()));
1365                        }
1366                        BarrierManagerRequest::GetFragmentBackfillProgress(tx) => {
1367                            let _ = tx.send(Err(anyhow!("cluster under recovery[{}]", recovery_reason).into()));
1368                        }
1369                        BarrierManagerRequest::GetCdcProgress(tx) => {
1370                            let _ = tx.send(Err(anyhow!("cluster under recovery[{}]", recovery_reason).into()));
1371                        }
1372                        BarrierManagerRequest::AdhocRecovery(tx) => {
1373                            recover_txs.push(tx);
1374                        }
1375                        BarrierManagerRequest::UpdateDatabaseBarrier(request) => {
1376                            update_barrier_requests.push(request);
1377                        }
1378                        BarrierManagerRequest::MayHaveCreatingJob(tx) => {
1379                            // May recover creating jobs.
1380                            let _ = tx.send(true);
1381                        }
1382                    }
1383                }
1384            }
1385        };
1386
1387        let duration = recovery_timer.stop_and_record();
1388
1389        (
1390            self.active_streaming_nodes,
1391            self.partial_graph_manager,
1392            self.checkpoint_control,
1393            self.periodic_barriers,
1394        ) = new_state;
1395
1396        tracing::info!("recovery success");
1397
1398        for UpdateDatabaseBarrierRequest {
1399            database_id,
1400            barrier_interval_ms,
1401            checkpoint_frequency,
1402            sender,
1403        } in update_barrier_requests
1404        {
1405            self.periodic_barriers.update_database_barrier(
1406                database_id,
1407                barrier_interval_ms,
1408                checkpoint_frequency,
1409            );
1410            let _ = sender.send(());
1411        }
1412
1413        for tx in recover_txs {
1414            let _ = tx.send(());
1415        }
1416
1417        let recovering_databases = self
1418            .checkpoint_control
1419            .recovering_databases()
1420            .map(|database| database.as_raw_id())
1421            .collect_vec();
1422        let running_databases = self
1423            .checkpoint_control
1424            .running_databases()
1425            .map(|database| database.as_raw_id())
1426            .collect_vec();
1427
1428        event_log_manager_ref.add_event_logs(vec![Event::Recovery(
1429            EventRecovery::global_recovery_success(
1430                recovery_reason.clone(),
1431                duration as f32,
1432                running_databases,
1433                recovering_databases,
1434            ),
1435        )]);
1436
1437        self.env
1438            .notification_manager()
1439            .notify_frontend_without_version(Operation::Update, Info::Recovery(Recovery {}));
1440        self.env
1441            .notification_manager()
1442            .notify_compute_without_version(Operation::Update, Info::Recovery(Recovery {}));
1443    }
1444}