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