1use std::collections::hash_map::Entry;
16use std::collections::{HashMap, HashSet};
17use std::mem::replace;
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::{
43 CheckpointControl, CheckpointControlEvent, DatabaseStatusAction, EnterReset,
44};
45use crate::barrier::complete_task::{BarrierCompleteOutput, CompletingTask};
46use crate::barrier::context::recovery::{RenderedDatabaseRuntimeInfo, render_runtime_info};
47use crate::barrier::context::{GlobalBarrierWorkerContext, GlobalBarrierWorkerContextImpl};
48use crate::barrier::info::InflightDatabaseInfo;
49use crate::barrier::rpc::{
50 DatabaseInitialBarrierCollector, database_partial_graphs, from_partial_graph_id,
51 merge_node_rpc_errors,
52};
53use crate::barrier::schedule::{MarkReadyOptions, PeriodicBarriers};
54use crate::barrier::{
55 BarrierManagerRequest, BarrierManagerStatus, BarrierWorkerRuntimeInfoSnapshot, Command,
56 CreateStreamingJobType, IndependentStreamingJobType, RecoveryReason, RescheduleContext,
57 UpdateDatabaseBarrierRequest, schedule,
58};
59use crate::controller::scale::{materialize_actor_assignments, preview_actor_assignments};
60use crate::error::MetaErrorInner;
61use crate::hummock::HummockManagerRef;
62use crate::manager::iceberg_compaction::IcebergCompactionManagerRef;
63use crate::manager::iceberg_pk_index_sink::IcebergPkIndexSinkManager;
64use crate::manager::sink_coordination::{RecoveryStart, SinkCoordinatorManager};
65use crate::manager::{
66 ActiveStreamingWorkerChange, ActiveStreamingWorkerNodes, LocalNotification, MetaSrvEnv,
67 MetadataManager,
68};
69use crate::rpc::metrics::GLOBAL_META_METRICS;
70use crate::serving::ServingVnodeMappingRef;
71use crate::stream::{
72 GlobalRefreshManagerRef, ScaleControllerRef, SourceManagerRef, build_reschedule_commands,
73 rendered_layout_matches_current,
74};
75use crate::{MetaError, MetaResult};
76
77pub(super) struct GlobalBarrierWorker<C> {
87 enable_recovery: bool,
89
90 periodic_barriers: PeriodicBarriers,
92
93 system_enable_per_database_isolation: bool,
95
96 pub(super) context: Arc<C>,
97
98 env: MetaSrvEnv,
99
100 checkpoint_control: CheckpointControl,
101
102 completing_task: CompletingTask,
105
106 request_rx: mpsc::UnboundedReceiver<BarrierManagerRequest>,
107
108 active_streaming_nodes: ActiveStreamingWorkerNodes,
109
110 partial_graph_manager: PartialGraphManager,
111}
112
113#[cfg(test)]
114mod tests {
115 use std::collections::HashMap;
116
117 use super::*;
118 use crate::barrier::RescheduleContext;
119 use crate::notification::Notifier;
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 (notifier, started_rx) = Notifier::new();
127
128 let new_barrier = schedule::NewBarrier {
129 database_id,
130 command: Some((
131 Command::RescheduleIntent {
132 context: RescheduleContext::empty(),
133 reschedule_plan: None,
134 },
135 notifier,
136 )),
137 span: tracing::Span::none(),
138 checkpoint: false,
139 };
140
141 let result =
142 resolve_reschedule_intent(env, HashMap::new(), Some(&database_info), new_barrier);
143
144 assert!(matches!(result, Ok(None)));
145 let started = started_rx.await.expect("started notifier dropped");
146 assert!(started.is_err());
147 }
148}
149
150impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
151 pub(super) async fn new_inner(
152 env: MetaSrvEnv,
153 request_rx: mpsc::UnboundedReceiver<BarrierManagerRequest>,
154 context: Arc<C>,
155 ) -> Self {
156 let enable_recovery = env.opts.enable_recovery;
157
158 let active_streaming_nodes = ActiveStreamingWorkerNodes::uninitialized();
159
160 let partial_graph_manager = PartialGraphManager::uninitialized(env.clone());
161
162 let reader = env.system_params_reader().await;
163 let system_enable_per_database_isolation = reader.per_database_isolation();
164 let periodic_barriers = PeriodicBarriers::default();
166
167 let checkpoint_control = CheckpointControl::new(env.clone());
168 Self {
169 enable_recovery,
170 periodic_barriers,
171 system_enable_per_database_isolation,
172 context,
173 env,
174 checkpoint_control,
175 completing_task: CompletingTask::None,
176 request_rx,
177 active_streaming_nodes,
178 partial_graph_manager,
179 }
180 }
181}
182
183fn resolve_reschedule_intent(
184 env: MetaSrvEnv,
185 worker_nodes: HashMap<WorkerId, WorkerNode>,
186 database_info: Option<&InflightDatabaseInfo>,
187 mut new_barrier: schedule::NewBarrier,
188) -> MetaResult<Option<schedule::NewBarrier>> {
189 let Some((command, notifier)) = new_barrier.command.take() else {
190 return Ok(Some(new_barrier));
191 };
192
193 match command {
194 Command::RescheduleIntent {
195 context,
196 reschedule_plan,
197 } => {
198 if let Some(reschedule_plan) = reschedule_plan {
199 new_barrier.command = Some((
200 Command::RescheduleIntent {
201 context,
202 reschedule_plan: Some(reschedule_plan),
203 },
204 notifier,
205 ));
206 return Ok(Some(new_barrier));
207 }
208 let span = tracing::info_span!(
209 "resolve_reschedule_intent",
210 database_id = %new_barrier.database_id
211 );
212 let reschedule_plan = {
213 let _guard = span.enter();
214 build_reschedule_from_context(
215 &env,
216 worker_nodes,
217 new_barrier.database_id,
218 context,
219 database_info.ok_or_else(|| {
220 anyhow!(
221 "database {} not found when resolving reschedule intent",
222 new_barrier.database_id
223 )
224 })?,
225 )
226 };
227 match reschedule_plan {
228 Ok(Some(reschedule_plan)) => {
229 new_barrier.command = Some((
230 Command::RescheduleIntent {
231 context: RescheduleContext::empty(),
232 reschedule_plan: Some(reschedule_plan),
233 },
234 notifier,
235 ));
236 Ok(Some(new_barrier))
237 }
238 Ok(None) => {
239 notifier.start().started();
241 Ok(None)
242 }
243 Err(err) => {
244 notifier.notify_start_failed(err);
245 Ok(None)
246 }
247 }
248 }
249 _ => {
250 new_barrier.command = Some((command, notifier));
251 Ok(Some(new_barrier))
252 }
253 }
254}
255
256fn build_reschedule_from_context(
257 env: &MetaSrvEnv,
258 worker_nodes: HashMap<WorkerId, WorkerNode>,
259 database_id: DatabaseId,
260 context: RescheduleContext,
261 database_info: &InflightDatabaseInfo,
262) -> MetaResult<Option<crate::barrier::ReschedulePlan>> {
263 if worker_nodes.is_empty() {
264 return Err(anyhow!("no active streaming workers for reschedule").into());
265 }
266
267 if context.is_empty() {
268 return Ok(None);
269 }
270
271 let all_prev_fragments = database_info
274 .fragment_infos()
275 .map(|fragment| (fragment.fragment_id, fragment))
276 .collect();
277
278 let previewed = preview_actor_assignments(&worker_nodes, &context.loaded)?;
279
280 if rendered_layout_matches_current(&previewed.fragments, &all_prev_fragments)? {
281 return Ok(None);
282 }
283
284 let actor_id_counter = env.actor_id_generator();
285 let rendered = materialize_actor_assignments(actor_id_counter, previewed);
288 let mut commands = build_reschedule_commands(rendered.fragments, context, all_prev_fragments)?;
289 Ok(commands.remove(&database_id))
290}
291
292impl GlobalBarrierWorker<GlobalBarrierWorkerContextImpl> {
293 #[expect(clippy::too_many_arguments)]
295 pub async fn new(
296 scheduled_barriers: schedule::ScheduledBarriers,
297 env: MetaSrvEnv,
298 metadata_manager: MetadataManager,
299 hummock_manager: HummockManagerRef,
300 serving_vnode_mapping: ServingVnodeMappingRef,
301 source_manager: SourceManagerRef,
302 sink_manager: SinkCoordinatorManager,
303 iceberg_pk_index_sink_manager: IcebergPkIndexSinkManager,
304 iceberg_compaction_manager: IcebergCompactionManagerRef,
305 scale_controller: ScaleControllerRef,
306 request_rx: mpsc::UnboundedReceiver<BarrierManagerRequest>,
307 refresh_manager: GlobalRefreshManagerRef,
308 ) -> Self {
309 let status = Arc::new(ArcSwap::new(Arc::new(BarrierManagerStatus::Starting)));
310
311 let context = Arc::new(GlobalBarrierWorkerContextImpl::new(
312 scheduled_barriers,
313 status,
314 metadata_manager,
315 hummock_manager,
316 serving_vnode_mapping,
317 source_manager,
318 scale_controller,
319 env.clone(),
320 refresh_manager,
321 sink_manager,
322 iceberg_pk_index_sink_manager,
323 iceberg_compaction_manager,
324 ));
325
326 Self::new_inner(env, request_rx, context).await
327 }
328
329 pub fn start(self) -> (JoinHandle<()>, Sender<()>) {
330 let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
331 let fut = (self.env.await_tree_reg())
332 .register_derived_root("Global Barrier Worker")
333 .instrument(self.run(shutdown_rx));
334 let join_handle = tokio::spawn(fut);
335
336 (join_handle, shutdown_tx)
337 }
338
339 async fn take_pause_on_bootstrap(&mut self) -> MetaResult<bool> {
341 let paused = self
342 .env
343 .system_params_reader()
344 .await
345 .pause_on_next_bootstrap()
346 || self.env.opts.pause_on_next_bootstrap_offline;
347
348 if paused {
349 warn!(
350 "The cluster will bootstrap with all data sources paused as specified by the system parameter `{}`. \
351 It will now be reset to `false`. \
352 To resume the data sources, either restart the cluster again or use `risectl meta resume`.",
353 PAUSE_ON_NEXT_BOOTSTRAP_KEY
354 );
355 self.env
356 .system_params_manager_impl_ref()
357 .set_param(PAUSE_ON_NEXT_BOOTSTRAP_KEY, Some("false".to_owned()))
358 .await?;
359 }
360 Ok(paused)
361 }
362
363 async fn run(mut self, shutdown_rx: Receiver<()>) {
365 tracing::info!(
366 "Starting barrier manager with: enable_recovery={}, in_flight_barrier_nums={}",
367 self.enable_recovery,
368 self.checkpoint_control.in_flight_barrier_nums,
369 );
370
371 if !self.enable_recovery {
372 let job_exist = self
373 .context
374 .metadata_manager
375 .catalog_controller
376 .has_any_streaming_jobs()
377 .await
378 .unwrap();
379 if job_exist {
380 panic!(
381 "Some streaming jobs already exist in meta, please start with recovery enabled \
382 or clean up the metadata using `./risedev clean-data`"
383 );
384 }
385 }
386
387 {
388 let span = tracing::info_span!("bootstrap_recovery");
393 crate::telemetry::report_event(
394 risingwave_pb::telemetry::TelemetryEventStage::Recovery,
395 "normal_recovery",
396 0,
397 None,
398 None,
399 None,
400 );
401
402 let paused = self.take_pause_on_bootstrap().await.unwrap_or(false);
403
404 Box::pin(
407 self.recovery(paused, RecoveryReason::Bootstrap)
408 .instrument(span),
409 )
410 .await;
411 }
412
413 Box::pin(self.run_inner(shutdown_rx)).await
414 }
415}
416
417impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
418 async fn abort_database_and_mark_blocked(
419 context: &C,
420 entering_recovery: &DatabaseStatusAction<'_, EnterReset>,
421 err: impl Into<MetaError>,
422 ) -> MetaResult<()> {
423 context
424 .abort_and_mark_blocked(
425 RecoveryStart::Database {
426 database_id: entering_recovery.database_id(),
427 job_ids: entering_recovery.job_ids(),
428 },
429 RecoveryReason::Failover(err.into()),
430 )
431 .await
432 }
433
434 fn enable_per_database_isolation(&self) -> bool {
435 self.system_enable_per_database_isolation && {
436 if let Err(e) =
437 risingwave_common::license::Feature::DatabaseFailureIsolation.check_available()
438 {
439 warn!(error = %e.as_report(), "DatabaseFailureIsolation disabled by license");
440 false
441 } else {
442 true
443 }
444 }
445 }
446
447 async fn resolve_since_timestamp_snapshot_backfill(
448 &mut self,
449 new_barrier: &mut schedule::NewBarrier,
450 ) -> MetaResult<bool> {
451 let Some((
452 Command::CreateStreamingJob {
453 job_type:
454 CreateStreamingJobType::Independent {
455 snapshot_backfill_info,
456 kind:
457 IndependentStreamingJobType::SnapshotBackfill {
458 since_epoch: Some(since_epoch),
459 },
460 },
461 ..
462 },
463 _,
464 )) = &mut new_barrier.command
465 else {
466 return Ok(true);
467 };
468 let since_timestamp_epoch = since_epoch.provided_since_epoch;
469
470 match self.completing_task.wait_completing_task().await {
473 Ok(Some(output)) => self
474 .checkpoint_control
475 .ack_completed(&mut self.partial_graph_manager, output)?,
476 Ok(None) => {}
477 Err(err) => {
478 error!(
479 err = %err.as_report(),
480 "failed to wait completing task before resolving since_timestamp"
481 );
482 return Err(err);
483 }
484 }
485
486 match self
487 .context
488 .resolve_log_store_epoch(
489 snapshot_backfill_info
490 .upstream_mv_table_id_to_backfill_epoch
491 .keys()
492 .copied(),
493 since_timestamp_epoch,
494 )
495 .await
496 {
497 Ok(upstream_log_epochs) => {
498 since_epoch.resolved = Some(upstream_log_epochs);
499 Ok(true)
500 }
501 Err(err) => {
502 error!(
503 err = %err.as_report(),
504 "failed to resolve log store epoch for since_timestamp"
505 );
506 let (_, notifier) = new_barrier
507 .command
508 .take()
509 .expect("matched command should still exist");
510 notifier.notify_start_failed(err);
511 Ok(false)
512 }
513 }
514 }
515
516 pub(super) async fn run_inner(mut self, mut shutdown_rx: Receiver<()>) {
517 let (local_notification_tx, mut local_notification_rx) =
518 tokio::sync::mpsc::unbounded_channel();
519 self.env
520 .notification_manager()
521 .insert_local_sender(local_notification_tx);
522
523 loop {
525 tokio::select! {
526 biased;
527
528 _ = &mut shutdown_rx => {
530 tracing::info!("Barrier manager is stopped");
531 break;
532 }
533
534 request = self.request_rx.recv() => {
535 if let Some(request) = request {
536 match request {
537 BarrierManagerRequest::GetBackfillProgress(result_tx) => {
538 let progress = self.checkpoint_control.gen_backfill_progress();
539 if result_tx.send(Ok(progress)).is_err() {
540 error!("failed to send get ddl progress");
541 }
542 }
543 BarrierManagerRequest::GetFragmentBackfillProgress(result_tx) => {
544 let progress =
545 self.checkpoint_control.gen_fragment_backfill_progress();
546 if result_tx.send(Ok(progress)).is_err() {
547 error!("failed to send get fragment backfill progress");
548 }
549 }
550 BarrierManagerRequest::GetCdcProgress(result_tx) => {
551 let progress = self.checkpoint_control.gen_cdc_progress();
552 if result_tx.send(Ok(progress)).is_err() {
553 error!("failed to send get ddl progress");
554 }
555 }
556 BarrierManagerRequest::AdhocRecovery(sender) => {
558 self.adhoc_recovery().await;
559 if sender.send(()).is_err() {
560 warn!("failed to notify finish of adhoc recovery");
561 }
562 }
563 BarrierManagerRequest::UpdateDatabaseBarrier( UpdateDatabaseBarrierRequest {
564 database_id,
565 barrier_interval_ms,
566 checkpoint_frequency,
567 sender,
568 }) => {
569 self.periodic_barriers
570 .update_database_barrier(
571 database_id,
572 barrier_interval_ms,
573 checkpoint_frequency,
574 );
575 if sender.send(()).is_err() {
576 warn!("failed to notify finish of update database barrier");
577 }
578 }
579 }
580 } else {
581 tracing::info!("end of request stream. meta node may be shutting down. Stop global barrier manager");
582 return;
583 }
584 }
585
586 changed_worker = self.active_streaming_nodes.changed() => {
587 #[cfg(debug_assertions)]
588 {
589 self.active_streaming_nodes.validate_change().await;
590 }
591
592 info!(?changed_worker, "worker changed");
593
594 match changed_worker {
595 ActiveStreamingWorkerChange::Add(node)
596 | ActiveStreamingWorkerChange::Update(node) => {
597 self.partial_graph_manager
598 .add_worker(node, self.context.clone())
599 .await;
600 }
601 ActiveStreamingWorkerChange::Remove(node) => {
602 self.partial_graph_manager.remove_worker(node);
603 }
604 }
605 }
606
607 notification = local_notification_rx.recv() => {
608 let notification = notification.unwrap();
609 if let LocalNotification::SystemParamsChange(p) = notification {
610 {
611 self.periodic_barriers.set_sys_barrier_interval(Duration::from_millis(p.barrier_interval_ms() as u64));
612 self.periodic_barriers
613 .set_sys_checkpoint_frequency(p.checkpoint_frequency());
614 self.system_enable_per_database_isolation = p.per_database_isolation();
615 }
616 }
617 }
618 complete_result = self
619 .completing_task
620 .next_completed_barrier(
621 &mut self.periodic_barriers,
622 &mut self.checkpoint_control,
623 &mut self.partial_graph_manager,
624 &self.context,
625 &self.env,
626 ) => {
627 match complete_result {
628 Ok(output) => {
629 if let Err(e) = self.checkpoint_control.ack_completed(
630 &mut self.partial_graph_manager,
631 output,
632 ) {
633 self.failure_recovery(e).await;
634 }
635 }
636 Err(e) => {
637 self.failure_recovery(e).await;
638 }
639 }
640 },
641 event = self.checkpoint_control.next_event() => {
642 let result: MetaResult<()> = try {
643 match event {
644 CheckpointControlEvent::EnteringInitializing(entering_initializing) => {
645 let database_id = entering_initializing.database_id();
646 let error = merge_node_rpc_errors(&format!("database {} reset", database_id), entering_initializing.action.0.iter().filter_map(|(worker_id, resp)| {
647 resp.root_err.as_ref().map(|root_err| {
648 (*worker_id, ScoredError {
649 error: Status::internal(&root_err.err_msg),
650 score: Score(root_err.score)
651 })
652 })
653 }));
654 Self::report_collect_failure(&self.env, &error);
655 self.context.notify_creating_job_failed(Some(database_id), format!("{}", error.as_report())).await;
656 let result: MetaResult<_> = try {
657 let runtime_info = self.context.reload_database_runtime_info(database_id).await.inspect_err(|err| {
658 warn!(%database_id, err = %err.as_report(), "reload runtime info failed");
659 })?;
660 let rendered_info = render_runtime_info(
661 self.env.actor_id_generator(),
662 &self.active_streaming_nodes,
663 self.partial_graph_manager.control_stream_manager(),
664 &runtime_info.recovery_context,
665 database_id,
666 )
667 .inspect_err(|err: &MetaError| {
668 warn!(%database_id, err = %err.as_report(), "render runtime info failed");
669 })?;
670 if let Some(rendered_info) = rendered_info {
671 BarrierWorkerRuntimeInfoSnapshot::validate_database_info(
672 database_id,
673 &rendered_info.job_infos,
674 &self.active_streaming_nodes,
675 &rendered_info.stream_actors,
676 &runtime_info.state_table_committed_epochs,
677 )
678 .inspect_err(|err| {
679 warn!(%database_id, err = ?err.as_report(), "database runtime info failed validation");
680 })?;
681 Some((runtime_info, rendered_info))
682 } else {
683 None
684 }
685 };
686 match result {
687 Ok(Some((runtime_info, rendered_info))) => {
688 entering_initializing.enter(
689 runtime_info,
690 rendered_info,
691 &mut self.partial_graph_manager,
692 );
693 }
694 Ok(None) => {
695 info!(%database_id, "database removed after reloading empty runtime info");
696 entering_initializing.remove();
697 self.context
698 .refresh_table_refill_runtime_state_after_recovery()
699 .await?;
700 self.context
702 .mark_ready(MarkReadyOptions::Database(database_id))
703 .await?;
704 }
705 Err(e) => {
706 entering_initializing.fail_reload_runtime_info(e);
707 }
708 }
709 }
710 CheckpointControlEvent::EnteringRunning(entering_running) => {
711 let database_id = entering_running.database_id();
712 entering_running.enter();
713 self.context
714 .refresh_table_refill_runtime_state_after_recovery()
715 .await?;
716 self.context
717 .mark_ready(MarkReadyOptions::Database(database_id))
718 .await?;
719 }
720 CheckpointControlEvent::BatchRefreshTrigger { database_id, job_id } => {
721 self.handle_batch_refresh_trigger(database_id, job_id).await?;
722 }
723 }
724 };
725 if let Err(e) = result {
726 self.failure_recovery(e).await;
727 }
728 }
729 event = self.partial_graph_manager.next_event(&self.context) => {
730 let result: MetaResult<()> = try {
731 match event {
732 PartialGraphManagerEvent::Worker(_worker_id, WorkerEvent::WorkerConnected) => {
733 }
735 PartialGraphManagerEvent::Worker(worker_id, WorkerEvent::WorkerError { err, affected_partial_graphs }) => {
736 let failed_databases = self
737 .checkpoint_control
738 .databases_failed_at_worker_err(worker_id)
739 .chain(
740 affected_partial_graphs
741 .into_iter()
742 .map(|partial_graph_id| {
743 let (database_id, _) = from_partial_graph_id(partial_graph_id);
744 database_id
745 })
746 )
747 .collect::<HashSet<_>>();
748 if !failed_databases.is_empty() {
749 if !self.enable_recovery {
750 panic!("control stream to worker {} failed but recovery not enabled: {}", worker_id, err.as_report());
751 }
752 if !self.enable_per_database_isolation() {
753 Err(err.clone())?;
754 }
755 Self::report_collect_failure(&self.env, &err);
756 for database_id in failed_databases {
757 if let Some(entering_recovery) = self.checkpoint_control.on_report_failure(database_id, &mut self.partial_graph_manager) {
758 warn!(%worker_id, %database_id, "database entering recovery on node failure");
759 Self::abort_database_and_mark_blocked(
760 &self.context,
761 &entering_recovery,
762 anyhow!("reset database: {}", database_id),
763 )
764 .await?;
765 self.context.notify_creating_job_failed(Some(database_id), format!("database {} reset due to node {} failure: {}", database_id, worker_id, err.as_report())).await;
766 let output = self.completing_task.wait_completing_task().await?;
768 entering_recovery.enter(output, &mut self.partial_graph_manager)?;
769 }
770 }
771 } else {
772 warn!(%worker_id, "no barrier to collect from worker, ignore err");
773 }
774 continue;
775 }
776 PartialGraphManagerEvent::PartialGraph(partial_graph_id, event) => {
777 let (database_id, _creating_job_id) = from_partial_graph_id(partial_graph_id);
778 match event {
779 PartialGraphEvent::BarrierCollected(collected_barrier) => {
780 self.checkpoint_control.barrier_collected(partial_graph_id, collected_barrier, &mut self.periodic_barriers)?;
781 }
782 PartialGraphEvent::Error(worker_id) => {
783 if !self.enable_recovery {
784 panic!("database {database_id} failure reported from {worker_id} but recovery not enabled")
785 }
786 if !self.enable_per_database_isolation() {
787 Err(MetaError::from(anyhow!("database {database_id} report failure from {worker_id}")))?;
788 }
789 if let Some(entering_recovery) = self.checkpoint_control.on_report_failure(database_id, &mut self.partial_graph_manager) {
790 warn!(%database_id, "database entering recovery");
791 Self::abort_database_and_mark_blocked(
792 &self.context,
793 &entering_recovery,
794 anyhow!("reset database: {}", database_id),
795 )
796 .await?;
797 let output = self.completing_task.wait_completing_task().await?;
799 entering_recovery.enter(output, &mut self.partial_graph_manager)?;
800 }
801 }
802 PartialGraphEvent::Reset(reset_resps) => {
803 self.checkpoint_control.on_partial_graph_reset(partial_graph_id, reset_resps);
804 }
805 PartialGraphEvent::Initialized => {
806 self.checkpoint_control.on_partial_graph_initialized(
807 partial_graph_id,
808 &mut self.partial_graph_manager,
809 )?;
810 }
811 }
812 }
813 };
814 };
815 if let Err(e) = result {
816 self.failure_recovery(e).await;
817 }
818 }
819 new_barrier = self.periodic_barriers.next_barrier(&*self.context) => {
820 let database_id = new_barrier.database_id;
821 let mut new_barrier = if matches!(
822 new_barrier.command,
823 Some((Command::RescheduleIntent { .. }, _))
824 ) {
825 let env = self.env.clone();
826 let worker_nodes = self
827 .active_streaming_nodes
828 .current()
829 .iter()
830 .map(|(worker_id, worker)| (*worker_id, worker.clone()))
831 .collect();
832 let database_info = self.checkpoint_control.database_info(database_id);
833 match resolve_reschedule_intent(
834 env,
835 worker_nodes,
836 database_info,
837 new_barrier,
838 ) {
839 Ok(Some(new_barrier)) => new_barrier,
840 Ok(None) => continue,
841 Err(err) => {
842 self.failure_recovery(err).await;
843 continue;
844 }
845 }
846 } else {
847 new_barrier
848 };
849 match self
850 .resolve_since_timestamp_snapshot_backfill(&mut new_barrier)
851 .await
852 {
853 Ok(true) => {}
854 Ok(false) => continue,
855 Err(err) => {
856 self.failure_recovery(err).await;
857 continue;
858 }
859 }
860 if let Err(e) = self.checkpoint_control.handle_new_barrier(
861 new_barrier,
862 &mut self.partial_graph_manager,
863 &mut self.periodic_barriers,
864 self.active_streaming_nodes.current(),
865 ) {
866 if !self.enable_recovery {
867 panic!(
868 "failed to inject barrier to some databases but recovery not enabled: {:?}", (
869 database_id,
870 e.as_report()
871 )
872 );
873 }
874 let result: MetaResult<_> = try {
875 if !self.enable_per_database_isolation() {
876 let err = anyhow!("failed to inject barrier to databases: {:?}", (database_id, e.as_report()));
877 Err(MetaError::from(err))?;
878 } else if let Some(entering_recovery) = self.checkpoint_control.on_report_failure(database_id, &mut self.partial_graph_manager) {
879 warn!(%database_id, e = %e.as_report(),"database entering recovery on inject failure");
880 Self::abort_database_and_mark_blocked(
881 &self.context,
882 &entering_recovery,
883 anyhow!(e).context("inject barrier failure"),
884 )
885 .await?;
886 let output = self.completing_task.wait_completing_task().await?;
888 entering_recovery.enter(output, &mut self.partial_graph_manager)?;
889 }
890 };
891 if let Err(e) = result {
892 self.failure_recovery(e).await;
893 }
894 }
895 }
896 }
897 }
898 }
899}
900
901impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
902 pub async fn clear_on_err(&mut self, err: &MetaError) {
904 match replace(&mut self.completing_task, CompletingTask::None) {
906 CompletingTask::None | CompletingTask::Err(_) => {}
907 CompletingTask::Completing {
908 epochs_to_ack,
909 join_handle,
910 ..
911 } => {
912 info!("waiting for completing command to finish in recovery");
913 match join_handle.await {
914 Err(e) => {
915 warn!(err = %e.as_report(), "failed to join completing task");
916 }
917 Ok(Err(e)) => {
918 warn!(
919 err = %e.as_report(),
920 "failed to complete barrier during clear"
921 );
922 }
923 Ok(Ok(hummock_version_stats)) => {
924 if let Err(e) = self.checkpoint_control.ack_completed(
925 &mut self.partial_graph_manager,
926 BarrierCompleteOutput {
927 epochs_to_ack,
928 hummock_version_stats,
929 },
930 ) {
931 warn!(
935 err = %e.as_report(),
936 "failed to acknowledge completed barrier during clear"
937 );
938 }
939 }
940 }
941 }
942 };
943 self.partial_graph_manager.notify_all_err(err);
944 }
945}
946
947impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
948 async fn handle_batch_refresh_trigger(
951 &mut self,
952 database_id: DatabaseId,
953 job_id: JobId,
954 ) -> MetaResult<()> {
955 let last_committed_epoch = self
957 .checkpoint_control
958 .get_batch_refresh_trigger_info(database_id, job_id);
959
960 let context = self
962 .context
963 .load_batch_refresh_trigger_context(job_id, database_id, last_committed_epoch)
964 .await?;
965
966 let started = self.checkpoint_control.start_batch_refresh_run(
968 database_id,
969 job_id,
970 &context,
971 self.active_streaming_nodes.current(),
972 self.env.actor_id_generator(),
973 &mut self.partial_graph_manager,
974 )?;
975
976 if started {
978 self.checkpoint_control
979 .apply_batch_refresh_fragment_infos(database_id, job_id);
980 }
981
982 Ok(())
983 }
984}
985
986impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
987 async fn failure_recovery(&mut self, err: MetaError) {
989 self.clear_on_err(&err).await;
990
991 if self.enable_recovery {
992 let span = tracing::info_span!(
993 "failure_recovery",
994 error = %err.as_report(),
995 );
996
997 crate::telemetry::report_event(
998 risingwave_pb::telemetry::TelemetryEventStage::Recovery,
999 "failure_recovery",
1000 0,
1001 None,
1002 None,
1003 None,
1004 );
1005
1006 let reason = RecoveryReason::Failover(err);
1007
1008 self.recovery(false, reason).instrument(span).await;
1011 } else {
1012 panic!(
1013 "a streaming error occurred while recovery is disabled, aborting: {:?}",
1014 err.as_report()
1015 );
1016 }
1017 }
1018
1019 async fn adhoc_recovery(&mut self) {
1020 let err = MetaErrorInner::AdhocRecovery.into();
1021 self.clear_on_err(&err).await;
1022
1023 let span = tracing::info_span!(
1024 "adhoc_recovery",
1025 error = %err.as_report(),
1026 );
1027
1028 crate::telemetry::report_event(
1029 risingwave_pb::telemetry::TelemetryEventStage::Recovery,
1030 "adhoc_recovery",
1031 0,
1032 None,
1033 None,
1034 None,
1035 );
1036
1037 self.recovery(false, RecoveryReason::Adhoc)
1040 .instrument(span)
1041 .await;
1042 }
1043}
1044
1045impl<C> GlobalBarrierWorker<C> {
1046 pub(super) fn report_collect_failure(env: &MetaSrvEnv, error: &MetaError) {
1048 use risingwave_pb::meta::event_log;
1050 let event = event_log::EventCollectBarrierFail {
1051 error: error.to_report_string(),
1052 };
1053 env.event_log_manager_ref()
1054 .add_event_logs(vec![event_log::Event::CollectBarrierFail(event)]);
1055 }
1056}
1057
1058mod retry_strategy {
1059 use std::time::Duration;
1060
1061 use risingwave_common::util::retry::exponential_backoff;
1062 use tokio_retry::strategy::jitter;
1063
1064 const RECOVERY_RETRY_BASE_INTERVAL: u64 = 20;
1066 const RECOVERY_RETRY_MAX_INTERVAL: Duration = Duration::from_secs(5);
1068
1069 pub(crate) type RetryBackoffFuture = std::pin::Pin<Box<tokio::time::Sleep>>;
1088
1089 pub(crate) fn get_retry_backoff_future(duration: Duration) -> RetryBackoffFuture {
1090 Box::pin(tokio::time::sleep(duration))
1091 }
1092
1093 pub(crate) type RetryBackoffStrategy =
1094 impl Iterator<Item = RetryBackoffFuture> + Send + 'static;
1095
1096 #[inline(always)]
1098 pub(crate) fn get_retry_strategy() -> impl Iterator<Item = Duration> + Send + 'static {
1099 exponential_backoff(
1100 Duration::from_millis(RECOVERY_RETRY_BASE_INTERVAL),
1101 RECOVERY_RETRY_BASE_INTERVAL,
1102 RECOVERY_RETRY_MAX_INTERVAL,
1103 )
1104 .map(jitter)
1105 }
1106
1107 #[define_opaque(RetryBackoffStrategy)]
1108 pub(crate) fn get_retry_backoff_strategy() -> RetryBackoffStrategy {
1109 get_retry_strategy().map(get_retry_backoff_future)
1110 }
1111}
1112
1113pub(crate) use retry_strategy::*;
1114use risingwave_common::error::tonic::extra::{Score, ScoredError};
1115use risingwave_pb::meta::event_log::{Event, EventRecovery};
1116
1117use crate::barrier::partial_graph::{
1118 PartialGraphEvent, PartialGraphManager, PartialGraphManagerEvent, WorkerEvent,
1119};
1120
1121impl<C: GlobalBarrierWorkerContext> GlobalBarrierWorker<C> {
1122 pub async fn recovery(&mut self, is_paused: bool, recovery_reason: RecoveryReason) {
1130 let reason_str = match &recovery_reason {
1131 RecoveryReason::Bootstrap => "bootstrap".to_owned(),
1132 RecoveryReason::Failover(err) => {
1133 format!("failed over: {}", err.as_report())
1134 }
1135 RecoveryReason::Adhoc => "adhoc recovery".to_owned(),
1136 };
1137 self.context
1138 .abort_and_mark_blocked(RecoveryStart::Global, recovery_reason)
1139 .await
1140 .expect("sink coordinator manager should be running during global recovery");
1141
1142 self.partial_graph_manager.clear_worker();
1145
1146 let failed_databases = self.recovery_inner(is_paused, reason_str).await;
1147 self.context
1148 .mark_ready(MarkReadyOptions::Global { failed_databases })
1149 .await
1150 .expect("sink coordinator manager should be running after global recovery");
1151 }
1152
1153 #[await_tree::instrument("recovery({recovery_reason})")]
1154 async fn recovery_inner(
1155 &mut self,
1156 is_paused: bool,
1157 recovery_reason: String,
1158 ) -> HashMap<DatabaseId, HashSet<JobId>> {
1159 let event_log_manager_ref = self.env.event_log_manager_ref();
1160
1161 tracing::info!("recovery start!");
1162 event_log_manager_ref.add_event_logs(vec![Event::Recovery(
1163 EventRecovery::global_recovery_start(recovery_reason.clone()),
1164 )]);
1165
1166 let retry_strategy = get_retry_strategy();
1167
1168 let recovery_timer = GLOBAL_META_METRICS
1171 .recovery_latency
1172 .with_label_values(&["global"])
1173 .start_timer();
1174
1175 let enable_per_database_isolation = self.enable_per_database_isolation();
1176
1177 let recovery_future = tokio_retry::Retry::spawn(retry_strategy, || async {
1178 self.env.stream_client_pool().invalidate_all();
1179 self.context
1182 .notify_creating_job_failed(None, recovery_reason.clone())
1183 .await;
1184
1185 let runtime_info_snapshot = self
1186 .context
1187 .reload_runtime_info()
1188 .await?;
1189 let BarrierWorkerRuntimeInfoSnapshot {
1190 active_streaming_nodes,
1191 recovery_context,
1192 mut state_table_committed_epochs,
1193 mut state_table_log_epochs,
1194 mut mv_depended_subscriptions,
1195 mut creating_jobs,
1196 hummock_version_stats,
1197 database_infos,
1198 mut cdc_table_snapshot_splits,
1199 } = runtime_info_snapshot;
1200
1201 let database_job_ids = recovery_context
1205 .fragment_context
1206 .streaming_job_databases
1207 .iter()
1208 .fold(HashMap::<_, HashSet<_>>::new(), |mut job_ids, (job_id, database_id)| {
1209 job_ids
1210 .entry(*database_id)
1211 .or_default()
1212 .insert(*job_id);
1213 job_ids
1214 });
1215 let mut partial_graph_manager = PartialGraphManager::recover(
1216 self.env.clone(),
1217 active_streaming_nodes.current(),
1218 self.context.clone(),
1219 )
1220 .await;
1221 {
1222 let mut empty_databases = HashSet::new();
1223 let mut collected_databases = HashMap::new();
1224 let mut collecting_databases = HashMap::new();
1225 let mut failed_databases = HashMap::new();
1226 for &database_id in recovery_context.fragment_context.database_map.keys() {
1227 let mut recoverer = partial_graph_manager.start_recover();
1228 let result: MetaResult<_> = try {
1229 let Some(rendered_info) = render_runtime_info(
1230 self.env.actor_id_generator(),
1231 &active_streaming_nodes,
1232 recoverer.control_stream_manager(),
1233 &recovery_context,
1234 database_id,
1235 )
1236 .inspect_err(|err: &MetaError| {
1237 warn!(%database_id, err = %err.as_report(), "render runtime info failed");
1238 })? else {
1239 empty_databases.insert(database_id);
1240 continue;
1241 };
1242 BarrierWorkerRuntimeInfoSnapshot::validate_database_info(
1243 database_id,
1244 &rendered_info.job_infos,
1245 &active_streaming_nodes,
1246 &rendered_info.stream_actors,
1247 &state_table_committed_epochs,
1248 )
1249 .inspect_err(|err| {
1250 warn!(%database_id, err = %err.as_report(), "rendered runtime info failed validation");
1251 })?;
1252 let RenderedDatabaseRuntimeInfo {
1253 job_infos,
1254 stream_actors,
1255 mut source_splits,
1256 batch_refresh,
1257 iceberg_v3,
1258 } = rendered_info;
1259 recoverer.inject_database_initial_barrier(
1260 database_id,
1261 job_infos,
1262 &recovery_context.job_extra_info,
1263 &mut state_table_committed_epochs,
1264 &mut state_table_log_epochs,
1265 &recovery_context.fragment_relations,
1266 &stream_actors,
1267 &mut source_splits,
1268 &mut creating_jobs,
1269 &mut mv_depended_subscriptions,
1270 is_paused,
1271 &hummock_version_stats,
1272 &mut cdc_table_snapshot_splits,
1273 batch_refresh,
1274 iceberg_v3,
1275 )?
1276 };
1277 let collector = match result {
1278 Ok(database) => {
1279 DatabaseInitialBarrierCollector {
1280 database_id,
1281 initializing_partial_graphs: recoverer.all_initializing(),
1282 database,
1283 }
1284 }
1285 Err(e) => {
1286 warn!(%database_id, e = %e.as_report(), "failed to inject database initial barrier");
1287 assert!(failed_databases.insert(database_id, recoverer.failed()).is_none(), "non-duplicate");
1288 continue;
1289 }
1290 };
1291 if !collector.is_collected() {
1292 assert!(collecting_databases.insert(database_id, collector).is_none());
1293 } else {
1294 warn!(%database_id, "database has no node to inject initial barrier");
1295 assert!(collected_databases.insert(database_id, collector.finish()).is_none());
1296 }
1297 }
1298 if !empty_databases.is_empty() {
1299 info!(?empty_databases, "empty database in global recovery");
1300 }
1301 while !collecting_databases.is_empty() {
1302 match partial_graph_manager.next_event(&self.context).await {
1303 PartialGraphManagerEvent::Worker(_, WorkerEvent::WorkerConnected) => {
1304 }
1306 PartialGraphManagerEvent::Worker(worker_id, WorkerEvent::WorkerError { err, affected_partial_graphs }) => {
1307 let affected_databases: HashSet<_> = affected_partial_graphs.into_iter().map(|partial_graph_id| {
1308 let (database_id, _) = from_partial_graph_id(partial_graph_id);
1309 database_id
1310 }).collect();
1311 warn!(%worker_id, err = %err.as_report(), "worker node failure during recovery");
1312 for (failed_database_id, collector) in collecting_databases.extract_if(|database_id, collector| {
1313 !collector.is_valid_after_worker_err(worker_id) || affected_databases.contains(database_id)
1314 }) {
1315 warn!(%failed_database_id, %worker_id, "database failed to recovery in global recovery due to worker node err");
1316 let resetting_partial_graphs: HashSet<_> = collector.all_partial_graphs().collect();
1317 partial_graph_manager.reset_partial_graphs(resetting_partial_graphs.iter().copied());
1318 assert!(failed_databases.insert(failed_database_id, resetting_partial_graphs).is_none());
1319 }
1320 }
1321 PartialGraphManagerEvent::PartialGraph(partial_graph_id, event) => {
1322 match event {
1323 PartialGraphEvent::BarrierCollected(_) => {
1324 unreachable!("no barrier collected event on initializing")
1325 }
1326 PartialGraphEvent::Reset(_) => {
1327 let (database_id, _) =
1330 from_partial_graph_id(partial_graph_id);
1331 let resetting_partial_graphs = failed_databases
1332 .get_mut(&database_id)
1333 .expect("reset partial graph should belong to a failed database");
1334 assert!(
1335 resetting_partial_graphs.remove(&partial_graph_id),
1336 "partial graph {partial_graph_id} should be resetting"
1337 );
1338 }
1339 PartialGraphEvent::Error(worker_id) => {
1340 let (database_id, _) = from_partial_graph_id(partial_graph_id);
1341 if let Some(collector) = collecting_databases.remove(&database_id) {
1342 warn!(%database_id, %worker_id, "database reset during global recovery");
1343 let resetting_partial_graphs: HashSet<_> = collector.all_partial_graphs().collect();
1344 partial_graph_manager.reset_partial_graphs(resetting_partial_graphs.iter().copied());
1345 assert!(failed_databases.insert(database_id, resetting_partial_graphs).is_none());
1346 } else if let Some(database) = collected_databases.remove(&database_id) {
1347 warn!(%database_id, %worker_id, "database initialized but later reset during global recovery");
1348 let resetting_partial_graphs: HashSet<_> = database_partial_graphs(database_id, database.independent_checkpoint_job_controls.keys().copied()).collect();
1349 partial_graph_manager.reset_partial_graphs(resetting_partial_graphs.iter().copied());
1350 assert!(failed_databases.insert(database_id, resetting_partial_graphs).is_none());
1351 } else {
1352 assert!(failed_databases.contains_key(&database_id));
1353 }
1354 }
1355 PartialGraphEvent::Initialized => {
1356 let (database_id, _) = from_partial_graph_id(partial_graph_id);
1357 if failed_databases.contains_key(&database_id) {
1358 assert!(!collecting_databases.contains_key(&database_id));
1359 continue;
1361 }
1362 let Entry::Occupied(mut entry) = collecting_databases.entry(database_id) else {
1363 unreachable!("should exist")
1364 };
1365 let collector = entry.get_mut();
1366 collector.partial_graph_initialized(partial_graph_id);
1367 if collector.is_collected() {
1368 let collector = entry.remove();
1369 assert!(collected_databases.insert(database_id, collector.finish()).is_none());
1370 }
1371 }
1372 }
1373 }
1374 }
1375 }
1376 debug!("collected initial barrier");
1377 if !creating_jobs.is_empty() {
1378 warn!(job_ids = ?creating_jobs.iter().collect_vec(), "unused recovered creating jobs in recovery");
1379 }
1380 if !mv_depended_subscriptions.is_empty() {
1381 warn!(?mv_depended_subscriptions, "unused subscription infos in recovery");
1382 }
1383 if !state_table_committed_epochs.is_empty() {
1384 warn!(?state_table_committed_epochs, "unused state table committed epoch in recovery");
1385 }
1386 if !enable_per_database_isolation && !failed_databases.is_empty() {
1387 return Err(anyhow!(
1388 "global recovery failed due to failure of databases {:?}",
1389 failed_databases.keys().collect_vec()).into()
1390 );
1391 }
1392 let failed_database_job_ids = failed_databases
1393 .keys()
1394 .map(|database_id| {
1395 (
1396 *database_id,
1397 database_job_ids
1398 .get(database_id)
1399 .cloned()
1400 .unwrap_or_default(),
1401 )
1402 })
1403 .collect();
1404 let checkpoint_control = CheckpointControl::recover(
1405 collected_databases,
1406 failed_databases,
1407 hummock_version_stats,
1408 self.env.clone(),
1409 );
1410
1411 let reader = self.env.system_params_reader().await;
1412 let checkpoint_frequency = reader.checkpoint_frequency();
1413 let barrier_interval = Duration::from_millis(reader.barrier_interval_ms() as u64);
1414 let periodic_barriers = PeriodicBarriers::new(
1415 barrier_interval,
1416 checkpoint_frequency,
1417 database_infos,
1418 );
1419
1420 self.context
1421 .refresh_table_refill_runtime_state_after_recovery()
1422 .await?;
1423
1424 Ok((
1425 active_streaming_nodes,
1426 partial_graph_manager,
1427 checkpoint_control,
1428 periodic_barriers,
1429 failed_database_job_ids,
1430 ))
1431 }
1432 }.inspect_err(|err: &MetaError| {
1433 tracing::error!(error = %err.as_report(), "recovery failed");
1434 event_log_manager_ref.add_event_logs(vec![Event::Recovery(
1435 EventRecovery::global_recovery_failure(recovery_reason.clone(), err.to_report_string()),
1436 )]);
1437 GLOBAL_META_METRICS.recovery_failure_cnt.with_label_values(&["global"]).inc();
1438 }))
1439 .instrument(tracing::info_span!("recovery_attempt"));
1440
1441 let mut recover_txs = vec![];
1442 let mut update_barrier_requests = vec![];
1443 pin_mut!(recovery_future);
1444 let mut request_rx_closed = false;
1445 let new_state = loop {
1446 select! {
1447 biased;
1448 new_state = &mut recovery_future => {
1449 break new_state.expect("Retry until recovery success.");
1450 }
1451 request = pin!(self.request_rx.recv()), if !request_rx_closed => {
1452 let Some(request) = request else {
1453 warn!("request rx channel closed during recovery");
1454 request_rx_closed = true;
1455 continue;
1456 };
1457 match request {
1458 BarrierManagerRequest::GetBackfillProgress(tx) => {
1459 let _ = tx.send(Err(anyhow!("cluster under recovery[{}]", recovery_reason).into()));
1460 }
1461 BarrierManagerRequest::GetFragmentBackfillProgress(tx) => {
1462 let _ = tx.send(Err(anyhow!("cluster under recovery[{}]", recovery_reason).into()));
1463 }
1464 BarrierManagerRequest::GetCdcProgress(tx) => {
1465 let _ = tx.send(Err(anyhow!("cluster under recovery[{}]", recovery_reason).into()));
1466 }
1467 BarrierManagerRequest::AdhocRecovery(tx) => {
1468 recover_txs.push(tx);
1469 }
1470 BarrierManagerRequest::UpdateDatabaseBarrier(request) => {
1471 update_barrier_requests.push(request);
1472 }
1473 }
1474 }
1475 }
1476 };
1477
1478 let duration = recovery_timer.stop_and_record();
1479
1480 let (
1481 active_streaming_nodes,
1482 partial_graph_manager,
1483 checkpoint_control,
1484 periodic_barriers,
1485 failed_database_job_ids,
1486 ) = new_state;
1487 self.active_streaming_nodes = active_streaming_nodes;
1488 self.partial_graph_manager = partial_graph_manager;
1489 self.checkpoint_control = checkpoint_control;
1490 self.periodic_barriers = periodic_barriers;
1491
1492 tracing::info!("recovery success");
1493
1494 for UpdateDatabaseBarrierRequest {
1495 database_id,
1496 barrier_interval_ms,
1497 checkpoint_frequency,
1498 sender,
1499 } in update_barrier_requests
1500 {
1501 self.periodic_barriers.update_database_barrier(
1502 database_id,
1503 barrier_interval_ms,
1504 checkpoint_frequency,
1505 );
1506 let _ = sender.send(());
1507 }
1508
1509 for tx in recover_txs {
1510 let _ = tx.send(());
1511 }
1512
1513 let recovering_databases = self
1514 .checkpoint_control
1515 .recovering_databases()
1516 .map(|database| database.as_raw_id())
1517 .collect_vec();
1518 let running_databases = self
1519 .checkpoint_control
1520 .running_databases()
1521 .map(|database| database.as_raw_id())
1522 .collect_vec();
1523
1524 event_log_manager_ref.add_event_logs(vec![Event::Recovery(
1525 EventRecovery::global_recovery_success(
1526 recovery_reason.clone(),
1527 duration as f32,
1528 running_databases,
1529 recovering_databases,
1530 ),
1531 )]);
1532
1533 self.env
1534 .notification_manager()
1535 .notify_frontend_without_version(Operation::Update, Info::Recovery(Recovery {}));
1536 self.env
1537 .notification_manager()
1538 .notify_compute_without_version(Operation::Update, Info::Recovery(Recovery {}));
1539
1540 failed_database_job_ids
1541 }
1542}