Skip to main content

risingwave_meta/hummock/manager/
mod.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{BTreeMap, HashMap};
16use std::ops::{Deref, DerefMut};
17use std::sync::Arc;
18use std::sync::atomic::AtomicBool;
19
20use anyhow::anyhow;
21use bytes::Bytes;
22use futures::FutureExt;
23use itertools::Itertools;
24use parking_lot::lock_api::RwLock;
25use risingwave_common::catalog::{TableId, TableOption};
26use risingwave_common::monitor::MonitoredRwLock;
27use risingwave_common::system_param::reader::SystemParamsRead;
28use risingwave_hummock_sdk::change_log::TableChangeLog;
29use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta};
30use risingwave_hummock_sdk::{
31    CompactionGroupId, HummockCompactionTaskId, HummockContextId, HummockVersionId,
32    version_archive_dir, version_checkpoint_path,
33};
34use risingwave_meta_model::{
35    compaction_status, compaction_task, hummock_pinned_version, hummock_version_delta,
36    hummock_version_stats,
37};
38use risingwave_pb::hummock::compact_task::TaskStatus;
39use risingwave_pb::hummock::{
40    HummockVersionStats, PbCompactionGroupInfo, SubscribeCompactionEventRequest,
41};
42use table_write_throughput_statistic::TableWriteThroughputStatisticManager;
43use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
44use tokio::sync::{Mutex, Semaphore, oneshot};
45use tonic::Streaming;
46
47use crate::MetaResult;
48use crate::hummock::CompactorManagerRef;
49use crate::hummock::compaction::CompactStatus;
50use crate::hummock::error::Result;
51use crate::hummock::manager::checkpoint::HummockVersionCheckpoint;
52use crate::hummock::manager::context::ContextInfo;
53use crate::hummock::manager::gc::{FullGcState, GcManager};
54use crate::hummock::manager::sequence::PrefetchedSequence;
55use crate::hummock::model::ext::{compaction_task_model_to_assignment, to_table_change_log};
56use crate::manager::{MetaSrvEnv, MetadataManager};
57use crate::model::{ClusterId, MetadataModelError};
58use crate::rpc::metrics::MetaMetrics;
59
60mod context;
61mod gc;
62mod tests;
63mod versioning;
64pub use context::HummockVersionSafePoint;
65use versioning::*;
66pub(crate) mod checkpoint;
67mod commit_epoch;
68mod compaction;
69pub mod sequence;
70pub mod table_write_throughput_statistic;
71pub mod time_travel;
72mod timer_task;
73mod transaction;
74mod utils;
75mod worker;
76
77pub use commit_epoch::{CommitEpochInfo, NewTableFragmentInfo};
78pub use compaction::compaction_event_loop::*;
79use compaction::*;
80pub use compaction::{GroupState, GroupStateValidator, ManualCompactionTriggerResult};
81pub(crate) use utils::*;
82
83struct TableCommittedEpochNotifiers {
84    txs: HashMap<TableId, Vec<UnboundedSender<u64>>>,
85}
86
87impl TableCommittedEpochNotifiers {
88    fn notify_deltas(&mut self, deltas: &[HummockVersionDelta]) {
89        self.txs.retain(|table_id, txs| {
90            let mut is_dropped = false;
91            let mut committed_epoch = None;
92            for delta in deltas {
93                if delta.removed_table_ids.contains(table_id) {
94                    is_dropped = true;
95                    break;
96                }
97                if let Some(info) = delta.state_table_info_delta.get(table_id) {
98                    committed_epoch = Some(info.committed_epoch);
99                }
100            }
101            if is_dropped {
102                false
103            } else if let Some(committed_epoch) = committed_epoch {
104                txs.retain(|tx| tx.send(committed_epoch).is_ok());
105                !txs.is_empty()
106            } else {
107                true
108            }
109        })
110    }
111}
112
113#[derive(Clone, Debug)]
114struct CompactionTaskReportResult {
115    task_id: HummockCompactionTaskId,
116
117    task_status: TaskStatus,
118    reported: bool,
119}
120
121struct CompactionTaskReportNotifiers {
122    txs: HashMap<HummockCompactionTaskId, Vec<oneshot::Sender<CompactionTaskReportResult>>>,
123}
124
125impl CompactionTaskReportNotifiers {
126    fn register(
127        &mut self,
128        task_id: HummockCompactionTaskId,
129        tx: oneshot::Sender<CompactionTaskReportResult>,
130    ) {
131        self.txs.entry(task_id).or_default().push(tx);
132    }
133
134    fn remove(&mut self, task_id: HummockCompactionTaskId) {
135        self.txs.remove(&task_id);
136    }
137
138    fn notify(&mut self, result: CompactionTaskReportResult) {
139        if let Some(txs) = self.txs.remove(&result.task_id) {
140            for tx in txs {
141                let _ = tx.send(result.clone());
142            }
143        }
144    }
145}
146// Update to states are performed as follow:
147// - Initialize ValTransaction for the meta state to update
148// - Make changes on the ValTransaction.
149// - Call `commit_multi_var` to commit the changes via meta store transaction. If transaction
150//   succeeds, the in-mem state will be updated by the way.
151pub struct HummockManager {
152    pub env: MetaSrvEnv,
153
154    metadata_manager: MetadataManager,
155    /// Lock order: `compaction`, `versioning`, `compaction_group_manager`, `context_info`
156    /// - Lock `compaction` first, then `versioning`, then `compaction_group_manager` and finally `context_info`.
157    /// - This order should be strictly followed to prevent deadlock.
158    compaction: MonitoredRwLock<Compaction>,
159    versioning: MonitoredRwLock<Versioning>,
160    /// `CompactionGroupManager` manages compaction configs for compaction groups.
161    compaction_group_manager: MonitoredRwLock<CompactionGroupManager>,
162    context_info: MonitoredRwLock<ContextInfo>,
163
164    pub metrics: Arc<MetaMetrics>,
165
166    pub compactor_manager: CompactorManagerRef,
167    pub iceberg_compactor_manager: Arc<IcebergCompactorManager>,
168    event_sender: HummockManagerEventSender,
169    object_store: ObjectStoreRef,
170    version_checkpoint_path: String,
171    version_archive_dir: String,
172    pause_version_checkpoint: AtomicBool,
173    table_write_throughput_statistic_manager:
174        parking_lot::RwLock<TableWriteThroughputStatisticManager>,
175    table_committed_epoch_notifiers: parking_lot::Mutex<TableCommittedEpochNotifiers>,
176    compaction_task_report_notifiers: parking_lot::Mutex<CompactionTaskReportNotifiers>,
177    version_stat_tx: UnboundedSender<Arc<HummockVersion>>,
178
179    // for compactor
180    // `compactor_streams_change_tx` is used to pass the mapping from `context_id` to event_stream
181    // and is maintained in memory. All event_streams are consumed through a separate event loop
182    compactor_streams_change_tx:
183        UnboundedSender<(HummockContextId, Streaming<SubscribeCompactionEventRequest>)>,
184
185    // `compaction_state` will record the types of compact tasks that can be triggered in `hummock`
186    // and suggest types with a certain priority.
187    pub compaction_state: CompactionState,
188    full_gc_state: Arc<FullGcState>,
189    now: Mutex<u64>,
190    inflight_time_travel_query: Semaphore,
191    gc_manager: GcManager,
192
193    /// In-memory cache of prefetched compaction task ids to reduce per-task DB round-trips.
194    prefetched_compaction_task_ids: PrefetchedSequence,
195    table_id_to_table_option: parking_lot::RwLock<HashMap<TableId, TableOption>>,
196}
197
198pub type HummockManagerRef = Arc<HummockManager>;
199
200use risingwave_object_store::object::{ObjectError, ObjectStoreRef, build_remote_object_store};
201use risingwave_pb::catalog::Table;
202
203use super::IcebergCompactorManager;
204use crate::controller::SqlMetaStore;
205use crate::hummock::manager::compaction_group_manager::CompactionGroupManager;
206use crate::hummock::manager::worker::HummockManagerEventSender;
207
208impl HummockManager {
209    pub async fn new(
210        env: MetaSrvEnv,
211        metadata_manager: MetadataManager,
212        metrics: Arc<MetaMetrics>,
213        compactor_manager: CompactorManagerRef,
214        compactor_streams_change_tx: UnboundedSender<(
215            HummockContextId,
216            Streaming<SubscribeCompactionEventRequest>,
217        )>,
218    ) -> Result<HummockManagerRef> {
219        let compaction_group_manager = CompactionGroupManager::new(&env).await?;
220        Self::new_impl(
221            env,
222            metadata_manager,
223            metrics,
224            compactor_manager,
225            compaction_group_manager,
226            compactor_streams_change_tx,
227        )
228        .await
229    }
230
231    #[cfg(any(test, feature = "test"))]
232    pub(super) async fn with_config(
233        env: MetaSrvEnv,
234        cluster_controller: crate::controller::cluster::ClusterControllerRef,
235        catalog_controller: crate::controller::catalog::CatalogControllerRef,
236        metrics: Arc<MetaMetrics>,
237        compactor_manager: CompactorManagerRef,
238        config: risingwave_pb::hummock::CompactionConfig,
239        compactor_streams_change_tx: UnboundedSender<(
240            HummockContextId,
241            Streaming<SubscribeCompactionEventRequest>,
242        )>,
243    ) -> HummockManagerRef {
244        let compaction_group_manager = CompactionGroupManager::new_with_config(&env, config)
245            .await
246            .unwrap();
247        let metadata_manager = MetadataManager::new(cluster_controller, catalog_controller);
248        Self::new_impl(
249            env,
250            metadata_manager,
251            metrics,
252            compactor_manager,
253            compaction_group_manager,
254            compactor_streams_change_tx,
255        )
256        .await
257        .unwrap()
258    }
259
260    async fn new_impl(
261        env: MetaSrvEnv,
262        metadata_manager: MetadataManager,
263        metrics: Arc<MetaMetrics>,
264        compactor_manager: CompactorManagerRef,
265        compaction_group_manager: CompactionGroupManager,
266        compactor_streams_change_tx: UnboundedSender<(
267            HummockContextId,
268            Streaming<SubscribeCompactionEventRequest>,
269        )>,
270    ) -> Result<HummockManagerRef> {
271        let sys_params = env.system_params_reader().await;
272        let state_store_url = sys_params.state_store();
273
274        let state_store_dir: &str = sys_params.data_directory();
275        let use_new_object_prefix_strategy: bool = sys_params.use_new_object_prefix_strategy();
276        let deterministic_mode = env.opts.compaction_deterministic_test;
277        let mut object_store_config = env.opts.object_store_config.clone();
278        // For fs and hdfs object store, operations are not always atomic.
279        // We should manually enable atomicity guarantee by setting the atomic_write_dir config when building services.
280        object_store_config.set_atomic_write_dir();
281        let object_store = Arc::new(
282            build_remote_object_store(
283                state_store_url.strip_prefix("hummock+").unwrap_or("memory"),
284                metrics.object_store_metric.clone(),
285                "Version Checkpoint",
286                Arc::new(object_store_config),
287            )
288            .await,
289        );
290        // Make sure data dir is not used by another cluster.
291        // Skip this check in e2e compaction test, which needs to start a secondary cluster with
292        // same bucket
293        if !deterministic_mode {
294            write_exclusive_cluster_id(
295                state_store_dir,
296                env.cluster_id().clone(),
297                object_store.clone(),
298            )
299            .await?;
300
301            // config bucket lifecycle for new cluster.
302            if let risingwave_object_store::object::ObjectStoreImpl::S3(s3) = object_store.as_ref()
303                && !env.opts.do_not_config_object_storage_lifecycle
304            {
305                let is_bucket_expiration_configured =
306                    s3.inner().configure_bucket_lifecycle(state_store_dir).await;
307                if is_bucket_expiration_configured {
308                    return Err(ObjectError::internal("Cluster cannot start with object expiration configured for bucket because RisingWave data will be lost when object expiration kicks in.
309                    Please disable object expiration and restart the cluster.")
310                    .into());
311                }
312            }
313        }
314        let version_checkpoint_path = version_checkpoint_path(state_store_dir);
315        let version_archive_dir = version_archive_dir(state_store_dir);
316        let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
317        let (version_stat_tx, mut version_stat_rx) = tokio::sync::mpsc::unbounded_channel();
318        let inflight_time_travel_query = env.opts.max_inflight_time_travel_query;
319        let gc_manager = GcManager::new(
320            object_store.clone(),
321            state_store_dir,
322            use_new_object_prefix_strategy,
323        );
324
325        let max_table_statistic_expired_time = std::cmp::max(
326            env.opts.table_stat_throuput_window_seconds_for_split,
327            env.opts.table_stat_throuput_window_seconds_for_merge,
328        ) as i64;
329
330        let iceberg_compactor_manager = Arc::new(IcebergCompactorManager::new());
331
332        let instance = HummockManager {
333            env,
334            versioning: MonitoredRwLock::new(
335                metrics.hummock_manager_lock_time.clone(),
336                metrics.hummock_manager_real_process_time.clone(),
337                Default::default(),
338                "hummock_manager::versioning",
339            ),
340            compaction: MonitoredRwLock::new(
341                metrics.hummock_manager_lock_time.clone(),
342                metrics.hummock_manager_real_process_time.clone(),
343                Default::default(),
344                "hummock_manager::compaction",
345            ),
346            compaction_group_manager: MonitoredRwLock::new(
347                metrics.hummock_manager_lock_time.clone(),
348                metrics.hummock_manager_real_process_time.clone(),
349                compaction_group_manager,
350                "hummock_manager::compaction_group_manager",
351            ),
352            context_info: MonitoredRwLock::new(
353                metrics.hummock_manager_lock_time.clone(),
354                metrics.hummock_manager_real_process_time.clone(),
355                Default::default(),
356                "hummock_manager::context_info",
357            ),
358            metrics,
359            metadata_manager,
360            compactor_manager,
361            iceberg_compactor_manager,
362            event_sender: tx,
363            object_store,
364            version_checkpoint_path,
365            version_archive_dir,
366            pause_version_checkpoint: AtomicBool::new(false),
367            table_write_throughput_statistic_manager: parking_lot::RwLock::new(
368                TableWriteThroughputStatisticManager::new(max_table_statistic_expired_time),
369            ),
370            table_committed_epoch_notifiers: parking_lot::Mutex::new(
371                TableCommittedEpochNotifiers {
372                    txs: Default::default(),
373                },
374            ),
375            compaction_task_report_notifiers: parking_lot::Mutex::new(
376                CompactionTaskReportNotifiers {
377                    txs: Default::default(),
378                },
379            ),
380            version_stat_tx,
381            compactor_streams_change_tx,
382            compaction_state: CompactionState::new(),
383            full_gc_state: FullGcState::new().into(),
384            now: Mutex::new(0),
385            inflight_time_travel_query: Semaphore::new(inflight_time_travel_query as usize),
386            gc_manager,
387            prefetched_compaction_task_ids: PrefetchedSequence::new(),
388            table_id_to_table_option: RwLock::new(HashMap::new()),
389        };
390        let instance = Arc::new(instance);
391        let version_stat_metrics = instance.metrics.clone();
392        tokio::spawn(async move {
393            while let Some(mut version) = version_stat_rx.recv().await {
394                while let Some(Some(next_version)) = version_stat_rx.recv().now_or_never() {
395                    version = next_version;
396                }
397                transaction::trigger_version_stat(&version_stat_metrics, version.as_ref());
398            }
399        });
400        instance.init_time_travel_state().await?;
401        instance.load_meta_store_state().await?;
402        // may_fill_backward_table_change_logs MUST execute after load_meta_store_state
403        instance.may_fill_backward_table_change_logs().await?;
404        instance.start_worker(rx);
405        instance.release_invalid_contexts().await?;
406        // Release snapshots pinned by meta on restarting.
407        instance.release_meta_context().await?;
408        Ok(instance)
409    }
410
411    fn meta_store_ref(&self) -> &SqlMetaStore {
412        self.env.meta_store_ref()
413    }
414
415    /// Load state from meta store.
416    async fn load_meta_store_state(&self) -> Result<()> {
417        let now = self.load_now().await?;
418        *self.now.lock().await = now.unwrap_or(0);
419
420        let mut compaction_guard = self.compaction.write().await;
421        let mut versioning_guard = self.versioning.write().await;
422        let mut context_info_guard = self.context_info.write().await;
423        self.load_meta_store_state_impl(
424            &mut compaction_guard,
425            &mut versioning_guard,
426            &mut context_info_guard,
427        )
428        .await
429    }
430
431    /// Load state from meta store.
432    async fn load_meta_store_state_impl(
433        &self,
434        compaction_guard: &mut Compaction,
435        versioning_guard: &mut Versioning,
436        context_info: &mut ContextInfo,
437    ) -> Result<()> {
438        use sea_orm::EntityTrait;
439        let meta_store = self.meta_store_ref();
440        let compaction_statuses: BTreeMap<CompactionGroupId, CompactStatus> =
441            compaction_status::Entity::find()
442                .all(&meta_store.conn)
443                .await
444                .map_err(MetadataModelError::from)?
445                .into_iter()
446                .map(|m| (m.compaction_group_id as CompactionGroupId, m.into()))
447                .collect();
448        if !compaction_statuses.is_empty() {
449            compaction_guard.compaction_statuses = compaction_statuses;
450        }
451
452        compaction_guard.compact_task_assignment = compaction_task::Entity::find()
453            .all(&meta_store.conn)
454            .await
455            .map_err(MetadataModelError::from)?
456            .into_iter()
457            .map(|m| {
458                (
459                    m.id as HummockCompactionTaskId,
460                    compaction_task_model_to_assignment(m),
461                )
462            })
463            .collect();
464
465        let hummock_version_deltas: BTreeMap<HummockVersionId, HummockVersionDelta> =
466            hummock_version_delta::Entity::find()
467                .all(&meta_store.conn)
468                .await
469                .map_err(MetadataModelError::from)?
470                .into_iter()
471                .map(|m| {
472                    (
473                        m.id,
474                        HummockVersionDelta::from_persisted_protobuf_owned(m.into()),
475                    )
476                })
477                .collect();
478
479        let checkpoint = self.try_read_checkpoint().await?;
480        let mut redo_state = if let Some(c) = checkpoint {
481            versioning_guard.checkpoint = c;
482            versioning_guard.checkpoint.version.as_ref().clone()
483        } else {
484            let default_compaction_config = self
485                .compaction_group_manager
486                .read()
487                .await
488                .default_compaction_config();
489            let checkpoint_version = HummockVersion::create_init_version(default_compaction_config);
490            tracing::info!("init hummock version checkpoint");
491            versioning_guard.checkpoint = HummockVersionCheckpoint {
492                version: Arc::new(checkpoint_version.clone()),
493                stale_objects: Default::default(),
494            };
495            self.write_checkpoint(&versioning_guard.checkpoint).await?;
496            checkpoint_version
497        };
498        let mut applied_delta_count = 0;
499        let total_to_apply = hummock_version_deltas.range(redo_state.id + 1..).count();
500        tracing::info!(
501            total_delta = hummock_version_deltas.len(),
502            total_to_apply,
503            "Start redo Hummock version."
504        );
505        for version_delta in hummock_version_deltas
506            .range(redo_state.id + 1..)
507            .map(|(_, v)| v)
508        {
509            assert_eq!(
510                version_delta.prev_id, redo_state.id,
511                "delta prev_id {}, redo state id {}",
512                version_delta.prev_id, redo_state.id
513            );
514            redo_state.apply_version_delta(version_delta);
515            // backward compatibility: migrate table change log to meta store.
516            redo_state.apply_table_change_log_delta_backward_compatibility(version_delta);
517            applied_delta_count += 1;
518            if applied_delta_count % 1000 == 0 {
519                tracing::info!("Redo progress {applied_delta_count}/{total_to_apply}.");
520            }
521        }
522        tracing::info!("Finish redo Hummock version.");
523        let pruned_stale_table_id_count = redo_state.prune_stale_table_ids_from_ssts();
524        if pruned_stale_table_id_count > 0 {
525            tracing::warn!(
526                pruned_stale_table_id_count,
527                version_id = ?redo_state.id,
528                "Pruned stale table ids from recovered Hummock SST metadata."
529            );
530        }
531        versioning_guard.version_stats = hummock_version_stats::Entity::find()
532            .one(&meta_store.conn)
533            .await
534            .map_err(MetadataModelError::from)?
535            .map(HummockVersionStats::from)
536            .unwrap_or_else(|| HummockVersionStats {
537                // version_stats.hummock_version_id is always 0 in meta store.
538                hummock_version_id: 0.into(),
539                ..Default::default()
540            });
541
542        versioning_guard.current_version = Arc::new(redo_state);
543        versioning_guard.hummock_version_deltas = hummock_version_deltas;
544        versioning_guard.table_change_log =
545            risingwave_meta_model::hummock_table_change_log::Entity::find()
546                .all(&self.env.meta_store_ref().conn)
547                .await
548                .map_err(MetadataModelError::from)?
549                .into_iter()
550                .map(|m| (m.table_id, to_table_change_log(m)))
551                .into_group_map()
552                .into_iter()
553                .map(|(table_id, unordered_change_logs)| {
554                    (
555                        table_id,
556                        TableChangeLog::new(
557                            unordered_change_logs
558                                .into_iter()
559                                .sorted_by_key(|l| l.checkpoint_epoch),
560                        ),
561                    )
562                })
563                .collect();
564
565        context_info.pinned_versions = hummock_pinned_version::Entity::find()
566            .all(&meta_store.conn)
567            .await
568            .map_err(MetadataModelError::from)?
569            .into_iter()
570            .map(|m| (m.context_id as HummockContextId, m.into()))
571            .collect();
572
573        self.initial_compaction_group_config_after_load(
574            versioning_guard,
575            self.compaction_group_manager.write().await.deref_mut(),
576        )
577        .await?;
578
579        Ok(())
580    }
581
582    pub fn init_metadata_for_version_replay(
583        &self,
584        _table_catalogs: Vec<Table>,
585        _compaction_groups: Vec<PbCompactionGroupInfo>,
586    ) -> Result<()> {
587        unimplemented!("kv meta store is deprecated");
588    }
589
590    /// Replay a version delta to current hummock version.
591    /// Returns the `version_id`, `max_committed_epoch` of the new version and the modified
592    /// compaction groups
593    #[cfg(any(test, feature = "test"))]
594    pub async fn replay_version_delta(
595        &self,
596        mut version_delta: HummockVersionDelta,
597    ) -> Result<(HummockVersion, Vec<CompactionGroupId>)> {
598        let mut versioning_guard = self.versioning.write().await;
599        // ensure the version id is ascending after replay
600        version_delta.id = versioning_guard.current_version.next_version_id();
601        version_delta.prev_id = versioning_guard.current_version.id;
602        let mut version_new = versioning_guard.current_version.as_ref().clone();
603        version_new.apply_version_delta(&version_delta);
604
605        let compaction_group_ids = version_delta.group_deltas.keys().cloned().collect();
606        versioning_guard.current_version = Arc::new(version_new.clone());
607        Ok((version_new, compaction_group_ids))
608    }
609
610    pub async fn disable_commit_epoch(&self) -> Arc<HummockVersion> {
611        let mut versioning_guard = self.versioning.write().await;
612        versioning_guard.disable_commit_epochs = true;
613        versioning_guard.current_version.clone()
614    }
615
616    pub fn metadata_manager(&self) -> &MetadataManager {
617        &self.metadata_manager
618    }
619
620    pub fn object_store_media_type(&self) -> &'static str {
621        self.object_store.media_type()
622    }
623
624    pub fn update_table_id_to_table_option(
625        &self,
626        new_table_id_to_table_option: HashMap<TableId, TableOption>,
627    ) {
628        *self.table_id_to_table_option.write() = new_table_id_to_table_option;
629    }
630
631    pub fn metadata_manager_ref(&self) -> &MetadataManager {
632        &self.metadata_manager
633    }
634
635    pub async fn subscribe_table_committed_epoch(
636        &self,
637        table_id: TableId,
638    ) -> MetaResult<(u64, UnboundedReceiver<u64>)> {
639        let version = self.versioning.read().await;
640        if let Some(epoch) = version.current_version.table_committed_epoch(table_id) {
641            let (tx, rx) = unbounded_channel();
642            self.table_committed_epoch_notifiers
643                .lock()
644                .txs
645                .entry(table_id)
646                .or_default()
647                .push(tx);
648            Ok((epoch, rx))
649        } else {
650            Err(anyhow!("table {} not exist", table_id).into())
651        }
652    }
653}
654
655async fn write_exclusive_cluster_id(
656    state_store_dir: &str,
657    cluster_id: ClusterId,
658    object_store: ObjectStoreRef,
659) -> Result<()> {
660    const CLUSTER_ID_DIR: &str = "cluster_id";
661    const CLUSTER_ID_NAME: &str = "0";
662    let cluster_id_dir = format!("{}/{}/", state_store_dir, CLUSTER_ID_DIR);
663    let cluster_id_full_path = format!("{}{}", cluster_id_dir, CLUSTER_ID_NAME);
664    tracing::info!("try reading cluster_id");
665    match object_store.read(&cluster_id_full_path, ..).await {
666        Ok(stored_cluster_id) => {
667            let stored_cluster_id = String::from_utf8(stored_cluster_id.to_vec()).unwrap();
668            if cluster_id.deref() == stored_cluster_id {
669                return Ok(());
670            }
671
672            Err(ObjectError::internal(format!(
673                "Data directory is already used by another cluster with id {:?}, path {}.",
674                stored_cluster_id, cluster_id_full_path,
675            ))
676            .into())
677        }
678        Err(e) => {
679            if e.is_object_not_found_error() {
680                tracing::info!("cluster_id not found, writing cluster_id");
681                object_store
682                    .upload(&cluster_id_full_path, Bytes::from(String::from(cluster_id)))
683                    .await?;
684                return Ok(());
685            }
686            Err(e.into())
687        }
688    }
689}