1use std::collections::hash_map::Entry;
16use std::collections::{HashMap, HashSet, VecDeque};
17use std::future::Future;
18use std::sync::Arc;
19
20use anyhow::{Context, anyhow};
21use assert_matches::assert_matches;
22use await_tree::InstrumentAwait;
23use itertools::Itertools;
24use parking_lot::Mutex;
25use risingwave_common::catalog::{DatabaseId, TableId};
26use risingwave_common::id::JobId;
27use risingwave_hummock_sdk::HummockVersionId;
28use risingwave_pb::catalog::Database;
29use rw_futures_util::pending_on_none;
30use tokio::select;
31use tokio::sync::watch;
32use tokio::time::{Duration, Instant};
33use tokio_stream::wrappers::IntervalStream;
34use tokio_stream::{StreamExt, StreamMap};
35use tracing::{info, warn};
36
37use super::{Command, Scheduled};
38use crate::barrier::context::GlobalBarrierWorkerContext;
39use crate::hummock::HummockManagerRef;
40use crate::notification::{Notifier, wait_collection};
41use crate::rpc::metrics::GLOBAL_META_METRICS;
42use crate::{MetaError, MetaResult};
43
44pub(super) struct NewBarrier<'a> {
45 pub database_id: DatabaseId,
46 pub command: Option<(Command, Notifier)>,
47 pub span: tracing::Span,
48 pub checkpoint: bool,
49 pub periodic_barriers: &'a mut PeriodicBarriers,
50 periodic: bool,
51}
52
53impl NewBarrier<'_> {
54 pub(super) fn injected(self) {
59 if !self.periodic {
60 self.periodic_barriers
61 .reset_database_timer(self.database_id);
62 }
63 self.periodic_barriers
64 .update_num_uncheckpointed_barrier(self.database_id, self.checkpoint);
65 }
66
67 #[cfg(test)]
68 pub(super) fn for_test(
69 database_id: DatabaseId,
70 command: Option<(Command, Notifier)>,
71 checkpoint: bool,
72 periodic_barriers: &mut PeriodicBarriers,
73 ) -> NewBarrier<'_> {
74 NewBarrier {
75 database_id,
76 command,
77 span: tracing::Span::none(),
78 checkpoint,
79 periodic_barriers,
80 periodic: false,
81 }
82 }
83}
84
85struct Inner {
87 queue: Mutex<ScheduledQueue>,
88
89 changed_tx: watch::Sender<()>,
91}
92
93#[derive(Debug)]
94enum QueueStatus {
95 Ready,
97 Blocked(String),
99}
100
101impl QueueStatus {
102 fn is_blocked(&self) -> bool {
103 matches!(self, Self::Blocked(_))
104 }
105}
106
107struct ScheduledQueueItem {
108 command: Command,
109 notifier: Notifier,
110 span: tracing::Span,
111}
112
113struct StatusQueue<T> {
114 queue: T,
115 status: QueueStatus,
116}
117
118type DatabaseScheduledQueue = StatusQueue<VecDeque<ScheduledQueueItem>>;
119type ScheduledQueue = StatusQueue<HashMap<DatabaseId, DatabaseScheduledQueue>>;
120
121impl DatabaseScheduledQueue {
122 fn new(status: QueueStatus) -> Self {
123 Self {
124 queue: Default::default(),
125 status,
126 }
127 }
128}
129
130impl<T> StatusQueue<T> {
131 fn mark_blocked(&mut self, reason: String) {
132 self.status = QueueStatus::Blocked(reason);
133 }
134
135 fn mark_ready(&mut self) -> bool {
136 let prev_blocked = self.status.is_blocked();
137 self.status = QueueStatus::Ready;
138 prev_blocked
139 }
140
141 fn validate_item(&mut self, command: &Command) -> MetaResult<()> {
142 if let QueueStatus::Blocked(reason) = &self.status
148 && !matches!(
149 command,
150 Command::DropStreamingJobs { .. } | Command::DropSubscription { .. }
151 )
152 {
153 return Err(MetaError::unavailable(reason));
154 }
155 Ok(())
156 }
157}
158
159fn tracing_span() -> tracing::Span {
160 if tracing::Span::current().is_none() {
161 tracing::Span::none()
162 } else {
163 tracing::info_span!(
164 "barrier",
165 checkpoint = tracing::field::Empty,
166 epoch = tracing::field::Empty
167 )
168 }
169}
170
171#[derive(Clone)]
174pub struct BarrierScheduler {
175 inner: Arc<Inner>,
176
177 hummock_manager: HummockManagerRef,
179}
180
181impl BarrierScheduler {
182 pub fn new_pair(hummock_manager: HummockManagerRef) -> (Self, ScheduledBarriers) {
185 let inner = Arc::new(Inner {
186 queue: Mutex::new(ScheduledQueue {
187 queue: Default::default(),
188 status: QueueStatus::Ready,
189 }),
190 changed_tx: watch::channel(()).0,
191 });
192
193 (
194 Self {
195 inner: inner.clone(),
196 hummock_manager,
197 },
198 ScheduledBarriers { inner },
199 )
200 }
201
202 fn push(
204 &self,
205 database_id: DatabaseId,
206 scheduleds: impl IntoIterator<Item = (Command, Notifier)>,
207 ) -> MetaResult<()> {
208 let mut queue = self.inner.queue.lock();
209 let scheduleds = scheduleds.into_iter().collect_vec();
210 scheduleds
211 .iter()
212 .try_for_each(|(command, _)| queue.validate_item(command))?;
213 let queue = queue
214 .queue
215 .entry(database_id)
216 .or_insert_with(|| DatabaseScheduledQueue::new(QueueStatus::Ready));
217 scheduleds
218 .iter()
219 .try_for_each(|(command, _)| queue.validate_item(command))?;
220 for (command, notifier) in scheduleds {
221 queue.queue.push_back(ScheduledQueueItem {
222 command,
223 notifier,
224 span: tracing_span(),
225 });
226 if queue.queue.len() == 1 {
227 self.inner.changed_tx.send(()).ok();
228 }
229 }
230 Ok(())
231 }
232
233 pub fn try_cancel_scheduled_create(&self, database_id: DatabaseId, job_id: JobId) -> bool {
235 let queue = &mut self.inner.queue.lock();
236 let Some(queue) = queue.queue.get_mut(&database_id) else {
237 return false;
238 };
239
240 if let Some(idx) = queue.queue.iter().position(|scheduled| {
241 if let Command::CreateStreamingJob { info, .. } = &scheduled.command
242 && info.stream_job_fragments.stream_job_id() == job_id
243 {
244 true
245 } else {
246 false
247 }
248 }) {
249 queue.queue.remove(idx).unwrap();
250 true
251 } else {
252 false
253 }
254 }
255
256 #[await_tree::instrument("run_command({})", command)]
261 pub async fn run_command(&self, database_id: DatabaseId, command: Command) -> MetaResult<()> {
262 self.schedule_command(database_id, command)?.await
263 }
264
265 pub fn schedule_command(
267 &self,
268 database_id: DatabaseId,
269 command: Command,
270 ) -> MetaResult<impl Future<Output = MetaResult<()>> + use<>> {
271 tracing::trace!("schedule_command: {:?}", command);
272 let (notifier, started_rx) = Notifier::new();
273 self.push(database_id, vec![(command, notifier)])?;
274 Ok(async move {
275 let collect_rxs = started_rx
276 .instrument_await("wait_injected")
277 .await
278 .ok()
279 .context("failed to inject barrier")??;
280 tracing::trace!(
281 collection_count = collect_rxs.len(),
282 "waiting for collect_rx"
283 );
284 wait_collection(collect_rxs)
286 .instrument_await("wait_collected")
287 .await
288 })
289 }
290
291 pub fn run_command_no_wait(&self, database_id: DatabaseId, command: Command) -> MetaResult<()> {
293 tracing::trace!("run_command_no_wait: {:?}", command);
294 let (notifier, _started_rx) = Notifier::new();
295 self.push(database_id, vec![(command, notifier)])
296 }
297
298 pub async fn flush(&self, database_id: DatabaseId) -> MetaResult<HummockVersionId> {
300 let start = Instant::now();
301
302 tracing::debug!("start barrier flush");
303 self.run_command(database_id, Command::Flush).await?;
304
305 let elapsed = Instant::now().duration_since(start);
306 tracing::debug!("barrier flushed in {:?}", elapsed);
307
308 let version_id = self.hummock_manager.get_version_id().await;
309 Ok(version_id)
310 }
311}
312
313pub struct ScheduledBarriers {
315 inner: Arc<Inner>,
316}
317
318#[derive(Debug)]
320pub struct DatabaseBarrierState {
321 barrier_interval: Option<Duration>,
322 checkpoint_frequency: Option<u64>,
323 num_uncheckpointed_barrier: u64,
325}
326
327impl DatabaseBarrierState {
328 fn new(barrier_interval_ms: Option<u32>, checkpoint_frequency: Option<u64>) -> Self {
329 Self {
330 barrier_interval: barrier_interval_ms.map(|ms| Duration::from_millis(ms as u64)),
331 checkpoint_frequency,
332 num_uncheckpointed_barrier: 0,
333 }
334 }
335}
336
337#[derive(Default, Debug)]
339pub struct PeriodicBarriers {
340 sys_barrier_interval: Duration,
342 sys_checkpoint_frequency: u64,
343 databases: HashMap<DatabaseId, DatabaseBarrierState>,
345 timer_streams: StreamMap<DatabaseId, IntervalStream>,
348 force_checkpoint_databases: HashSet<DatabaseId>,
349}
350
351impl PeriodicBarriers {
352 pub(super) fn new(
353 sys_barrier_interval: Duration,
354 sys_checkpoint_frequency: u64,
355 database_infos: Vec<Database>,
356 ) -> Self {
357 let mut databases = HashMap::with_capacity(database_infos.len());
358 let mut timer_streams = StreamMap::with_capacity(database_infos.len());
359 database_infos.into_iter().for_each(|database| {
360 let database_id: DatabaseId = database.id;
361 let barrier_interval_ms = database.barrier_interval_ms;
362 let checkpoint_frequency = database.checkpoint_frequency;
363 databases.insert(
364 database_id,
365 DatabaseBarrierState::new(barrier_interval_ms, checkpoint_frequency),
366 );
367 let duration = if let Some(ms) = barrier_interval_ms {
368 Duration::from_millis(ms as u64)
369 } else {
370 sys_barrier_interval
371 };
372
373 let interval_stream = Self::new_interval_stream(duration, &database_id);
375 timer_streams.insert(database_id, interval_stream);
376 });
377 Self {
378 sys_barrier_interval,
379 sys_checkpoint_frequency,
380 databases,
381 timer_streams,
382 force_checkpoint_databases: Default::default(),
383 }
384 }
385
386 fn new_interval_stream(duration: Duration, database_id: &DatabaseId) -> IntervalStream {
388 GLOBAL_META_METRICS
389 .barrier_interval_by_database
390 .with_label_values(&[&database_id.to_string()])
391 .set(duration.as_millis_f64());
392 let mut interval = tokio::time::interval(duration);
393 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
394 IntervalStream::new(interval)
395 }
396
397 pub(super) fn set_sys_barrier_interval(&mut self, duration: Duration) {
399 if self.sys_barrier_interval == duration {
400 return;
401 }
402 self.sys_barrier_interval = duration;
403 for (db_id, db_state) in &mut self.databases {
405 if db_state.barrier_interval.is_none() {
406 let interval_stream = Self::new_interval_stream(duration, db_id);
407 self.timer_streams.insert(*db_id, interval_stream);
408 }
409 }
410 }
411
412 pub fn set_sys_checkpoint_frequency(&mut self, frequency: u64) {
414 if self.sys_checkpoint_frequency == frequency {
415 return;
416 }
417 self.sys_checkpoint_frequency = frequency;
418 for db_state in self.databases.values_mut() {
420 if db_state.checkpoint_frequency.is_none() {
421 db_state.num_uncheckpointed_barrier = 0;
422 }
423 }
424 }
425
426 pub(super) fn update_database_barrier(
427 &mut self,
428 database_id: DatabaseId,
429 barrier_interval_ms: Option<u32>,
430 checkpoint_frequency: Option<u64>,
431 ) {
432 match self.databases.entry(database_id) {
433 Entry::Occupied(mut entry) => {
434 let db_state = entry.get_mut();
435 db_state.barrier_interval =
436 barrier_interval_ms.map(|ms| Duration::from_millis(ms as u64));
437 db_state.checkpoint_frequency = checkpoint_frequency;
438 db_state.num_uncheckpointed_barrier = 0;
440 }
441 Entry::Vacant(entry) => {
442 entry.insert(DatabaseBarrierState::new(
443 barrier_interval_ms,
444 checkpoint_frequency,
445 ));
446 }
447 }
448
449 let duration = if let Some(ms) = barrier_interval_ms {
451 Duration::from_millis(ms as u64)
452 } else {
453 self.sys_barrier_interval
454 };
455
456 let interval_stream = Self::new_interval_stream(duration, &database_id);
457 self.timer_streams.insert(database_id, interval_stream);
458 }
459
460 pub fn force_checkpoint_in_next_barrier(&mut self, database_id: DatabaseId) {
462 if self.databases.contains_key(&database_id) {
463 self.force_checkpoint_databases.insert(database_id);
464 } else {
465 warn!(
466 ?database_id,
467 "force checkpoint in next barrier for non-existing database"
468 );
469 }
470 }
471
472 fn reset_database_timer(&mut self, database_id: DatabaseId) {
473 assert!(
475 self.databases.contains_key(&database_id),
476 "database {} not found in scheduled barriers",
477 database_id
478 );
479 assert!(
480 self.timer_streams.contains_key(&database_id),
481 "timer stream for database {} not found in scheduled barriers",
482 database_id
483 );
484 for (db_id, timer_stream) in self.timer_streams.iter_mut() {
486 if *db_id == database_id {
487 timer_stream.as_mut().reset();
488 }
489 }
490 }
491
492 #[await_tree::instrument]
493 pub(super) async fn next_barrier(
494 &mut self,
495 context: &impl GlobalBarrierWorkerContext,
496 ) -> NewBarrier<'_> {
497 let force_checkpoint_database = self.force_checkpoint_databases.extract_if(|_| true).next();
498 if let Some(database_id) = force_checkpoint_database {
499 NewBarrier {
500 database_id,
501 command: None,
502 span: tracing_span(),
503 checkpoint: true,
504 periodic_barriers: self,
505 periodic: false,
506 }
507 } else {
508 select! {
509 biased;
510 scheduled = context.next_scheduled() => {
511 let database_id = scheduled.database_id;
512 let checkpoint = scheduled.command.need_checkpoint() || self.try_get_checkpoint(database_id);
513 NewBarrier {
514 database_id: scheduled.database_id,
515 command: Some((scheduled.command, scheduled.notifier)),
516 span: scheduled.span,
517 checkpoint,
518 periodic_barriers: self,
519 periodic: false,
520 }
521 },
522 (database_id, _instant) = pending_on_none(self.timer_streams.next()) => {
525 let checkpoint = self.try_get_checkpoint(database_id);
526 NewBarrier {
527 database_id,
528 command: None,
529 span: tracing_span(),
530 checkpoint,
531 periodic_barriers: self,
532 periodic: true,
533 }
534 }
535 }
536 }
537 }
538
539 fn try_get_checkpoint(&self, database_id: DatabaseId) -> bool {
541 let db_state = self.databases.get(&database_id).unwrap();
542 let checkpoint_frequency = db_state
543 .checkpoint_frequency
544 .unwrap_or(self.sys_checkpoint_frequency);
545 db_state.num_uncheckpointed_barrier + 1 >= checkpoint_frequency
546 }
547
548 fn update_num_uncheckpointed_barrier(&mut self, database_id: DatabaseId, checkpoint: bool) {
550 let db_state = self.databases.get_mut(&database_id).unwrap();
551 if checkpoint {
552 db_state.num_uncheckpointed_barrier = 0;
553 } else {
554 db_state.num_uncheckpointed_barrier += 1;
555 }
556 }
557}
558
559impl ScheduledBarriers {
560 pub(super) async fn next_scheduled(&self) -> Scheduled {
561 'outer: loop {
562 let mut rx = self.inner.changed_tx.subscribe();
563 {
564 let mut queue = self.inner.queue.lock();
565 if queue.status.is_blocked() {
566 continue;
567 }
568 for (database_id, queue) in &mut queue.queue {
569 if queue.status.is_blocked() {
570 continue;
571 }
572 if let Some(item) = queue.queue.pop_front() {
573 break 'outer Scheduled {
574 database_id: *database_id,
575 command: item.command,
576 notifier: item.notifier,
577 span: item.span,
578 };
579 }
580 }
581 }
582 rx.changed().await.unwrap();
583 }
584 }
585}
586
587pub(super) enum MarkReadyOptions {
588 Database(DatabaseId),
589 Global {
590 failed_databases: HashMap<DatabaseId, HashSet<JobId>>,
591 },
592}
593
594pub(super) struct PreApplyDropCancel {
595 pub streaming_job_ids: Vec<JobId>,
596 pub dropped_state_table_ids: Vec<TableId>,
597}
598
599impl ScheduledBarriers {
600 pub(super) fn pre_apply_drop_cancel(
602 &self,
603 database_id: Option<DatabaseId>,
604 ) -> PreApplyDropCancel {
605 self.pre_apply_drop_cancel_scheduled(database_id)
606 }
607
608 pub(super) fn abort_and_mark_blocked(
611 &self,
612 database_id: Option<DatabaseId>,
613 reason: impl Into<String>,
614 ) {
615 let mut queue = self.inner.queue.lock();
616 fn database_blocked_reason(database_id: DatabaseId, reason: &String) -> String {
617 format!("database {} unavailable {}", database_id, reason)
618 }
619 fn mark_blocked_and_notify_failed(
620 database_id: DatabaseId,
621 queue: &mut DatabaseScheduledQueue,
622 reason: &String,
623 ) {
624 let reason = database_blocked_reason(database_id, reason);
625 let err: MetaError = anyhow!("{}", reason).into();
626 queue.mark_blocked(reason);
627 while let Some(ScheduledQueueItem { notifier, .. }) = queue.queue.pop_front() {
628 notifier.notify_start_failed(err.clone());
629 }
630 }
631 if let Some(database_id) = database_id {
632 let reason = reason.into();
633 match queue.queue.entry(database_id) {
634 Entry::Occupied(entry) => {
635 let queue = entry.into_mut();
636 if queue.status.is_blocked() {
637 if cfg!(debug_assertions) {
638 panic!("database {} marked as blocked twice", database_id);
639 } else {
640 warn!(?database_id, "database marked as blocked twice");
641 }
642 }
643 info!(?database_id, "database marked as blocked");
644 mark_blocked_and_notify_failed(database_id, queue, &reason);
645 }
646 Entry::Vacant(entry) => {
647 entry.insert(DatabaseScheduledQueue::new(QueueStatus::Blocked(
648 database_blocked_reason(database_id, &reason),
649 )));
650 }
651 }
652 } else {
653 let reason = reason.into();
654 if queue.status.is_blocked() {
655 if cfg!(debug_assertions) {
656 panic!("cluster marked as blocked twice");
657 } else {
658 warn!("cluster marked as blocked twice");
659 }
660 }
661 info!("cluster marked as blocked");
662 queue.mark_blocked(reason.clone());
663 for (database_id, queue) in &mut queue.queue {
664 mark_blocked_and_notify_failed(*database_id, queue, &reason);
665 }
666 }
667 }
668
669 pub(super) fn mark_ready(&self, options: MarkReadyOptions) {
671 let mut queue = self.inner.queue.lock();
672 let queue = &mut *queue;
673 match options {
674 MarkReadyOptions::Database(database_id) => {
675 info!(?database_id, "database marked as ready");
676 let database_queue = queue
677 .queue
678 .entry(database_id)
679 .or_insert_with(|| DatabaseScheduledQueue::new(QueueStatus::Ready));
680 if !database_queue.status.is_blocked() {
681 if cfg!(debug_assertions) {
682 panic!("database {} marked as ready twice", database_id);
683 } else {
684 warn!(?database_id, "database marked as ready twice");
685 }
686 }
687 if database_queue.mark_ready()
688 && !queue.status.is_blocked()
689 && !database_queue.queue.is_empty()
690 {
691 self.inner.changed_tx.send(()).ok();
692 }
693 }
694 MarkReadyOptions::Global { failed_databases } => {
695 if !queue.status.is_blocked() {
696 if cfg!(debug_assertions) {
697 panic!("cluster marked as ready twice");
698 } else {
699 warn!("cluster marked as ready twice");
700 }
701 }
702 info!(
703 failed_database_ids = ?failed_databases.keys().collect_vec(),
704 "cluster marked as ready"
705 );
706 let prev_blocked = queue.mark_ready();
707 for database_id in failed_databases.keys() {
708 queue.queue.entry(*database_id).or_insert_with(|| {
709 DatabaseScheduledQueue::new(QueueStatus::Blocked(format!(
710 "database {} failed to recover in global recovery",
711 database_id
712 )))
713 });
714 }
715 for (database_id, queue) in &mut queue.queue {
716 if !failed_databases.contains_key(database_id) {
717 queue.mark_ready();
718 }
719 }
720 if prev_blocked
721 && queue
722 .queue
723 .values()
724 .any(|database_queue| !database_queue.queue.is_empty())
725 {
726 self.inner.changed_tx.send(()).ok();
727 }
728 }
729 }
730 }
731
732 pub(super) fn pre_apply_drop_cancel_scheduled(
735 &self,
736 database_id: Option<DatabaseId>,
737 ) -> PreApplyDropCancel {
738 let mut queue = self.inner.queue.lock();
739 let mut drop_cancel = PreApplyDropCancel {
740 streaming_job_ids: vec![],
741 dropped_state_table_ids: vec![],
742 };
743
744 let mut pre_apply_drop_cancel = |queue: &mut DatabaseScheduledQueue| {
745 while let Some(ScheduledQueueItem {
746 notifier, command, ..
747 }) = queue.queue.pop_front()
748 {
749 match command {
750 Command::DropStreamingJobs {
751 streaming_job_ids,
752 unregistered_state_table_ids,
753 ..
754 } => {
755 drop_cancel.streaming_job_ids.extend(streaming_job_ids);
756 drop_cancel
757 .dropped_state_table_ids
758 .extend(unregistered_state_table_ids);
759 }
760 Command::DropSubscription { .. } => {}
761 _ => {
762 unreachable!("only drop and cancel streaming jobs should be buffered");
763 }
764 }
765 notifier.start().started();
767 }
768 };
769
770 if let Some(database_id) = database_id {
771 assert_matches!(queue.status, QueueStatus::Ready);
772 if let Some(queue) = queue.queue.get_mut(&database_id) {
773 assert_matches!(queue.status, QueueStatus::Blocked(_));
774 pre_apply_drop_cancel(queue);
775 }
776 } else {
777 assert_matches!(queue.status, QueueStatus::Blocked(_));
778 for queue in queue.queue.values_mut() {
779 pre_apply_drop_cancel(queue);
780 }
781 }
782
783 drop_cancel
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use futures::FutureExt;
790 use risingwave_meta_model::PartialGraphId;
791
792 use super::*;
793 use crate::barrier::RescheduleContext;
794
795 fn create_test_database(
796 id: u32,
797 barrier_interval_ms: Option<u32>,
798 checkpoint_frequency: Option<u64>,
799 ) -> Database {
800 Database {
801 id: id.into(),
802 name: format!("test_db_{}", id),
803 barrier_interval_ms,
804 checkpoint_frequency,
805 ..Default::default()
806 }
807 }
808
809 fn noop_reschedule_intent(database_id: DatabaseId) -> Scheduled {
810 let (notifier, _) = Notifier::new();
811 Scheduled {
812 database_id,
813 command: Command::RescheduleIntent {
814 context: RescheduleContext::empty(),
815 reschedule_plan: None,
816 },
817 notifier,
818 span: tracing::Span::none(),
819 }
820 }
821
822 struct MockGlobalBarrierWorkerContext {
824 scheduled_rx: tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Scheduled>>,
825 }
826
827 impl MockGlobalBarrierWorkerContext {
828 fn new() -> (Self, tokio::sync::mpsc::UnboundedSender<Scheduled>) {
829 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
830 (
831 Self {
832 scheduled_rx: tokio::sync::Mutex::new(rx),
833 },
834 tx,
835 )
836 }
837 }
838
839 impl GlobalBarrierWorkerContext for MockGlobalBarrierWorkerContext {
840 async fn next_scheduled(&self) -> Scheduled {
841 self.scheduled_rx.lock().await.recv().await.unwrap()
842 }
843
844 async fn commit_epoch(
845 &self,
846 _commit_info: crate::hummock::CommitEpochInfo,
847 ) -> MetaResult<risingwave_pb::hummock::HummockVersionStats> {
848 unimplemented!()
849 }
850
851 async fn abort_and_mark_blocked(
852 &self,
853 _recovery: crate::manager::sink_coordination::RecoveryStart,
854 _recovery_reason: &crate::barrier::RecoveryReason,
855 ) -> MetaResult<()> {
856 unimplemented!()
857 }
858
859 async fn mark_ready(
860 &self,
861 _options: MarkReadyOptions,
862 _running_job_ids: HashSet<JobId>,
863 ) -> MetaResult<()> {
864 unimplemented!()
865 }
866
867 async fn resolve_log_store_epoch<'a>(
868 &'a self,
869 _upstream_table_ids: impl Iterator<Item = risingwave_common::catalog::TableId> + Send + 'a,
870 _since_epoch: u64,
871 ) -> MetaResult<crate::barrier::command::SinceTimestampResolvedEpoch> {
872 Ok(Default::default())
873 }
874
875 async fn post_collect_command(
876 &self,
877 _command: crate::barrier::command::PostCollectCommand,
878 ) -> MetaResult<()> {
879 unimplemented!()
880 }
881
882 async fn notify_creating_job_failed(&self, _database_id: Option<DatabaseId>, _err: String) {
883 unimplemented!()
884 }
885
886 async fn finish_creating_job(
887 &self,
888 _job: crate::barrier::progress::TrackingJob,
889 ) -> MetaResult<()> {
890 unimplemented!()
891 }
892
893 async fn new_control_stream(
894 &self,
895 _node: &risingwave_pb::common::WorkerNode,
896 ) -> MetaResult<risingwave_rpc_client::StreamingControlHandle> {
897 unimplemented!()
898 }
899
900 async fn reload_runtime_info(
901 &self,
902 ) -> MetaResult<crate::barrier::BarrierWorkerRuntimeInfoSnapshot> {
903 unimplemented!()
904 }
905
906 async fn reload_database_runtime_info(
907 &self,
908 _database_id: DatabaseId,
909 ) -> MetaResult<crate::barrier::DatabaseRuntimeInfoSnapshot> {
910 unimplemented!()
911 }
912
913 async fn handle_list_finished_source_ids(
914 &self,
915 _list_finished_source_ids: Vec<
916 risingwave_pb::stream_service::barrier_complete_response::PbListFinishedSource,
917 >,
918 ) -> MetaResult<()> {
919 unimplemented!()
920 }
921
922 async fn handle_load_finished_source_ids(
923 &self,
924 _load_finished_source_ids: Vec<
925 risingwave_pb::stream_service::barrier_complete_response::PbLoadFinishedSource,
926 >,
927 ) -> MetaResult<()> {
928 unimplemented!()
929 }
930
931 async fn finish_cdc_table_backfill(&self, _job_id: JobId) -> MetaResult<()> {
932 unimplemented!()
933 }
934
935 async fn handle_refresh_finished_actors(
936 &self,
937 _refresh_finished_actors: Vec<
938 risingwave_pb::stream_service::barrier_complete_response::PbRefreshFinishedActor,
939 >,
940 ) -> MetaResult<()> {
941 unimplemented!()
942 }
943
944 async fn load_batch_refresh_trigger_context(
945 &self,
946 _job_id: JobId,
947 _database_id: DatabaseId,
948 _last_committed_epoch: u64,
949 ) -> MetaResult<crate::barrier::checkpoint::independent_job::BatchRefreshJobTriggerContext>
950 {
951 unimplemented!()
952 }
953
954 async fn pre_commit_iceberg_pk_index_sink_metadata(
955 &self,
956 _metadata: Vec<crate::manager::iceberg_pk_index_sink::IcebergPkIndexPreCommitMetadata>,
957 ) -> MetaResult<Vec<risingwave_meta_model::SinkId>> {
958 unimplemented!()
959 }
960
961 async fn commit_iceberg_pk_index_sink_metadata(
962 &self,
963 _sink_ids: Vec<risingwave_meta_model::SinkId>,
964 ) -> MetaResult<()> {
965 unimplemented!()
966 }
967
968 fn advance_iceberg_pk_index_sink_committed_epochs(
969 &self,
970 _epochs: impl IntoIterator<Item = (PartialGraphId, u64)>,
971 ) {
972 unimplemented!()
973 }
974 }
975
976 #[tokio::test(start_paused = true)]
977 async fn test_next_barrier_with_different_intervals() {
978 let databases = vec![
980 create_test_database(1, Some(50), Some(2)), create_test_database(2, Some(100), Some(3)), create_test_database(3, None, Some(5)), ];
984
985 let mut periodic = PeriodicBarriers::new(
986 Duration::from_millis(200), 10, databases,
989 );
990
991 let (context, _tx) = MockGlobalBarrierWorkerContext::new();
992
993 for _ in 0..3 {
995 let barrier = periodic.next_barrier(&context).await;
996 assert!(barrier.command.is_none()); assert!(!barrier.checkpoint); barrier.injected();
999 }
1000
1001 let start_time = Instant::now();
1004 let barrier = periodic.next_barrier(&context).await;
1005 let mut elapsed = start_time.elapsed();
1006
1007 assert_eq!(barrier.database_id, DatabaseId::from(1));
1009 assert!(barrier.command.is_none()); assert!(barrier.checkpoint); barrier.injected();
1012 assert_eq!(
1014 elapsed,
1015 Duration::from_millis(50),
1016 "Elapsed time exceeded: {:?}",
1017 elapsed
1018 );
1019
1020 let db1_id = DatabaseId::from(1);
1022 let db1_state = periodic.databases.get_mut(&db1_id).unwrap();
1023 assert_eq!(db1_state.num_uncheckpointed_barrier, 0); for _ in 0..2 {
1027 let barrier = periodic.next_barrier(&context).await;
1028 assert!(barrier.command.is_none()); assert!(!barrier.checkpoint); barrier.injected();
1031 }
1032
1033 elapsed = start_time.elapsed();
1034
1035 assert_eq!(
1036 elapsed,
1037 Duration::from_millis(100),
1038 "Elapsed time exceeded: {:?}",
1039 elapsed
1040 );
1041 }
1042
1043 #[tokio::test(start_paused = true)]
1044 async fn test_next_barrier_with_scheduled_command() {
1045 let databases = vec![
1046 create_test_database(1, Some(1000), Some(2)), ];
1048
1049 let mut periodic = PeriodicBarriers::new(Duration::from_millis(1000), 10, databases);
1050
1051 let (context, tx) = MockGlobalBarrierWorkerContext::new();
1052
1053 periodic.next_barrier(&context).await.injected();
1055
1056 let (notifier, _started_rx) = Notifier::new();
1058 let scheduled_command = Scheduled {
1059 database_id: DatabaseId::from(1),
1060 command: Command::Flush,
1061 notifier,
1062 span: tracing::Span::none(),
1063 };
1064
1065 let tx_clone = tx.clone();
1067 tokio::spawn(async move {
1068 tokio::time::sleep(Duration::from_millis(10)).await;
1069 tx_clone.send(scheduled_command).unwrap();
1070 });
1071
1072 let barrier = periodic.next_barrier(&context).await;
1073
1074 assert!(barrier.command.is_some());
1076 assert_eq!(barrier.database_id, DatabaseId::from(1));
1077 assert!(!barrier.periodic);
1078
1079 if let Some((command, _)) = &barrier.command {
1080 assert!(matches!(command, Command::Flush));
1081 }
1082
1083 barrier.injected();
1084 let injected_at = Instant::now();
1085
1086 let barrier = periodic.next_barrier(&context).await;
1088 assert!(barrier.periodic);
1089 assert!(injected_at.elapsed() >= Duration::from_millis(1000));
1090 }
1091
1092 #[tokio::test(start_paused = true)]
1093 async fn test_next_barrier_multiple_databases_timing() {
1094 let databases = vec![
1095 create_test_database(1, Some(30), Some(10)), create_test_database(2, Some(100), Some(10)), ];
1098
1099 let mut periodic = PeriodicBarriers::new(Duration::from_millis(500), 10, databases);
1100
1101 let (context, _tx) = MockGlobalBarrierWorkerContext::new();
1102
1103 for _ in 0..2 {
1105 periodic.next_barrier(&context).await;
1106 }
1107
1108 let mut barrier_counts = HashMap::new();
1109
1110 let mut barriers = Vec::new();
1112 for _ in 0..5 {
1113 let barrier = periodic.next_barrier(&context).await;
1114 barriers.push(barrier.database_id);
1115 }
1116
1117 for database_id in barriers {
1119 *barrier_counts.entry(database_id).or_insert(0) += 1;
1120 }
1121
1122 let db1_count = barrier_counts.get(&DatabaseId::from(1)).unwrap_or(&0);
1124 let db2_count = barrier_counts.get(&DatabaseId::from(2)).unwrap_or(&0);
1125
1126 assert_eq!(*db1_count, 4);
1128 assert_eq!(*db2_count, 1);
1129 }
1130
1131 #[tokio::test(start_paused = true)]
1132 async fn test_next_barrier_force_checkpoint() {
1133 let databases = vec![create_test_database(1, Some(100), Some(10))];
1134
1135 let mut periodic = PeriodicBarriers::new(Duration::from_millis(100), 10, databases);
1136
1137 let (context, _tx) = MockGlobalBarrierWorkerContext::new();
1138
1139 periodic.force_checkpoint_in_next_barrier(DatabaseId::from(1));
1141
1142 let barrier = periodic.next_barrier(&context).now_or_never().unwrap();
1143
1144 assert!(barrier.checkpoint);
1146 assert_eq!(barrier.database_id, DatabaseId::from(1));
1147 assert!(barrier.command.is_none());
1148 assert!(!barrier.periodic);
1149
1150 let start_time = Instant::now();
1153 let barrier = periodic.next_barrier(&context).await;
1155 assert!(barrier.periodic);
1156 assert_eq!(barrier.database_id, DatabaseId::from(1));
1157 assert!(start_time.elapsed() < Duration::from_millis(100));
1158 }
1159
1160 #[tokio::test(start_paused = true)]
1161 async fn test_next_barrier_multiple_force_checkpoints() {
1162 let databases = vec![
1163 create_test_database(1, Some(100), Some(10)),
1164 create_test_database(2, Some(100), Some(10)),
1165 ];
1166
1167 let mut periodic = PeriodicBarriers::new(Duration::from_millis(100), 10, databases);
1168
1169 let (context, _tx) = MockGlobalBarrierWorkerContext::new();
1170
1171 periodic.force_checkpoint_in_next_barrier(DatabaseId::from(1));
1172 periodic.force_checkpoint_in_next_barrier(DatabaseId::from(2));
1173
1174 let barrier1 = periodic.next_barrier(&context).now_or_never().unwrap();
1175 assert!(barrier1.checkpoint);
1176 assert!(barrier1.command.is_none());
1177 let database_id1 = barrier1.database_id;
1178
1179 let barrier2 = periodic.next_barrier(&context).now_or_never().unwrap();
1180 assert!(barrier2.checkpoint);
1181 assert!(barrier2.command.is_none());
1182 assert_eq!(
1183 HashSet::from([database_id1, barrier2.database_id]),
1184 HashSet::from([DatabaseId::from(1), DatabaseId::from(2)])
1185 );
1186 assert!(periodic.force_checkpoint_databases.is_empty());
1187
1188 let start_time = Instant::now();
1191 let barrier3 = periodic.next_barrier(&context).await;
1192 assert!(barrier3.periodic);
1193 let database_id3 = barrier3.database_id;
1194
1195 let barrier4 = periodic.next_barrier(&context).await;
1196 assert!(barrier4.periodic);
1197 assert_eq!(
1198 HashSet::from([database_id3, barrier4.database_id]),
1199 HashSet::from([DatabaseId::from(1), DatabaseId::from(2)])
1200 );
1201 assert!(start_time.elapsed() < Duration::from_millis(100));
1203 }
1204
1205 #[tokio::test(start_paused = true)]
1206 async fn test_next_barrier_checkpoint_frequency() {
1207 let databases = vec![create_test_database(1, Some(50), Some(2))]; let mut periodic = PeriodicBarriers::new(Duration::from_millis(50), 10, databases);
1210
1211 let (context, tx) = MockGlobalBarrierWorkerContext::new();
1212
1213 let barrier1 = periodic.next_barrier(&context).await;
1215 assert!(!barrier1.checkpoint);
1216 barrier1.injected();
1217
1218 let start_time = Instant::now();
1222 tokio::time::sleep(Duration::from_millis(50)).await;
1223 tx.send(noop_reschedule_intent(DatabaseId::from(1)))
1224 .unwrap();
1225 let discarded = periodic.next_barrier(&context).now_or_never().unwrap();
1226 assert!(discarded.command.is_some());
1227 assert!(!discarded.periodic);
1228
1229 let barrier2 = periodic.next_barrier(&context).now_or_never().unwrap();
1231 assert!(barrier2.periodic);
1232 assert!(barrier2.checkpoint);
1233 assert!(start_time.elapsed() < Duration::from_millis(100));
1235 barrier2.injected();
1236
1237 let barrier3 = periodic.next_barrier(&context).await;
1239 assert!(!barrier3.checkpoint);
1240 barrier3.injected();
1241 }
1242
1243 #[tokio::test]
1244 async fn test_update_database_barrier() {
1245 let databases = vec![create_test_database(1, Some(1000), Some(10))];
1246
1247 let mut periodic = PeriodicBarriers::new(Duration::from_millis(500), 20, databases);
1248
1249 let database_id = DatabaseId::new(1);
1250
1251 periodic.update_database_barrier(database_id, Some(2000), Some(15));
1253
1254 let db_state = periodic.databases.get(&database_id).unwrap();
1255 assert_eq!(db_state.barrier_interval, Some(Duration::from_millis(2000)));
1256 assert_eq!(db_state.checkpoint_frequency, Some(15));
1257 assert_eq!(db_state.num_uncheckpointed_barrier, 0);
1258 assert!(!periodic.force_checkpoint_databases.contains(&database_id));
1259
1260 periodic.update_database_barrier(DatabaseId::from(2), None, None);
1262
1263 assert!(periodic.databases.contains_key(&DatabaseId::from(2)));
1264 let db2_state = periodic.databases.get(&DatabaseId::from(2)).unwrap();
1265 assert_eq!(db2_state.barrier_interval, None);
1266 assert_eq!(db2_state.checkpoint_frequency, None);
1267 }
1268}