1use 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
74pub(super) struct GlobalBarrierWorker<C> {
84 enable_recovery: bool,
86
87 periodic_barriers: PeriodicBarriers,
89
90 system_enable_per_database_isolation: bool,
92
93 pub(super) context: Arc<C>,
94
95 env: MetaSrvEnv,
96
97 checkpoint_control: CheckpointControl,
98
99 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 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 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 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 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 #[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 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 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 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 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 loop {
511 tokio::select! {
512 biased;
513
514 _ = &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 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 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 }
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 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 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 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 pub async fn clear_on_err(&mut self, err: &MetaError) {
862 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 async fn handle_batch_refresh_trigger(
901 &mut self,
902 database_id: DatabaseId,
903 job_id: JobId,
904 ) -> MetaResult<()> {
905 let last_committed_epoch = self
907 .checkpoint_control
908 .get_batch_refresh_trigger_info(database_id, job_id);
909
910 let context = self
912 .context
913 .load_batch_refresh_trigger_context(job_id, database_id, last_committed_epoch)
914 .await?;
915
916 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 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 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 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 self.recovery(false, RecoveryReason::Adhoc)
990 .instrument(span)
991 .await;
992 }
993}
994
995impl<C> GlobalBarrierWorker<C> {
996 pub(super) fn report_collect_failure(env: &MetaSrvEnv, error: &MetaError) {
998 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 const RECOVERY_RETRY_BASE_INTERVAL: u64 = 20;
1015 const RECOVERY_RETRY_MAX_INTERVAL: Duration = Duration::from_secs(5);
1017
1018 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 #[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 pub async fn recovery(&mut self, is_paused: bool, recovery_reason: RecoveryReason) {
1076 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 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 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 }
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 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 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 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}