1#![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 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]
180macro_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 pub sync_size: usize,
209 pub uncommitted_ssts: Vec<LocalSstableInfo>,
211 pub table_watermarks: HashMap<TableId, TableWatermarks>,
213 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#[derive(Debug, Clone, Copy)]
256pub enum HummockReadEpoch {
257 Committed(HummockEpoch),
259 BatchQueryCommitted(HummockEpoch, HummockVersionId),
262 NoWait(HummockEpoch),
264 Backup(HummockEpoch),
266 TimeTravel(HummockEpoch),
267 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 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 pub start_id: HummockRawObjectId,
320 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 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#[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 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 pub(crate) fn as_u64(&self) -> HummockEpoch {
443 self.0
444 }
445
446 pub fn from_u64(epoch_with_gap: u64) -> Self {
448 EpochWithGap(epoch_with_gap)
449 }
450
451 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}