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