Skip to main content

risingwave_hummock_sdk/
lib.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
15#![feature(impl_trait_in_assoc_type)]
16#![feature(map_try_insert)]
17#![feature(stmt_expr_attributes)]
18
19mod key_cmp;
20
21use std::borrow::Borrow;
22use std::cmp::Ordering;
23use std::collections::HashMap;
24
25pub use key_cmp::*;
26use risingwave_common::util::epoch::EPOCH_SPILL_TIME_MASK;
27use risingwave_pb::common::{BatchQueryEpoch, batch_query_epoch};
28use sstable_info::SstableInfo;
29
30use crate::key_range::KeyRangeCommon;
31use crate::table_stats::TableStatsMap;
32
33pub mod change_log;
34pub mod compact;
35pub mod compact_task;
36pub mod compaction_group;
37pub mod filter_utils;
38pub mod key;
39pub mod key_range;
40pub mod level;
41pub mod prost_key_range;
42pub mod sstable_info;
43pub mod state_table_info;
44pub mod table_stats;
45pub mod table_watermark;
46pub mod time_travel;
47pub mod version;
48pub use frontend_version::{FrontendHummockVersion, FrontendHummockVersionDelta};
49mod frontend_version;
50pub mod vector_index;
51
52pub use compact::*;
53use risingwave_common::catalog::TableId;
54use risingwave_pb::hummock::hummock_version_checkpoint::PbStaleObjects;
55use risingwave_pb::hummock::{PbVectorIndexObjectType, VectorIndexObjectType};
56pub use risingwave_pb::id::{
57    CompactionGroupId, HummockHnswGraphFileId, HummockRawObjectId, HummockSstableId,
58    HummockSstableObjectId, HummockVectorFileId, HummockVersionId,
59};
60
61use crate::table_watermark::TableWatermarks;
62use crate::vector_index::VectorIndexAdd;
63
64pub type HummockRefCount = u64;
65pub type HummockContextId = risingwave_common::id::WorkerId;
66pub type HummockEpoch = u64;
67pub type HummockCompactionTaskId = u64;
68
69pub const INVALID_VERSION_ID: HummockVersionId = HummockVersionId::new(0);
70pub const FIRST_VERSION_ID: HummockVersionId = HummockVersionId::new(1);
71pub const SPLIT_TABLE_COMPACTION_GROUP_ID_HEAD: u64 = 1u64 << 56;
72pub const SINGLE_TABLE_COMPACTION_GROUP_ID_HEAD: u64 = 2u64 << 56;
73pub const SST_OBJECT_SUFFIX: &str = "data";
74pub const VECTOR_FILE_OBJECT_SUFFIX: &str = "vector";
75pub const HUMMOCK_SSTABLE_OBJECT_ID_MAX_DECIMAL_LENGTH: usize = 20;
76
77macro_rules! for_all_object_suffix {
78    ($({$name:ident, $type_name:ty, $suffix:expr},)+) => {
79        #[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)]
80        pub enum HummockObjectId {
81            $(
82                $name($type_name),
83            )+
84        }
85
86        pub const VALID_OBJECT_ID_SUFFIXES: [&str; 3] = [$(
87                $suffix
88            ),+];
89
90        impl HummockObjectId {
91            fn new(id: u64, suffix: &str) -> Option<Self> {
92                match suffix {
93                    $(
94                        suffix if suffix == $suffix => Some(HummockObjectId::$name(<$type_name>::new(id))),
95                    )+
96                    _ => None,
97                }
98            }
99
100            pub fn suffix(&self) -> &str {
101                match self {
102                    $(
103                        HummockObjectId::$name(_) => $suffix,
104                    )+
105                }
106            }
107
108            pub fn as_raw(&self) -> HummockRawObjectId {
109                let raw = match self {
110                    $(
111                        HummockObjectId::$name(id) => id.as_raw_id(),
112                    )+
113                };
114                HummockRawObjectId::new(raw)
115            }
116        }
117
118        pub fn try_get_object_id_from_path(path: &str) -> Option<HummockObjectId> {
119            let split: Vec<_> = path.split(&['/', '.']).collect();
120            if split.len() <= 2 {
121                return None;
122            }
123            let suffix = split[split.len() - 1];
124            let id_str = split[split.len() - 2];
125            match suffix {
126                $(
127                    suffix if suffix == $suffix => {
128                        let id = id_str
129                            .parse::<u64>()
130                            .unwrap_or_else(|_| panic!("expect valid object id, got {}", id_str));
131                        Some(HummockObjectId::$name(<$type_name>::new(id)))
132                    },
133                )+
134                _ => None,
135            }
136        }
137    };
138    () => {
139        for_all_object_suffix! {
140            {Sstable, HummockSstableObjectId, SST_OBJECT_SUFFIX},
141            {VectorFile, HummockVectorFileId, VECTOR_FILE_OBJECT_SUFFIX},
142            {HnswGraphFile, HummockHnswGraphFileId, "hnsw_graph"},
143        }
144    };
145}
146
147for_all_object_suffix!();
148
149pub fn get_stale_object_ids(
150    stale_objects: &PbStaleObjects,
151) -> impl Iterator<Item = HummockObjectId> + '_ {
152    // DO NOT REMOVE THIS LINE
153    // This is to ensure that when adding new variant to `HummockObjectId`,
154    // the compiler will warn us if we forget to handle it here.
155    match HummockObjectId::Sstable(0.into()) {
156        HummockObjectId::Sstable(_) => {}
157        HummockObjectId::VectorFile(_) => {}
158        HummockObjectId::HnswGraphFile(_) => {}
159    };
160    stale_objects
161        .id
162        .iter()
163        .map(|sst_id| HummockObjectId::Sstable(*sst_id))
164        .chain(stale_objects.vector_files.iter().map(
165            |file| match file.get_object_type().unwrap() {
166                PbVectorIndexObjectType::VectorIndexObjectUnspecified => {
167                    unreachable!()
168                }
169                VectorIndexObjectType::VectorIndexObjectVector => {
170                    HummockObjectId::VectorFile(file.id.into())
171                }
172                VectorIndexObjectType::VectorIndexObjectHnswGraph => {
173                    HummockObjectId::HnswGraphFile(file.id.into())
174                }
175            },
176        ))
177}
178
179#[macro_export]
180/// This is wrapper for `info` log.
181///
182/// In our CI tests, we frequently create and drop tables, and checkpoint in all barriers, which may
183/// cause many events. However, these events are not expected to be frequent in production usage, so
184/// we print an info log for every these events. But these events are frequent in CI, and produce
185/// many logs in CI, and we may want to downgrade the log level of these event log to debug.
186/// Therefore, we provide this macro to wrap the `info` log, which will produce `info` log when
187/// `debug_assertions` is not enabled, and `debug` log when `debug_assertions` is enabled.
188macro_rules! info_in_release {
189    ($($arg:tt)*) => {
190        {
191            #[cfg(debug_assertions)]
192            {
193                use tracing::debug;
194                debug!($($arg)*);
195            }
196            #[cfg(not(debug_assertions))]
197            {
198                use tracing::info;
199                info!($($arg)*);
200            }
201        }
202    }
203}
204
205#[derive(Default, Debug)]
206pub struct SyncResult {
207    /// The size of all synced shared buffers.
208    pub sync_size: usize,
209    /// The `sst_info` of sync.
210    pub uncommitted_ssts: Vec<LocalSstableInfo>,
211    /// The collected table watermarks written by state tables.
212    pub table_watermarks: HashMap<TableId, TableWatermarks>,
213    /// Sstable that holds the uncommitted old value
214    pub old_value_ssts: Vec<LocalSstableInfo>,
215    pub vector_index_adds: HashMap<TableId, Vec<VectorIndexAdd>>,
216}
217
218#[derive(Debug, Clone)]
219pub struct LocalSstableInfo {
220    pub sst_info: SstableInfo,
221    pub table_stats: TableStatsMap,
222    pub created_at: u64,
223}
224
225impl LocalSstableInfo {
226    pub fn new(sst_info: SstableInfo, table_stats: TableStatsMap, created_at: u64) -> Self {
227        Self {
228            sst_info,
229            table_stats,
230            created_at,
231        }
232    }
233
234    pub fn for_test(sst_info: SstableInfo) -> Self {
235        Self {
236            sst_info,
237            table_stats: Default::default(),
238            created_at: u64::MAX,
239        }
240    }
241
242    pub fn file_size(&self) -> u64 {
243        assert_eq!(self.sst_info.file_size, self.sst_info.sst_size);
244        self.sst_info.file_size
245    }
246}
247
248impl PartialEq for LocalSstableInfo {
249    fn eq(&self, other: &Self) -> bool {
250        self.sst_info == other.sst_info
251    }
252}
253
254/// Package read epoch of hummock, it be used for `wait_epoch`
255#[derive(Debug, Clone, Copy)]
256pub enum HummockReadEpoch {
257    /// We need to wait the `committed_epoch` of the read table
258    Committed(HummockEpoch),
259    /// Wait for the table's committed epoch. The version ID is used only when the table is
260    /// absent, to distinguish delayed registration from a dropped table.
261    BatchQueryCommitted(HummockEpoch, HummockVersionId),
262    /// We don't need to wait epoch, we usually do stream reading with it.
263    NoWait(HummockEpoch),
264    /// We don't need to wait epoch.
265    Backup(HummockEpoch),
266    TimeTravel(HummockEpoch),
267    /// Resolve the local committed version and table epoch when creating a snapshot.
268    /// The version ID distinguishes delayed table registration from a dropped table.
269    LatestCommitted(HummockVersionId),
270}
271
272impl From<BatchQueryEpoch> for HummockReadEpoch {
273    fn from(e: BatchQueryEpoch) -> Self {
274        match e.epoch.unwrap() {
275            batch_query_epoch::Epoch::Committed(epoch) => {
276                HummockReadEpoch::BatchQueryCommitted(epoch.epoch, epoch.hummock_version_id)
277            }
278            batch_query_epoch::Epoch::Current(epoch) => HummockReadEpoch::NoWait(epoch),
279            batch_query_epoch::Epoch::Backup(epoch) => HummockReadEpoch::Backup(epoch),
280            batch_query_epoch::Epoch::TimeTravel(epoch) => HummockReadEpoch::TimeTravel(epoch),
281            batch_query_epoch::Epoch::LatestCommitted(epoch) => {
282                HummockReadEpoch::LatestCommitted(epoch.hummock_version_id)
283            }
284        }
285    }
286}
287
288pub fn test_batch_query_epoch() -> BatchQueryEpoch {
289    BatchQueryEpoch {
290        epoch: Some(batch_query_epoch::Epoch::Current(u64::MAX)),
291    }
292}
293
294impl HummockReadEpoch {
295    /// Returns no numeric epoch until a latest-committed request has been resolved.
296    pub fn get_epoch(&self) -> Option<HummockEpoch> {
297        match self {
298            HummockReadEpoch::Committed(epoch)
299            | HummockReadEpoch::BatchQueryCommitted(epoch, _)
300            | HummockReadEpoch::NoWait(epoch)
301            | HummockReadEpoch::Backup(epoch)
302            | HummockReadEpoch::TimeTravel(epoch) => Some(*epoch),
303            HummockReadEpoch::LatestCommitted(_) => None,
304        }
305    }
306
307    pub fn is_read_committed(&self) -> bool {
308        match self {
309            HummockReadEpoch::Committed(_)
310            | HummockReadEpoch::TimeTravel(_)
311            | HummockReadEpoch::BatchQueryCommitted(_, _)
312            | HummockReadEpoch::LatestCommitted(_) => true,
313            HummockReadEpoch::NoWait(_) | HummockReadEpoch::Backup(_) => false,
314        }
315    }
316}
317pub struct ObjectIdRange {
318    // inclusive
319    pub start_id: HummockRawObjectId,
320    // exclusive
321    pub end_id: HummockRawObjectId,
322}
323
324impl ObjectIdRange {
325    pub fn new(
326        start_id: impl Into<HummockRawObjectId>,
327        end_id: impl Into<HummockRawObjectId>,
328    ) -> Self {
329        Self {
330            start_id: start_id.into(),
331            end_id: end_id.into(),
332        }
333    }
334
335    fn peek_next_object_id(&self) -> Option<HummockRawObjectId> {
336        if self.start_id < self.end_id {
337            return Some(self.start_id);
338        }
339        None
340    }
341
342    /// Pops and returns next SST id.
343    pub fn get_next_object_id(&mut self) -> Option<HummockRawObjectId> {
344        let next_id = self.peek_next_object_id();
345        self.start_id += 1;
346        next_id
347    }
348}
349
350pub fn can_concat(ssts: &[impl Borrow<SstableInfo>]) -> bool {
351    let len = ssts.len();
352    for i in 1..len {
353        if ssts[i - 1]
354            .borrow()
355            .key_range
356            .compare_right_with(&ssts[i].borrow().key_range.left)
357            != Ordering::Less
358        {
359            return false;
360        }
361    }
362    true
363}
364
365pub fn full_key_can_concat(ssts: &[SstableInfo]) -> bool {
366    let len = ssts.len();
367    for i in 1..len {
368        let sst_1 = &ssts[i - 1];
369        let sst_2 = &ssts[i];
370
371        if sst_1.key_range.right_exclusive {
372            if KeyComparator::compare_encoded_full_key(
373                &sst_1.key_range.right,
374                &sst_2.key_range.left,
375            )
376            .is_gt()
377            {
378                return false;
379            }
380        } else if KeyComparator::compare_encoded_full_key(
381            &sst_1.key_range.right,
382            &sst_2.key_range.left,
383        )
384        .is_ge()
385        {
386            return false;
387        }
388    }
389    true
390}
391
392const CHECKPOINT_DIR: &str = "checkpoint";
393const CHECKPOINT_NAME: &str = "0";
394const ARCHIVE_DIR: &str = "archive";
395
396pub fn version_checkpoint_path(root_dir: &str) -> String {
397    format!("{}/{}/{}", root_dir, CHECKPOINT_DIR, CHECKPOINT_NAME)
398}
399
400pub fn version_archive_dir(root_dir: &str) -> String {
401    format!("{}/{}", root_dir, ARCHIVE_DIR)
402}
403
404pub fn version_checkpoint_dir(checkpoint_path: &str) -> String {
405    checkpoint_path.trim_end_matches(|c| c != '/').to_owned()
406}
407
408/// Represents an epoch with a gap.
409///
410/// When a spill of the mem table occurs between two epochs, `EpochWithGap` generates an offset.
411/// This offset is encoded when performing full key encoding. When returning to the upper-level
412/// interface, a pure epoch with the lower 16 bits set to 0 should be returned.
413#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug, PartialOrd, Ord)]
414pub struct EpochWithGap(u64);
415
416impl EpochWithGap {
417    pub fn new(epoch: u64, spill_offset: u16) -> Self {
418        // We only use 48 high bit to store epoch and use 16 low bit to store spill offset. But for MAX epoch,
419        // we still keep `u64::MAX` because we have use it in delete range and persist this value to sstable files.
420        //  So for compatibility, we must skip checking it for u64::MAX. See bug description in https://github.com/risingwavelabs/risingwave/issues/13717
421        if risingwave_common::util::epoch::is_max_epoch(epoch) {
422            EpochWithGap::new_max_epoch()
423        } else {
424            debug_assert!((epoch & EPOCH_SPILL_TIME_MASK) == 0);
425            EpochWithGap(epoch + spill_offset as u64)
426        }
427    }
428
429    pub fn new_from_epoch(epoch: u64) -> Self {
430        EpochWithGap::new(epoch, 0)
431    }
432
433    pub fn new_min_epoch() -> Self {
434        EpochWithGap(0)
435    }
436
437    pub fn new_max_epoch() -> Self {
438        EpochWithGap(HummockEpoch::MAX)
439    }
440
441    // return the epoch_with_gap(epoch + spill_offset)
442    pub(crate) fn as_u64(&self) -> HummockEpoch {
443        self.0
444    }
445
446    // return the epoch_with_gap(epoch + spill_offset)
447    pub fn from_u64(epoch_with_gap: u64) -> Self {
448        EpochWithGap(epoch_with_gap)
449    }
450
451    // return the pure epoch without spill offset
452    pub fn pure_epoch(&self) -> HummockEpoch {
453        self.0 & !EPOCH_SPILL_TIME_MASK
454    }
455
456    pub fn offset(&self) -> u64 {
457        self.0 & EPOCH_SPILL_TIME_MASK
458    }
459}
460
461pub fn get_object_data_path(
462    obj_prefix: &str,
463    path_prefix: &str,
464    object_id: HummockObjectId,
465) -> String {
466    let suffix = object_id.suffix();
467    let object_id = object_id.as_raw();
468
469    let mut path = String::with_capacity(
470        path_prefix.len()
471            + "/".len()
472            + obj_prefix.len()
473            + HUMMOCK_SSTABLE_OBJECT_ID_MAX_DECIMAL_LENGTH
474            + ".".len()
475            + suffix.len(),
476    );
477    path.push_str(path_prefix);
478    path.push('/');
479    path.push_str(obj_prefix);
480    path.push_str(&object_id.to_string());
481    path.push('.');
482    path.push_str(suffix);
483    path
484}
485
486pub fn get_object_id_from_path(path: &str) -> HummockObjectId {
487    use itertools::Itertools;
488    let split = path.split(&['/', '.']).collect_vec();
489    assert!(split.len() > 2);
490    let suffix = split[split.len() - 1];
491    let id = split[split.len() - 2]
492        .parse::<u64>()
493        .expect("valid object id");
494    HummockObjectId::new(id, suffix)
495        .unwrap_or_else(|| panic!("unknown object id suffix {}", suffix))
496}
497
498#[cfg(test)]
499mod tests {
500    use bytes::Bytes;
501    use sstable_info::SstableInfoInner;
502
503    use super::*;
504
505    #[test]
506    fn test_latest_committed_epoch() {
507        let epoch = HummockReadEpoch::from(BatchQueryEpoch {
508            epoch: Some(batch_query_epoch::Epoch::LatestCommitted(
509                risingwave_pb::common::BatchQueryLatestCommittedEpoch {
510                    hummock_version_id: 123.into(),
511                },
512            )),
513        });
514        assert!(
515            matches!(epoch, HummockReadEpoch::LatestCommitted(version_id) if version_id == HummockVersionId::new(123))
516        );
517        assert_eq!(epoch.get_epoch(), None);
518        assert!(epoch.is_read_committed());
519        assert_eq!(HummockReadEpoch::Committed(42).get_epoch(), Some(42));
520    }
521
522    #[test]
523    fn test_object_id_decimal_max_length() {
524        let len = u64::MAX.to_string().len();
525        assert_eq!(len, HUMMOCK_SSTABLE_OBJECT_ID_MAX_DECIMAL_LENGTH)
526    }
527
528    #[test]
529    fn test_full_key_concat() {
530        let key1 = b"\0\0\0\x08\0\0\0\x0112-3\0\0\0\0\x04\0\x1c\x16l'\xe2\0\0";
531        let key2 = b"\0\0\0\x08\0\0\0\x0112-3\0\0\0\0\x04\0\x1c\x16l \x12\0\0";
532
533        let sst_1 = SstableInfoInner {
534            key_range: key_range::KeyRange {
535                left: Bytes::from(key1.to_vec()),
536                right: Bytes::from(key1.to_vec()),
537                right_exclusive: false,
538            },
539            ..Default::default()
540        };
541
542        let sst_2 = SstableInfoInner {
543            key_range: key_range::KeyRange {
544                left: Bytes::from(key2.to_vec()),
545                right: Bytes::from(key2.to_vec()),
546                right_exclusive: false,
547            },
548            ..Default::default()
549        };
550
551        let sst_3 = SstableInfoInner {
552            key_range: key_range::KeyRange {
553                left: Bytes::from(key1.to_vec()),
554                right: Bytes::from(key2.to_vec()),
555                right_exclusive: false,
556            },
557            ..Default::default()
558        };
559
560        assert!(full_key_can_concat(&[sst_1.clone().into(), sst_2.into()]));
561
562        assert!(!full_key_can_concat(&[sst_1.into(), sst_3.into()]));
563    }
564}