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