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