Skip to main content

risingwave_storage/hummock/store/
hummock_storage.rs

1// Copyright 2023 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::HashSet;
16use std::future::Future;
17use std::ops::{Bound, Deref};
18use std::sync::Arc;
19
20use arc_swap::ArcSwap;
21use bytes::Bytes;
22use itertools::Itertools;
23use risingwave_common::array::VectorRef;
24use risingwave_common::catalog::{TableId, TableOption};
25use risingwave_common::dispatch_distance_measurement;
26use risingwave_common::util::epoch::is_max_epoch;
27use risingwave_common_service::{NotificationClient, ObserverManager};
28use risingwave_hummock_sdk::change_log::TableChangeLogs;
29use risingwave_hummock_sdk::key::{
30    TableKey, TableKeyRange, is_empty_key_range, vnode, vnode_range,
31};
32use risingwave_hummock_sdk::sstable_info::SstableInfo;
33use risingwave_hummock_sdk::table_watermark::TableWatermarksIndex;
34use risingwave_hummock_sdk::version::HummockVersion;
35use risingwave_hummock_sdk::{HummockRawObjectId, HummockReadEpoch, SyncResult};
36use risingwave_rpc_client::HummockMetaClient;
37use risingwave_rpc_client::error::RpcError;
38use thiserror_ext::AsReport;
39use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
40use tokio::sync::oneshot;
41
42use super::local_hummock_storage::LocalHummockStorage;
43use super::version::{CommittedVersion, HummockVersionReader, read_filter_for_version};
44use crate::compaction_catalog_manager::CompactionCatalogManagerRef;
45#[cfg(any(test, feature = "test"))]
46use crate::compaction_catalog_manager::{CompactionCatalogManager, FakeRemoteTableAccessor};
47use crate::error::StorageResult;
48use crate::hummock::backup_reader::{BackupReader, BackupReaderRef};
49use crate::hummock::compactor::{
50    CompactionAwaitTreeRegRef, CompactorContext, new_compaction_await_tree_reg_ref,
51};
52use crate::hummock::event_handler::hummock_event_handler::{BufferTracker, HummockEventSender};
53use crate::hummock::event_handler::{
54    HummockEvent, HummockEventHandler, HummockVersionUpdate, ReadOnlyReadVersionMapping,
55};
56use crate::hummock::iterator::change_log::ChangeLogIterator;
57use crate::hummock::local_version::pinned_version::{PinnedVersion, start_pinned_version_worker};
58use crate::hummock::local_version::recent_versions::RecentVersions;
59use crate::hummock::observer_manager::HummockObserverNode;
60use crate::hummock::store::vector_writer::HummockVectorWriter;
61use crate::hummock::table_change_log_manager::TableChangeLogManager;
62use crate::hummock::time_travel_version_cache::SimpleTimeTravelVersionCache;
63use crate::hummock::utils::{wait_for_epoch, wait_for_update};
64use crate::hummock::write_limiter::{WriteLimiter, WriteLimiterRef};
65use crate::hummock::{
66    HummockEpoch, HummockError, HummockResult, HummockStorageIterator, HummockStorageRevIterator,
67    MemoryLimiter, ObjectIdManager, ObjectIdManagerRef, SstableStoreRef,
68};
69use crate::mem_table::ImmutableMemtable;
70use crate::monitor::{CompactorMetrics, HummockStateStoreMetrics};
71use crate::opts::StorageOpts;
72use crate::store::*;
73
74struct HummockStorageShutdownGuard {
75    shutdown_sender: HummockEventSender,
76}
77
78impl Drop for HummockStorageShutdownGuard {
79    fn drop(&mut self) {
80        let _ = self
81            .shutdown_sender
82            .send(HummockEvent::Shutdown)
83            .inspect_err(|e| tracing::debug!(event = ?e.0, "unable to send shutdown"));
84    }
85}
86
87/// `HummockStorage` is the entry point of the Hummock state store backend.
88/// It implements the `StateStore` and `StateStoreRead` traits but without any write method
89/// since all writes should be done via `LocalHummockStorage` to ensure the single writer property
90/// of hummock. `LocalHummockStorage` instance can be created via `new_local` call.
91/// Hummock is the state store backend.
92#[derive(Clone)]
93pub struct HummockStorage {
94    hummock_event_sender: HummockEventSender,
95    // only used in test for setting hummock version in uploader
96    _version_update_sender: UnboundedSender<HummockVersionUpdate>,
97
98    context: CompactorContext,
99
100    compaction_catalog_manager_ref: CompactionCatalogManagerRef,
101
102    object_id_manager: ObjectIdManagerRef,
103
104    buffer_tracker: BufferTracker,
105
106    version_update_notifier_tx: Arc<tokio::sync::watch::Sender<PinnedVersion>>,
107
108    recent_versions: Arc<ArcSwap<RecentVersions>>,
109
110    hummock_version_reader: HummockVersionReader,
111
112    _shutdown_guard: Arc<HummockStorageShutdownGuard>,
113
114    read_version_mapping: ReadOnlyReadVersionMapping,
115
116    backup_reader: BackupReaderRef,
117
118    write_limiter: WriteLimiterRef,
119
120    compact_await_tree_reg: Option<CompactionAwaitTreeRegRef>,
121
122    hummock_meta_client: Arc<dyn HummockMetaClient>,
123
124    simple_time_travel_version_cache: Arc<SimpleTimeTravelVersionCache>,
125
126    table_change_log_manager: Arc<TableChangeLogManager>,
127}
128
129pub type ReadVersionTuple = (Vec<ImmutableMemtable>, Vec<SstableInfo>, CommittedVersion);
130
131pub fn get_committed_read_version_tuple(
132    version: PinnedVersion,
133    table_id: TableId,
134    mut key_range: TableKeyRange,
135    epoch: HummockEpoch,
136) -> (TableKeyRange, ReadVersionTuple) {
137    if let Some(table_watermarks) = version.table_watermarks.get(&table_id) {
138        TableWatermarksIndex::new_committed(
139            table_watermarks.clone(),
140            version
141                .state_table_info
142                .info()
143                .get(&table_id)
144                .expect("should exist when having table watermark")
145                .committed_epoch,
146            table_watermarks.watermark_type,
147        )
148        .rewrite_range_with_table_watermark(epoch, &mut key_range)
149    }
150    (key_range, (vec![], vec![], version))
151}
152
153impl HummockStorage {
154    /// Creates a [`HummockStorage`].
155    #[allow(clippy::too_many_arguments)]
156    pub async fn new(
157        options: Arc<StorageOpts>,
158        sstable_store: SstableStoreRef,
159        hummock_meta_client: Arc<dyn HummockMetaClient>,
160        notification_client: impl NotificationClient,
161        compaction_catalog_manager_ref: CompactionCatalogManagerRef,
162        state_store_metrics: Arc<HummockStateStoreMetrics>,
163        compactor_metrics: Arc<CompactorMetrics>,
164        await_tree_config: Option<await_tree::Config>,
165    ) -> HummockResult<Self> {
166        let object_id_manager = Arc::new(ObjectIdManager::new(
167            hummock_meta_client.clone(),
168            options.sstable_id_remote_fetch_number,
169        ));
170        let backup_reader = BackupReader::new(
171            &options.backup_storage_url,
172            &options.backup_storage_directory,
173            &options.object_store_config,
174        )
175        .await
176        .map_err(HummockError::read_backup_error)?;
177        let write_limiter = Arc::new(WriteLimiter::default());
178        let (version_update_tx, mut version_update_rx) = unbounded_channel();
179
180        let observer_manager = ObserverManager::new(
181            notification_client,
182            HummockObserverNode::new(
183                compaction_catalog_manager_ref.clone(),
184                backup_reader.clone(),
185                version_update_tx.clone(),
186                write_limiter.clone(),
187            ),
188        )
189        .await;
190        observer_manager.start().await;
191
192        let hummock_version = match version_update_rx.recv().await {
193            Some(HummockVersionUpdate::PinnedVersion(version)) => *version,
194            _ => unreachable!(
195                "the hummock observer manager is the first one to take the event tx. Should be full hummock version"
196            ),
197        };
198
199        let (pin_version_tx, pin_version_rx) = unbounded_channel();
200        let pinned_version = PinnedVersion::new(hummock_version, pin_version_tx);
201        tokio::spawn(start_pinned_version_worker(
202            pin_version_rx,
203            hummock_meta_client.clone(),
204            options.max_version_pinning_duration_sec,
205        ));
206
207        let await_tree_reg = await_tree_config.map(new_compaction_await_tree_reg_ref);
208
209        let compactor_context = CompactorContext::new_local_compact_context(
210            options.clone(),
211            sstable_store.clone(),
212            compactor_metrics.clone(),
213            await_tree_reg.clone(),
214        );
215
216        let hummock_event_handler = HummockEventHandler::new(
217            version_update_rx,
218            pinned_version,
219            compactor_context.clone(),
220            compaction_catalog_manager_ref.clone(),
221            object_id_manager.clone(),
222            state_store_metrics.clone(),
223        );
224
225        let event_tx = hummock_event_handler.event_sender();
226        let table_change_log_manager = Arc::new(TableChangeLogManager::new(
227            options.table_change_log_cache_capacity,
228            hummock_meta_client.clone(),
229            state_store_metrics.clone(),
230        ));
231        let instance = Self {
232            context: compactor_context,
233            compaction_catalog_manager_ref: compaction_catalog_manager_ref.clone(),
234            object_id_manager,
235            buffer_tracker: hummock_event_handler.buffer_tracker().clone(),
236            version_update_notifier_tx: hummock_event_handler.version_update_notifier_tx(),
237            hummock_event_sender: event_tx.clone(),
238            _version_update_sender: version_update_tx,
239            recent_versions: hummock_event_handler.recent_versions(),
240            hummock_version_reader: HummockVersionReader::new(
241                sstable_store,
242                state_store_metrics.clone(),
243                options.max_preload_io_retry_times,
244            ),
245            _shutdown_guard: Arc::new(HummockStorageShutdownGuard {
246                shutdown_sender: event_tx,
247            }),
248            read_version_mapping: hummock_event_handler.read_version_mapping(),
249            backup_reader,
250            write_limiter,
251            compact_await_tree_reg: await_tree_reg,
252            hummock_meta_client,
253            simple_time_travel_version_cache: Arc::new(SimpleTimeTravelVersionCache::new(
254                options.time_travel_version_cache_capacity,
255            )),
256            table_change_log_manager,
257        };
258
259        tokio::spawn(hummock_event_handler.start_hummock_event_handler_worker());
260
261        Ok(instance)
262    }
263}
264
265impl HummockStorageReadSnapshot {
266    /// Gets the value of a specified `key` in the table specified in `read_options`.
267    /// The result is based on a snapshot corresponding to the given `epoch`.
268    /// if `key` has consistent hash virtual node value, then such value is stored in `value_meta`
269    ///
270    /// If `Ok(Some())` is returned, the key is found. If `Ok(None)` is returned,
271    /// the key is not found. If `Err()` is returned, the searching for the key
272    /// failed due to other non-EOF errors.
273    async fn get_inner<'a, O>(
274        &'a self,
275        key: TableKey<Bytes>,
276        read_options: ReadOptions,
277        on_key_value_fn: impl KeyValueFn<'a, O>,
278    ) -> StorageResult<Option<O>> {
279        let key_range = (Bound::Included(key.clone()), Bound::Included(key.clone()));
280
281        let (key_range, read_version_tuple) =
282            self.build_read_version_tuple(self.epoch, key_range).await?;
283
284        if is_empty_key_range(&key_range) {
285            return Ok(None);
286        }
287
288        self.hummock_version_reader
289            .get(
290                key,
291                self.epoch.get_epoch(),
292                self.table_id,
293                self.table_option,
294                read_options,
295                read_version_tuple,
296                on_key_value_fn,
297            )
298            .await
299    }
300
301    async fn iter_inner(
302        &self,
303        key_range: TableKeyRange,
304        read_options: ReadOptions,
305    ) -> StorageResult<HummockStorageIterator> {
306        let (key_range, read_version_tuple) =
307            self.build_read_version_tuple(self.epoch, key_range).await?;
308
309        self.hummock_version_reader
310            .iter(
311                key_range,
312                self.epoch.get_epoch(),
313                self.table_id,
314                self.table_option,
315                read_options,
316                read_version_tuple,
317            )
318            .await
319    }
320
321    async fn rev_iter_inner(
322        &self,
323        key_range: TableKeyRange,
324        read_options: ReadOptions,
325    ) -> StorageResult<HummockStorageRevIterator> {
326        let (key_range, read_version_tuple) =
327            self.build_read_version_tuple(self.epoch, key_range).await?;
328
329        self.hummock_version_reader
330            .rev_iter(
331                key_range,
332                self.epoch.get_epoch(),
333                self.table_id,
334                self.table_option,
335                read_options,
336                read_version_tuple,
337                None,
338            )
339            .await
340    }
341
342    async fn get_time_travel_version(
343        &self,
344        epoch: u64,
345        table_id: TableId,
346    ) -> StorageResult<PinnedVersion> {
347        let meta_client = self.hummock_meta_client.clone();
348        let fetch = async move {
349            let pb_version = meta_client
350                .get_version_by_epoch(epoch, table_id)
351                .await
352                .inspect_err(|e| tracing::error!("{}", e.to_report_string()))
353                .map_err(|e| match &e {
354                    RpcError::GrpcStatus(status)
355                        if status.inner().code() == tonic::Code::OutOfRange =>
356                    {
357                        HummockError::time_travel_version_expired(table_id, epoch)
358                    }
359                    _ => HummockError::meta_error(e.to_report_string()),
360                })?;
361            let version = HummockVersion::from_rpc_protobuf(&pb_version);
362            let (tx, _rx) = unbounded_channel();
363            Ok(PinnedVersion::new(version, tx))
364        };
365        let version = self
366            .simple_time_travel_version_cache
367            .get_or_insert(table_id, epoch, fetch)
368            .await?;
369        Ok(version)
370    }
371
372    async fn build_read_version_tuple(
373        &self,
374        epoch: HummockReadEpoch,
375        key_range: TableKeyRange,
376    ) -> StorageResult<(TableKeyRange, ReadVersionTuple)> {
377        match epoch {
378            HummockReadEpoch::Backup(epoch) => {
379                self.build_read_version_tuple_from_backup(epoch, self.table_id, key_range)
380                    .await
381            }
382            HummockReadEpoch::Committed(epoch) => {
383                let tuple = self
384                    .build_read_version_tuple_from_committed(epoch, self.table_id, key_range)
385                    .await?;
386                let (_, (_, _, version)) = &tuple;
387                let Some(committed_epoch) = version.table_committed_epoch(self.table_id) else {
388                    return Err(HummockError::other(format!(
389                        "table {} not found in version",
390                        self.table_id
391                    ))
392                    .into());
393                };
394                if committed_epoch != epoch {
395                    return Err(HummockError::committed_epoch_mismatch(
396                        self.table_id,
397                        committed_epoch,
398                        epoch,
399                    )
400                    .into());
401                }
402                Ok(tuple)
403            }
404            HummockReadEpoch::BatchQueryCommitted(epoch, _)
405            | HummockReadEpoch::TimeTravel(epoch) => {
406                self.build_read_version_tuple_from_committed(epoch, self.table_id, key_range)
407                    .await
408            }
409            HummockReadEpoch::NoWait(epoch) => {
410                self.build_read_version_tuple_from_all(epoch, self.table_id, key_range)
411                    .await
412            }
413        }
414    }
415
416    async fn build_read_version_tuple_from_backup(
417        &self,
418        epoch: u64,
419        table_id: TableId,
420        key_range: TableKeyRange,
421    ) -> StorageResult<(TableKeyRange, ReadVersionTuple)> {
422        match self
423            .backup_reader
424            .try_get_hummock_version(table_id, epoch)
425            .await
426        {
427            Ok(Some(backup_version)) => Ok(get_committed_read_version_tuple(
428                backup_version,
429                table_id,
430                key_range,
431                epoch,
432            )),
433            Ok(None) => Err(HummockError::read_backup_error(format!(
434                "backup include epoch {} not found",
435                epoch
436            ))
437            .into()),
438            Err(e) => Err(e),
439        }
440    }
441
442    async fn get_epoch_hummock_version(
443        &self,
444        epoch: u64,
445        table_id: TableId,
446    ) -> StorageResult<PinnedVersion> {
447        match self
448            .recent_versions
449            .load()
450            .get_safe_version(table_id, epoch)
451        {
452            Some(version) => Ok(version),
453            None => self.get_time_travel_version(epoch, table_id).await,
454        }
455    }
456
457    async fn build_read_version_tuple_from_committed(
458        &self,
459        epoch: u64,
460        table_id: TableId,
461        key_range: TableKeyRange,
462    ) -> StorageResult<(TableKeyRange, ReadVersionTuple)> {
463        let version = self.get_epoch_hummock_version(epoch, table_id).await?;
464        Ok(get_committed_read_version_tuple(
465            version, table_id, key_range, epoch,
466        ))
467    }
468
469    async fn build_read_version_tuple_from_all(
470        &self,
471        epoch: u64,
472        table_id: TableId,
473        key_range: TableKeyRange,
474    ) -> StorageResult<(TableKeyRange, ReadVersionTuple)> {
475        let pinned_version = self.recent_versions.load().latest_version().clone();
476        let info = pinned_version.state_table_info.info().get(&table_id);
477
478        // check epoch if lower mce
479        let ret = if let Some(info) = info
480            && epoch <= info.committed_epoch
481        {
482            let pinned_version = if epoch < info.committed_epoch {
483                pinned_version
484            } else {
485                self.get_epoch_hummock_version(epoch, table_id).await?
486            };
487            // read committed_version directly without build snapshot
488            get_committed_read_version_tuple(pinned_version, table_id, key_range, epoch)
489        } else {
490            let vnode = vnode(&key_range);
491            let mut matched_replicated_read_version_cnt = 0;
492            let read_version_vec = {
493                let read_guard = self.read_version_mapping.read();
494                read_guard
495                    .get(&table_id)
496                    .map(|v| {
497                        v.values()
498                            .filter(|v| {
499                                let read_version = v.read();
500                                if read_version.is_initialized() && read_version.contains(vnode) {
501                                    if read_version.is_replicated() {
502                                        matched_replicated_read_version_cnt += 1;
503                                        false
504                                    } else {
505                                        // Only non-replicated read version with matched vnode is considered
506                                        true
507                                    }
508                                } else {
509                                    false
510                                }
511                            })
512                            .cloned()
513                            .collect_vec()
514                    })
515                    .unwrap_or_default()
516            };
517
518            // When the system has just started and no state has been created, the memory state
519            // may be empty
520            if read_version_vec.is_empty() {
521                let table_committed_epoch = info.map(|info| info.committed_epoch);
522                if matched_replicated_read_version_cnt > 0 {
523                    tracing::warn!(
524                        "Read(table_id={} vnode={} epoch={}) is not allowed on replicated read version ({} found). Fall back to committed version (epoch={:?})",
525                        table_id,
526                        vnode.to_index(),
527                        epoch,
528                        matched_replicated_read_version_cnt,
529                        table_committed_epoch,
530                    );
531                } else {
532                    tracing::debug!(
533                        "No read version found for read(table_id={} vnode={} epoch={}). Fall back to committed version (epoch={:?})",
534                        table_id,
535                        vnode.to_index(),
536                        epoch,
537                        table_committed_epoch
538                    );
539                }
540                get_committed_read_version_tuple(pinned_version, table_id, key_range, epoch)
541            } else {
542                if read_version_vec.len() != 1 {
543                    let read_version_vnodes = read_version_vec
544                        .into_iter()
545                        .map(|v| {
546                            let v = v.read();
547                            v.vnodes().iter_ones().collect_vec()
548                        })
549                        .collect_vec();
550                    return Err(HummockError::other(format!("There are {} read version associated with vnode {}. read_version_vnodes={:?}", read_version_vnodes.len(), vnode.to_index(), read_version_vnodes)).into());
551                }
552                read_filter_for_version(
553                    epoch,
554                    table_id,
555                    key_range,
556                    read_version_vec.first().unwrap(),
557                )?
558            }
559        };
560
561        Ok(ret)
562    }
563}
564
565impl HummockStorage {
566    async fn new_local_inner(&self, option: NewLocalOptions) -> LocalHummockStorage {
567        let (tx, rx) = tokio::sync::oneshot::channel();
568        self.hummock_event_sender
569            .send(HummockEvent::RegisterReadVersion {
570                table_id: option.table_id,
571                new_read_version_sender: tx,
572                is_replicated: option.is_replicated,
573                vnodes: option.vnodes.clone(),
574            })
575            .unwrap();
576
577        let (basic_read_version, instance_guard) = rx.await.unwrap();
578        let version_update_notifier_tx = self.version_update_notifier_tx.clone();
579        LocalHummockStorage::new(
580            instance_guard,
581            basic_read_version,
582            self.hummock_version_reader.clone(),
583            self.hummock_event_sender.clone(),
584            self.buffer_tracker.get_memory_limiter().clone(),
585            self.write_limiter.clone(),
586            option,
587            version_update_notifier_tx,
588            self.context.storage_opts.mem_table_spill_threshold,
589        )
590    }
591
592    pub async fn clear_shared_buffer(&self) {
593        let (tx, rx) = oneshot::channel();
594        self.hummock_event_sender
595            .send(HummockEvent::Clear(tx, None))
596            .expect("should send success");
597        rx.await.expect("should wait success");
598    }
599
600    pub async fn clear_tables(&self, table_ids: HashSet<TableId>) {
601        if !table_ids.is_empty() {
602            let (tx, rx) = oneshot::channel();
603            self.hummock_event_sender
604                .send(HummockEvent::Clear(tx, Some(table_ids)))
605                .expect("should send success");
606            rx.await.expect("should wait success");
607        }
608    }
609
610    /// Declare the start of an epoch. This information is provided for spill so that the spill task won't
611    /// include data of two or more syncs.
612    // TODO: remove this method when we support spill task that can include data of more two or more syncs
613    pub fn start_epoch(&self, epoch: HummockEpoch, table_ids: HashSet<TableId>) {
614        let _ = self
615            .hummock_event_sender
616            .send(HummockEvent::StartEpoch { epoch, table_ids });
617    }
618
619    pub fn sstable_store(&self) -> SstableStoreRef {
620        self.context.sstable_store.clone()
621    }
622
623    pub fn object_id_manager(&self) -> &ObjectIdManagerRef {
624        &self.object_id_manager
625    }
626
627    pub fn compaction_catalog_manager_ref(&self) -> CompactionCatalogManagerRef {
628        self.compaction_catalog_manager_ref.clone()
629    }
630
631    pub fn get_memory_limiter(&self) -> Arc<MemoryLimiter> {
632        self.buffer_tracker.get_memory_limiter().clone()
633    }
634
635    pub fn get_pinned_version(&self) -> PinnedVersion {
636        self.recent_versions.load().latest_version().clone()
637    }
638
639    pub fn backup_reader(&self) -> BackupReaderRef {
640        self.backup_reader.clone()
641    }
642
643    pub fn compaction_await_tree_reg(&self) -> Option<&await_tree::Registry> {
644        self.compact_await_tree_reg.as_ref()
645    }
646
647    pub async fn min_uncommitted_object_id(&self) -> Option<HummockRawObjectId> {
648        let (tx, rx) = oneshot::channel();
649        self.hummock_event_sender
650            .send(HummockEvent::GetMinUncommittedObjectId { result_tx: tx })
651            .expect("should send success");
652        rx.await.expect("should await success")
653    }
654
655    pub async fn sync(
656        &self,
657        sync_table_epochs: Vec<(HummockEpoch, HashSet<TableId>)>,
658    ) -> StorageResult<SyncResult> {
659        let (tx, rx) = oneshot::channel();
660        let _ = self.hummock_event_sender.send(HummockEvent::SyncEpoch {
661            sync_result_sender: tx,
662            sync_table_epochs,
663        });
664        let synced_data = rx
665            .await
666            .map_err(|_| HummockError::other("failed to receive sync result"))??;
667        Ok(synced_data.into_sync_result())
668    }
669}
670
671#[derive(Clone)]
672pub struct HummockStorageReadSnapshot {
673    epoch: HummockReadEpoch,
674    table_id: TableId,
675    table_option: TableOption,
676    recent_versions: Arc<ArcSwap<RecentVersions>>,
677    hummock_version_reader: HummockVersionReader,
678    read_version_mapping: ReadOnlyReadVersionMapping,
679    backup_reader: BackupReaderRef,
680    hummock_meta_client: Arc<dyn HummockMetaClient>,
681    simple_time_travel_version_cache: Arc<SimpleTimeTravelVersionCache>,
682}
683
684impl StateStoreGet for HummockStorageReadSnapshot {
685    fn on_key_value<'a, O: Send + 'a>(
686        &'a self,
687        key: TableKey<Bytes>,
688        read_options: ReadOptions,
689        on_key_value_fn: impl KeyValueFn<'a, O>,
690    ) -> impl StorageFuture<'a, Option<O>> {
691        self.get_inner(key, read_options, on_key_value_fn)
692    }
693}
694
695impl StateStoreRead for HummockStorageReadSnapshot {
696    type Iter = HummockStorageIterator;
697    type RevIter = HummockStorageRevIterator;
698
699    fn iter(
700        &self,
701        key_range: TableKeyRange,
702        read_options: ReadOptions,
703    ) -> impl Future<Output = StorageResult<Self::Iter>> + '_ {
704        let (l_vnode_inclusive, r_vnode_exclusive) = vnode_range(&key_range);
705        assert_eq!(
706            r_vnode_exclusive - l_vnode_inclusive,
707            1,
708            "read range {:?} for table {} iter contains more than one vnode",
709            key_range,
710            self.table_id
711        );
712        self.iter_inner(key_range, read_options)
713    }
714
715    fn rev_iter(
716        &self,
717        key_range: TableKeyRange,
718        read_options: ReadOptions,
719    ) -> impl Future<Output = StorageResult<Self::RevIter>> + '_ {
720        let (l_vnode_inclusive, r_vnode_exclusive) = vnode_range(&key_range);
721        assert_eq!(
722            r_vnode_exclusive - l_vnode_inclusive,
723            1,
724            "read range {:?} for table {} iter contains more than one vnode",
725            key_range,
726            self.table_id
727        );
728        self.rev_iter_inner(key_range, read_options)
729    }
730}
731
732impl StateStoreReadVector for HummockStorageReadSnapshot {
733    async fn nearest<'a, O: Send + 'a>(
734        &'a self,
735        vec: VectorRef<'a>,
736        options: VectorNearestOptions,
737        on_nearest_item_fn: impl OnNearestItemFn<'a, O>,
738    ) -> StorageResult<Vec<O>> {
739        let version = match self.epoch {
740            HummockReadEpoch::Committed(epoch)
741            | HummockReadEpoch::BatchQueryCommitted(epoch, _)
742            | HummockReadEpoch::TimeTravel(epoch) => {
743                self.get_epoch_hummock_version(epoch, self.table_id).await?
744            }
745            HummockReadEpoch::Backup(epoch) => self
746                .backup_reader
747                .try_get_hummock_version(self.table_id, epoch)
748                .await?
749                .ok_or_else(|| {
750                    HummockError::read_backup_error(format!(
751                        "backup include epoch {} not found",
752                        epoch
753                    ))
754                })?,
755            HummockReadEpoch::NoWait(_) => {
756                return Err(
757                    HummockError::other("nearest query does not support NoWait epoch").into(),
758                );
759            }
760        };
761        dispatch_distance_measurement!(options.measure, MeasurementType, {
762            Ok(self
763                .hummock_version_reader
764                .nearest::<MeasurementType, O>(
765                    version,
766                    self.table_id,
767                    vec,
768                    options,
769                    on_nearest_item_fn,
770                )
771                .await?)
772        })
773    }
774}
775
776impl StateStoreReadLog for HummockStorage {
777    type ChangeLogIter = ChangeLogIterator;
778
779    async fn next_epoch(&self, epoch: u64, options: NextEpochOptions) -> StorageResult<u64> {
780        fn next_epoch(
781            table_change_log: &TableChangeLogs,
782            epoch: u64,
783            table_id: TableId,
784        ) -> HummockResult<Option<u64>> {
785            let table_change_log = table_change_log.get(&table_id).ok_or_else(|| {
786                HummockError::next_epoch(format!("table {} has been dropped", table_id))
787            })?;
788            table_change_log
789                .next_epoch(epoch)
790                .map_err(|_| HummockError::change_log_retention_miss(table_id, epoch))
791        }
792        {
793            // fast path
794            if let Some(max_epoch) = self
795                .recent_versions
796                .load()
797                .latest_version()
798                .deref()
799                .state_table_info
800                .info()
801                .get(&options.table_id)
802                .map(|i| i.committed_epoch)
803                && max_epoch > epoch
804            {
805                // The next epoch exists either in the same `EpochNewChangeLog` or the next `EpochNewChangeLog`, so we fetch 2 `EpochNewChangeLogCommon`.
806                let table_change_log = self
807                    .table_change_log_manager
808                    .fetch_table_change_logs(options.table_id, (epoch, max_epoch), true, Some(2))
809                    .await?;
810                if let Some(next_epoch) = next_epoch(&table_change_log, epoch, options.table_id)? {
811                    return Ok(next_epoch);
812                }
813            }
814        }
815        let mut max_epoch = None;
816        wait_for_update(
817            &self.version_update_notifier_tx,
818            |version| {
819                let Some(mce) = version
820                    .state_table_info
821                    .info()
822                    .get(&options.table_id)
823                    .map(|i| i.committed_epoch)
824                else {
825                    return Ok(false);
826                };
827                max_epoch = Some(mce);
828                Ok(mce > epoch)
829            },
830            || format!("wait next_epoch: epoch: {} {}", epoch, options.table_id),
831        )
832        .await?;
833        // The next epoch exists either in the same `EpochNewChangeLog` or the next `EpochNewChangeLog`, so we fetch 2 `EpochNewChangeLogCommon`.
834        let table_change_log = self
835            .table_change_log_manager
836            .fetch_table_change_logs(
837                options.table_id,
838                (epoch, max_epoch.unwrap_or(u64::MAX)),
839                true,
840                Some(2),
841            )
842            .await?;
843        let next_epoch_ret = next_epoch(&table_change_log, epoch, options.table_id)?;
844        next_epoch_ret.ok_or_else(|| {
845            HummockError::next_epoch(format!(
846                "next_epoch for {} {} should be valid",
847                options.table_id, epoch
848            ))
849            .into()
850        })
851    }
852
853    async fn iter_log(
854        &self,
855        epoch_range: (u64, u64),
856        key_range: TableKeyRange,
857        options: ReadLogOptions,
858    ) -> StorageResult<Self::ChangeLogIter> {
859        let iter = self
860            .hummock_version_reader
861            .iter_log(
862                epoch_range,
863                key_range,
864                options,
865                self.table_change_log_manager.clone(),
866            )
867            .await?;
868        Ok(iter)
869    }
870}
871
872impl HummockStorage {
873    /// Waits until the local hummock version contains the epoch. If `wait_epoch` is `Current`,
874    /// we will only check whether it is le `sealed_epoch` and won't wait.
875    async fn try_wait_epoch_impl(
876        &self,
877        wait_epoch: HummockReadEpoch,
878        table_id: TableId,
879    ) -> StorageResult<()> {
880        tracing::debug!(
881            "try_wait_epoch: epoch: {:?}, table_id: {}",
882            wait_epoch,
883            table_id
884        );
885        match wait_epoch {
886            HummockReadEpoch::Committed(wait_epoch) => {
887                assert!(!is_max_epoch(wait_epoch), "epoch should not be MAX EPOCH");
888                wait_for_epoch(&self.version_update_notifier_tx, wait_epoch, table_id).await?;
889            }
890            HummockReadEpoch::BatchQueryCommitted(wait_epoch, wait_version_id) => {
891                assert!(!is_max_epoch(wait_epoch), "epoch should not be MAX EPOCH");
892                // fast path by checking recent_versions
893                {
894                    let recent_versions = self.recent_versions.load();
895                    let latest_version = recent_versions.latest_version();
896                    if latest_version.id >= wait_version_id
897                        && let Some(committed_epoch) =
898                            latest_version.table_committed_epoch(table_id)
899                        && committed_epoch >= wait_epoch
900                    {
901                        return Ok(());
902                    }
903                }
904                wait_for_update(
905                    &self.version_update_notifier_tx,
906                    |version| {
907                        if wait_version_id > version.id() {
908                            return Ok(false);
909                        }
910                        let committed_epoch =
911                            version.table_committed_epoch(table_id).ok_or_else(|| {
912                                // In batch query, since we have ensured that the current version must be after the
913                                // `wait_version_id`, when seeing that the table_id not exist in the latest version,
914                                // the table must have been dropped.
915                                HummockError::wait_epoch(format!(
916                                    "table id {} has been dropped",
917                                    table_id
918                                ))
919                            })?;
920                        Ok(committed_epoch >= wait_epoch)
921                    },
922                    || {
923                        format!(
924                            "try_wait_epoch: epoch: {}, version_id: {:?}",
925                            wait_epoch, wait_version_id
926                        )
927                    },
928                )
929                .await?;
930            }
931            _ => {}
932        };
933        Ok(())
934    }
935}
936
937impl StateStore for HummockStorage {
938    type Local = LocalHummockStorage;
939    type ReadSnapshot = HummockStorageReadSnapshot;
940    type VectorWriter = HummockVectorWriter;
941
942    /// Waits until the local hummock version contains the epoch. If `wait_epoch` is `Current`,
943    /// we will only check whether it is le `sealed_epoch` and won't wait.
944    async fn try_wait_epoch(
945        &self,
946        wait_epoch: HummockReadEpoch,
947        options: TryWaitEpochOptions,
948    ) -> StorageResult<()> {
949        self.try_wait_epoch_impl(wait_epoch, options.table_id).await
950    }
951
952    fn new_local(&self, option: NewLocalOptions) -> impl Future<Output = Self::Local> + Send + '_ {
953        self.new_local_inner(option)
954    }
955
956    async fn new_read_snapshot(
957        &self,
958        epoch: HummockReadEpoch,
959        options: NewReadSnapshotOptions,
960    ) -> StorageResult<Self::ReadSnapshot> {
961        self.try_wait_epoch_impl(epoch, options.table_id).await?;
962        Ok(HummockStorageReadSnapshot {
963            epoch,
964            table_id: options.table_id,
965            table_option: options.table_option,
966            recent_versions: self.recent_versions.clone(),
967            hummock_version_reader: self.hummock_version_reader.clone(),
968            read_version_mapping: self.read_version_mapping.clone(),
969            backup_reader: self.backup_reader.clone(),
970            hummock_meta_client: self.hummock_meta_client.clone(),
971            simple_time_travel_version_cache: self.simple_time_travel_version_cache.clone(),
972        })
973    }
974
975    async fn new_vector_writer(&self, options: NewVectorWriterOptions) -> Self::VectorWriter {
976        HummockVectorWriter::new(
977            options.table_id,
978            self.version_update_notifier_tx.clone(),
979            self.context.sstable_store.clone(),
980            self.object_id_manager.clone(),
981            self.hummock_event_sender.clone(),
982            self.hummock_version_reader.stats().clone(),
983            self.context.storage_opts.clone(),
984        )
985    }
986}
987
988#[cfg(any(test, feature = "test"))]
989impl HummockStorage {
990    pub async fn seal_and_sync_epoch(
991        &self,
992        epoch: u64,
993        table_ids: HashSet<TableId>,
994    ) -> StorageResult<risingwave_hummock_sdk::SyncResult> {
995        self.sync(vec![(epoch, table_ids)]).await
996    }
997
998    /// Used in the compaction test tool
999    pub async fn update_version_and_wait(&self, version: HummockVersion) {
1000        use tokio::task::yield_now;
1001        let version_id = version.id;
1002        self._version_update_sender
1003            .send(HummockVersionUpdate::PinnedVersion(Box::new(version)))
1004            .unwrap();
1005        loop {
1006            if self.recent_versions.load().latest_version().id() >= version_id {
1007                break;
1008            }
1009
1010            yield_now().await
1011        }
1012    }
1013
1014    pub async fn wait_version(&self, version: HummockVersion) {
1015        use tokio::task::yield_now;
1016        loop {
1017            if self.recent_versions.load().latest_version().id() >= version.id {
1018                break;
1019            }
1020
1021            yield_now().await
1022        }
1023    }
1024
1025    /// Creates a [`HummockStorage`] with default stats. Should only be used by tests.
1026    pub async fn for_test(
1027        options: Arc<StorageOpts>,
1028        sstable_store: SstableStoreRef,
1029        hummock_meta_client: Arc<dyn HummockMetaClient>,
1030        notification_client: impl NotificationClient,
1031    ) -> HummockResult<Self> {
1032        let compaction_catalog_manager = Arc::new(CompactionCatalogManager::new(Box::new(
1033            FakeRemoteTableAccessor {},
1034        )));
1035
1036        Self::new(
1037            options,
1038            sstable_store,
1039            hummock_meta_client,
1040            notification_client,
1041            compaction_catalog_manager,
1042            Arc::new(HummockStateStoreMetrics::unused()),
1043            Arc::new(CompactorMetrics::unused()),
1044            None,
1045        )
1046        .await
1047    }
1048
1049    pub fn storage_opts(&self) -> &Arc<StorageOpts> {
1050        &self.context.storage_opts
1051    }
1052
1053    pub fn version_reader(&self) -> &HummockVersionReader {
1054        &self.hummock_version_reader
1055    }
1056
1057    pub async fn wait_version_update(
1058        &self,
1059        old_id: risingwave_hummock_sdk::HummockVersionId,
1060    ) -> risingwave_hummock_sdk::HummockVersionId {
1061        use tokio::task::yield_now;
1062        loop {
1063            let cur_id = self.recent_versions.load().latest_version().id();
1064            if cur_id > old_id {
1065                return cur_id;
1066            }
1067            yield_now().await;
1068        }
1069    }
1070
1071    #[cfg(any(test, feature = "test"))]
1072    pub async fn flush_events_for_test(&self) {
1073        let (tx, rx) = oneshot::channel();
1074        self.hummock_event_sender
1075            .send(HummockEvent::FlushEvent(tx))
1076            .expect("flush event should succeed");
1077        rx.await.expect("flush event receiver dropped");
1078    }
1079}