1pub mod ingest;
6pub mod inner;
7pub mod sealed;
8
9use crate::{
10 compaction::{drop_range::OwnedBounds, state::CompactionState, CompactionStrategy},
11 config::Config,
12 file::CURRENT_VERSION_FILE,
13 format_version::FormatVersion,
14 iter_guard::{IterGuard, IterGuardImpl},
15 key::InternalKey,
16 manifest::Manifest,
17 memtable::Memtable,
18 slice::Slice,
19 table::Table,
20 value::InternalValue,
21 version::{recovery::recover, SuperVersion, SuperVersions, Version},
22 vlog::BlobFile,
23 AbstractTree, Checksum, KvPair, SeqNo, SequenceNumberCounter, TableId, UserKey, UserValue,
24 ValueType,
25};
26use inner::{TreeId, TreeInner};
27use std::{
28 ops::{Bound, RangeBounds},
29 path::Path,
30 sync::{Arc, Mutex, RwLock},
31};
32
33#[cfg(feature = "metrics")]
34use crate::metrics::Metrics;
35
36pub struct Guard(crate::Result<(UserKey, UserValue)>);
38
39impl IterGuard for Guard {
40 fn into_inner_if(
41 self,
42 pred: impl Fn(&UserKey) -> bool,
43 ) -> crate::Result<(UserKey, Option<UserValue>)> {
44 let (k, v) = self.0?;
45
46 if pred(&k) {
47 Ok((k, Some(v)))
48 } else {
49 Ok((k, None))
50 }
51 }
52
53 fn key(self) -> crate::Result<UserKey> {
54 self.0.map(|(k, _)| k)
55 }
56
57 fn size(self) -> crate::Result<u32> {
58 #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
59 self.into_inner().map(|(_, v)| v.len() as u32)
60 }
61
62 fn into_inner(self) -> crate::Result<(UserKey, UserValue)> {
63 self.0
64 }
65}
66
67fn ignore_tombstone_value(item: InternalValue) -> Option<InternalValue> {
68 if item.is_tombstone() {
69 None
70 } else {
71 Some(item)
72 }
73}
74
75#[derive(Clone)]
77pub struct Tree(#[doc(hidden)] pub Arc<TreeInner>);
78
79impl std::ops::Deref for Tree {
80 type Target = TreeInner;
81
82 fn deref(&self) -> &Self::Target {
83 &self.0
84 }
85}
86
87impl AbstractTree for Tree {
88 fn table_file_cache_size(&self) -> usize {
89 self.config
90 .descriptor_table
91 .as_ref()
92 .map_or(0, |dt| dt.len())
93 }
94
95 fn get_version_history_lock(
96 &self,
97 ) -> std::sync::RwLockWriteGuard<'_, crate::version::SuperVersions> {
98 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
99 self.version_history.write().expect("lock is poisoned")
100 }
101
102 fn next_table_id(&self) -> TableId {
103 self.0.table_id_counter.get()
104 }
105
106 fn id(&self) -> TreeId {
107 self.id
108 }
109
110 fn blob_file_count(&self) -> usize {
111 0
112 }
113
114 fn print_trace(&self, key: &[u8]) -> crate::Result<()> {
115 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
116 let super_version = self
117 .version_history
118 .read()
119 .expect("lock is poisoned")
120 .latest_version();
121
122 let key = Slice::from(key);
123
124 for kv in super_version
125 .active_memtable
126 .range(InternalKey::new(key.clone(), SeqNo::MAX, ValueType::Value)..)
127 {
128 log::info!("[Active] {kv:?}");
129 }
130
131 for mt in super_version.sealed_memtables.iter().rev() {
132 for kv in mt.range(InternalKey::new(key.clone(), SeqNo::MAX, ValueType::Value)..) {
133 log::info!("[Sealed #{}] {kv:?}", mt.id());
134 }
135 }
136
137 for table in super_version
138 .version
139 .iter_levels()
140 .flat_map(|lvl| lvl.iter())
141 .filter_map(|run| run.get_for_key(&key))
142 {
143 for kv in table.range(..) {
144 let kv = kv?;
145
146 if kv.key.user_key != key {
147 break;
148 }
149
150 log::info!("[Table #{}] {kv:?}", table.id());
151 }
152 }
153
154 Ok(())
155 }
156
157 fn get_internal_entry(&self, key: &[u8], seqno: SeqNo) -> crate::Result<Option<InternalValue>> {
158 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
159 let super_version = self
160 .version_history
161 .read()
162 .expect("lock is poisoned")
163 .get_version_for_snapshot(seqno);
164
165 Self::get_internal_entry_from_version(&super_version, key, seqno)
166 }
167
168 fn current_version(&self) -> Version {
169 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
170 self.version_history
171 .read()
172 .expect("poisoned")
173 .latest_version()
174 .version
175 }
176
177 fn get_flush_lock(&self) -> std::sync::MutexGuard<'_, ()> {
178 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
179 self.flush_lock.lock().expect("lock is poisoned")
180 }
181
182 #[cfg(feature = "metrics")]
183 fn metrics(&self) -> &Arc<crate::Metrics> {
184 &self.0.metrics
185 }
186
187 fn version_free_list_len(&self) -> usize {
188 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
189 self.version_history
190 .read()
191 .expect("lock is poisoned")
192 .free_list_len()
193 }
194
195 fn prefix<K: AsRef<[u8]>>(
196 &self,
197 prefix: K,
198 seqno: SeqNo,
199 index: Option<(Arc<Memtable>, SeqNo)>,
200 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
201 Box::new(
202 self.create_prefix(&prefix, seqno, index)
203 .map(|kv| IterGuardImpl::Standard(Guard(kv))),
204 )
205 }
206
207 fn range<K: AsRef<[u8]>, R: RangeBounds<K>>(
208 &self,
209 range: R,
210 seqno: SeqNo,
211 index: Option<(Arc<Memtable>, SeqNo)>,
212 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
213 Box::new(
214 self.create_range(&range, seqno, index)
215 .map(|kv| IterGuardImpl::Standard(Guard(kv))),
216 )
217 }
218
219 fn tombstone_count(&self) -> u64 {
221 self.current_version()
222 .iter_tables()
223 .map(Table::tombstone_count)
224 .sum()
225 }
226
227 fn weak_tombstone_count(&self) -> u64 {
229 self.current_version()
230 .iter_tables()
231 .map(Table::weak_tombstone_count)
232 .sum()
233 }
234
235 fn weak_tombstone_reclaimable_count(&self) -> u64 {
237 self.current_version()
238 .iter_tables()
239 .map(Table::weak_tombstone_reclaimable)
240 .sum()
241 }
242
243 fn drop_range<K: AsRef<[u8]>, R: RangeBounds<K>>(&self, range: R) -> crate::Result<()> {
244 let (bounds, is_empty) = Self::range_bounds_to_owned_bounds(&range);
245
246 if is_empty {
247 return Ok(());
248 }
249
250 let strategy = Arc::new(crate::compaction::drop_range::Strategy::new(bounds));
251
252 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
254 let _lock = self
255 .0
256 .major_compaction_lock
257 .write()
258 .expect("lock is poisoned");
259
260 log::info!("Starting drop_range compaction");
261 self.inner_compact(strategy, 0)
262 }
263
264 fn clear(&self) -> crate::Result<()> {
265 let config = self.tree_config();
266 let mut versions = self.get_version_history_lock();
267
268 versions.upgrade_version(
269 &config.path,
270 |v| {
271 let mut copy = v.clone();
272 copy.active_memtable = Arc::new(Memtable::new(self.memtable_id_counter.next()));
273 copy.sealed_memtables = Arc::default();
274 copy.version = Version::new(v.version.id() + 1, self.tree_type());
275 Ok(copy)
276 },
277 &config.seqno,
278 &config.visible_seqno,
279 )
280 }
281
282 #[doc(hidden)]
283 fn major_compact(&self, target_size: u64, seqno_threshold: SeqNo) -> crate::Result<()> {
284 let strategy = Arc::new(crate::compaction::major::Strategy::new(target_size));
285
286 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
288 let _lock = self
289 .0
290 .major_compaction_lock
291 .write()
292 .expect("lock is poisoned");
293
294 log::info!("Starting major compaction");
295 self.inner_compact(strategy, seqno_threshold)
296 }
297
298 fn l0_run_count(&self) -> usize {
299 self.current_version()
300 .level(0)
301 .map(|x| x.run_count())
302 .unwrap_or_default()
303 }
304
305 fn size_of<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<u32>> {
306 #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
307 Ok(self.get(key, seqno)?.map(|x| x.len() as u32))
308 }
309
310 fn filter_size(&self) -> u64 {
311 self.current_version()
312 .iter_tables()
313 .map(Table::filter_size)
314 .map(u64::from)
315 .sum()
316 }
317
318 fn pinned_filter_size(&self) -> usize {
319 self.current_version()
320 .iter_tables()
321 .map(Table::pinned_filter_size)
322 .sum()
323 }
324
325 fn pinned_block_index_size(&self) -> usize {
326 self.current_version()
327 .iter_tables()
328 .map(Table::pinned_block_index_size)
329 .sum()
330 }
331
332 fn sealed_memtable_count(&self) -> usize {
333 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
334 self.version_history
335 .read()
336 .expect("lock is poisoned")
337 .latest_version()
338 .sealed_memtables
339 .len()
340 }
341
342 fn flush_to_tables(
343 &self,
344 stream: impl Iterator<Item = crate::Result<InternalValue>>,
345 ) -> crate::Result<Option<(Vec<Table>, Option<Vec<BlobFile>>)>> {
346 use crate::{file::TABLES_FOLDER, table::multi_writer::MultiWriter};
347 use std::time::Instant;
348
349 let start = Instant::now();
350
351 let folder = self.config.path.join(TABLES_FOLDER);
352
353 let data_block_size = self.config.data_block_size_policy.get(0);
354
355 let data_block_restart_interval = self.config.data_block_restart_interval_policy.get(0);
356 let index_block_restart_interval = self.config.index_block_restart_interval_policy.get(0);
357
358 let data_block_compression = self.config.data_block_compression_policy.get(0);
359 let index_block_compression = self.config.index_block_compression_policy.get(0);
360
361 let data_block_hash_ratio = self.config.data_block_hash_ratio_policy.get(0);
362
363 let index_partitioning = self.config.index_block_partitioning_policy.get(0);
364 let filter_partitioning = self.config.filter_block_partitioning_policy.get(0);
365
366 log::debug!(
367 "Flushing memtable(s) to {}, data_block_restart_interval={data_block_restart_interval}, index_block_restart_interval={index_block_restart_interval}, data_block_size={data_block_size}, data_block_compression={data_block_compression:?}, index_block_compression={index_block_compression:?}",
368 folder.display(),
369 );
370
371 let mut table_writer = MultiWriter::new(
372 folder.clone(),
373 self.table_id_counter.clone(),
374 64 * 1_024 * 1_024,
375 0,
376 )?
377 .use_data_block_restart_interval(data_block_restart_interval)
378 .use_index_block_restart_interval(index_block_restart_interval)
379 .use_data_block_compression(data_block_compression)
380 .use_index_block_compression(index_block_compression)
381 .use_data_block_size(data_block_size)
382 .use_data_block_hash_ratio(data_block_hash_ratio)
383 .use_bloom_policy({
384 use crate::config::FilterPolicyEntry::{Bloom, None};
385 use crate::table::filter::BloomConstructionPolicy;
386
387 match self.config.filter_policy.get(0) {
388 Bloom(policy) => policy,
389 None => BloomConstructionPolicy::BitsPerKey(0.0),
390 }
391 });
392
393 if index_partitioning {
394 table_writer = table_writer.use_partitioned_index();
395 }
396 if filter_partitioning {
397 table_writer = table_writer.use_partitioned_filter();
398 }
399
400 for item in stream {
401 table_writer.write(item?)?;
402 }
403
404 let result = table_writer.finish()?;
405
406 log::debug!("Flushed memtable(s) in {:?}", start.elapsed());
407
408 let pin_filter = self.config.filter_block_pinning_policy.get(0);
409 let pin_index = self.config.index_block_pinning_policy.get(0);
410
411 let tables = result
413 .into_iter()
414 .map(|(table_id, checksum)| -> crate::Result<Table> {
415 Table::recover(
416 folder.join(table_id.to_string()),
417 checksum,
418 0,
419 self.id,
420 self.config.cache.clone(),
421 self.config.descriptor_table.clone(),
422 pin_filter,
423 pin_index,
424 #[cfg(feature = "metrics")]
425 self.metrics.clone(),
426 )
427 })
428 .collect::<crate::Result<Vec<_>>>()?;
429
430 Ok(Some((tables, None)))
431 }
432
433 #[expect(clippy::significant_drop_tightening)]
434 fn register_tables(
435 &self,
436 tables: &[Table],
437 blob_files: Option<&[BlobFile]>,
438 frag_map: Option<crate::blob_tree::FragmentationMap>,
439 sealed_memtables_to_delete: &[crate::tree::inner::MemtableId],
440 gc_watermark: SeqNo,
441 ) -> crate::Result<()> {
442 log::trace!(
443 "Registering {} tables, {} blob files",
444 tables.len(),
445 blob_files.map(<[BlobFile]>::len).unwrap_or_default(),
446 );
447
448 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
449 let mut _compaction_state = self.compaction_state.lock().expect("lock is poisoned");
450 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
451 let mut version_lock = self.version_history.write().expect("lock is poisoned");
452
453 if sealed_memtables_to_delete
456 .iter()
457 .any(|id| !version_lock.latest_version().sealed_memtables.contains(id))
458 {
459 log::debug!("Not registering tables because flush task processed some sealed memtables which do not exist (anymore)");
460 return Ok(());
461 }
462
463 version_lock.upgrade_version(
464 &self.config.path,
465 |current| {
466 let mut copy = current.clone();
467
468 copy.version = copy.version.with_new_l0_run(
469 tables,
470 blob_files,
471 frag_map.filter(|x| !x.is_empty()),
472 );
473
474 for &table_id in sealed_memtables_to_delete {
475 log::trace!("releasing sealed memtable #{table_id}");
476 copy.sealed_memtables = Arc::new(copy.sealed_memtables.remove(table_id));
477 }
478
479 Ok(copy)
480 },
481 &self.config.seqno,
482 &self.config.visible_seqno,
483 )?;
484
485 if let Err(e) = version_lock.maintenance(&self.config.path, gc_watermark) {
486 log::warn!("Version GC failed: {e:?}");
487 }
488
489 Ok(())
490 }
491
492 fn clear_active_memtable(&self) {
493 use crate::tree::sealed::SealedMemtables;
494
495 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
496 let mut version_history_lock = self.version_history.write().expect("lock is poisoned");
497 let super_version = version_history_lock.latest_version();
498
499 if super_version.active_memtable.is_empty() {
500 return;
501 }
502
503 let mut copy = version_history_lock.latest_version();
504 copy.active_memtable = Arc::new(Memtable::new(self.memtable_id_counter.next()));
505 copy.sealed_memtables = Arc::new(SealedMemtables::default());
506
507 copy.seqno = super_version.seqno;
509
510 version_history_lock.replace_latest_version(copy);
511
512 log::trace!("cleared active memtable");
513 }
514
515 fn compact(
516 &self,
517 strategy: Arc<dyn CompactionStrategy>,
518 seqno_threshold: SeqNo,
519 ) -> crate::Result<()> {
520 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
524 let _lock = self
525 .0
526 .major_compaction_lock
527 .read()
528 .expect("lock is poisoned");
529
530 self.inner_compact(strategy, seqno_threshold)
531 }
532
533 fn get_next_table_id(&self) -> TableId {
534 self.0.get_next_table_id()
535 }
536
537 fn tree_config(&self) -> &Config {
538 &self.config
539 }
540
541 fn active_memtable(&self) -> Arc<Memtable> {
542 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
543 self.version_history
544 .read()
545 .expect("lock is poisoned")
546 .latest_version()
547 .active_memtable
548 }
549
550 #[expect(clippy::significant_drop_tightening)]
551 fn rotate_memtable(&self) -> Option<Arc<Memtable>> {
552 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
553 let mut version_history_lock = self.version_history.write().expect("lock is poisoned");
554 let super_version = version_history_lock.latest_version();
555
556 if super_version.active_memtable.is_empty() {
557 return None;
558 }
559
560 let yanked_memtable = super_version.active_memtable;
561
562 let mut copy = version_history_lock.latest_version();
563 copy.active_memtable = Arc::new(Memtable::new(self.memtable_id_counter.next()));
564 copy.sealed_memtables =
565 Arc::new(super_version.sealed_memtables.add(yanked_memtable.clone()));
566
567 copy.seqno = super_version.seqno;
569
570 version_history_lock.replace_latest_version(copy);
571
572 log::trace!(
573 "rotate: added memtable id={} to sealed memtables",
574 yanked_memtable.id,
575 );
576
577 Some(yanked_memtable)
578 }
579
580 fn table_count(&self) -> usize {
581 self.current_version().table_count()
582 }
583
584 fn level_table_count(&self, idx: usize) -> Option<usize> {
585 self.current_version().level(idx).map(|x| x.table_count())
586 }
587
588 fn approximate_len(&self) -> usize {
589 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
590 let super_version = self
591 .version_history
592 .read()
593 .expect("lock is poisoned")
594 .latest_version();
595
596 let tables_item_count = self
597 .current_version()
598 .iter_tables()
599 .map(|x| x.metadata.item_count)
600 .sum::<u64>();
601
602 let memtable_count = super_version.active_memtable.len() as u64;
603 let sealed_count = super_version
604 .sealed_memtables
605 .iter()
606 .map(|mt| mt.len())
607 .sum::<usize>() as u64;
608
609 #[expect(clippy::expect_used, reason = "result should fit into usize")]
610 (memtable_count + sealed_count + tables_item_count)
611 .try_into()
612 .expect("approximate_len too large for usize")
613 }
614
615 fn disk_space(&self) -> u64 {
616 self.current_version()
617 .iter_levels()
618 .map(super::version::Level::size)
619 .sum()
620 }
621
622 fn get_highest_memtable_seqno(&self) -> Option<SeqNo> {
623 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
624 let version = self
625 .version_history
626 .read()
627 .expect("lock is poisoned")
628 .latest_version();
629
630 let active = version.active_memtable.get_highest_seqno();
631
632 let sealed = version
633 .sealed_memtables
634 .iter()
635 .map(|mt| mt.get_highest_seqno())
636 .max()
637 .flatten();
638
639 active.max(sealed)
640 }
641
642 fn get_highest_persisted_seqno(&self) -> Option<SeqNo> {
643 self.current_version()
644 .iter_tables()
645 .map(Table::get_highest_seqno)
646 .max()
647 }
648
649 fn get<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<UserValue>> {
650 Ok(self
651 .get_internal_entry(key.as_ref(), seqno)?
652 .map(|x| x.value))
653 }
654
655 fn insert<K: Into<UserKey>, V: Into<UserValue>>(
656 &self,
657 key: K,
658 value: V,
659 seqno: SeqNo,
660 ) -> (u64, u64) {
661 let value = InternalValue::from_components(key, value, seqno, ValueType::Value);
662 self.append_entry(value)
663 }
664
665 fn remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64) {
666 let value = InternalValue::new_tombstone(key, seqno);
667 self.append_entry(value)
668 }
669
670 fn remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64) {
671 let value = InternalValue::new_weak_tombstone(key, seqno);
672 self.append_entry(value)
673 }
674}
675
676impl Tree {
677 #[doc(hidden)]
678 pub fn create_internal_range<'a, K: AsRef<[u8]> + 'a, R: RangeBounds<K> + 'a>(
679 version: SuperVersion,
680 range: &'a R,
681 seqno: SeqNo,
682 ephemeral: Option<(Arc<Memtable>, SeqNo)>,
683 ) -> impl DoubleEndedIterator<Item = crate::Result<InternalValue>> + 'static {
684 use crate::range::{IterState, TreeIter};
685 use std::ops::Bound::{self, Excluded, Included, Unbounded};
686
687 let lo: Bound<UserKey> = match range.start_bound() {
688 Included(x) => Included(x.as_ref().into()),
689 Excluded(x) => Excluded(x.as_ref().into()),
690 Unbounded => Unbounded,
691 };
692
693 let hi: Bound<UserKey> = match range.end_bound() {
694 Included(x) => Included(x.as_ref().into()),
695 Excluded(x) => Excluded(x.as_ref().into()),
696 Unbounded => Unbounded,
697 };
698
699 let bounds: (Bound<UserKey>, Bound<UserKey>) = (lo, hi);
700
701 let iter_state = { IterState { version, ephemeral } };
702
703 TreeIter::create_range(iter_state, bounds, seqno)
704 }
705
706 pub(crate) fn get_internal_entry_from_version(
707 super_version: &SuperVersion,
708 key: &[u8],
709 seqno: SeqNo,
710 ) -> crate::Result<Option<InternalValue>> {
711 if let Some(entry) = super_version.active_memtable.get(key, seqno) {
712 return Ok(ignore_tombstone_value(entry));
713 }
714
715 if let Some(entry) =
717 Self::get_internal_entry_from_sealed_memtables(super_version, key, seqno)
718 {
719 return Ok(ignore_tombstone_value(entry));
720 }
721
722 Self::get_internal_entry_from_tables(&super_version.version, key, seqno)
724 }
725
726 fn get_internal_entry_from_tables(
727 version: &Version,
728 key: &[u8],
729 seqno: SeqNo,
730 ) -> crate::Result<Option<InternalValue>> {
731 let key_hash = crate::table::filter::standard_bloom::Builder::get_hash(key);
734
735 for table in version
736 .iter_levels()
737 .flat_map(|lvl| lvl.iter())
738 .filter_map(|run| run.get_for_key(key))
739 {
740 if let Some(item) = table.get(key, seqno, key_hash)? {
741 return Ok(ignore_tombstone_value(item));
742 }
743 }
744
745 Ok(None)
746 }
747
748 fn get_internal_entry_from_sealed_memtables(
749 super_version: &SuperVersion,
750 key: &[u8],
751 seqno: SeqNo,
752 ) -> Option<InternalValue> {
753 for mt in super_version.sealed_memtables.iter().rev() {
754 if let Some(entry) = mt.get(key, seqno) {
755 return Some(entry);
756 }
757 }
758
759 None
760 }
761
762 pub(crate) fn get_version_for_snapshot(&self, seqno: SeqNo) -> SuperVersion {
763 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
764 self.version_history
765 .read()
766 .expect("lock is poisoned")
767 .get_version_for_snapshot(seqno)
768 }
769
770 fn range_bounds_to_owned_bounds<K: AsRef<[u8]>, R: RangeBounds<K>>(
781 range: &R,
782 ) -> (OwnedBounds, bool) {
783 use Bound::{Excluded, Included, Unbounded};
784
785 let start = match range.start_bound() {
786 Included(key) => Included(Slice::from(key.as_ref())),
787 Excluded(key) => Excluded(Slice::from(key.as_ref())),
788 Unbounded => Unbounded,
789 };
790
791 let end = match range.end_bound() {
792 Included(key) => Included(Slice::from(key.as_ref())),
793 Excluded(key) => Excluded(Slice::from(key.as_ref())),
794 Unbounded => Unbounded,
795 };
796
797 let is_empty =
798 if let (Included(lo) | Excluded(lo), Included(hi) | Excluded(hi)) = (&start, &end) {
799 lo.as_ref() > hi.as_ref()
800 } else {
801 false
802 };
803
804 (OwnedBounds { start, end }, is_empty)
805 }
806
807 pub(crate) fn open(config: Config) -> crate::Result<Self> {
820 log::debug!("Opening LSM-tree at {}", config.path.display());
821
822 if config.path.join("version").try_exists()? {
824 log::error!("It looks like you are trying to open a V1 database - the database needs a manual migration, however a migration tool is not provided, as V1 is extremely outdated.");
825 return Err(crate::Error::InvalidVersion(FormatVersion::V1.into()));
826 }
827
828 let tree = if config.path.join(CURRENT_VERSION_FILE).try_exists()? {
829 Self::recover(config)
830 } else {
831 Self::create_new(config)
832 }?;
833
834 Ok(tree)
835 }
836
837 #[doc(hidden)]
839 #[must_use]
840 pub fn is_compacting(&self) -> bool {
841 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
842 !self
843 .compaction_state
844 .lock()
845 .expect("lock is poisoned")
846 .hidden_set()
847 .is_empty()
848 }
849
850 fn inner_compact(
851 &self,
852 strategy: Arc<dyn CompactionStrategy>,
853 mvcc_gc_watermark: SeqNo,
854 ) -> crate::Result<()> {
855 use crate::compaction::worker::{do_compaction, Options};
856
857 let mut opts = Options::from_tree(self, strategy);
858 opts.mvcc_gc_watermark = mvcc_gc_watermark;
859
860 do_compaction(&opts)?;
861
862 log::debug!("Compaction run over");
863
864 Ok(())
865 }
866
867 #[doc(hidden)]
868 #[must_use]
869 pub fn create_iter(
870 &self,
871 seqno: SeqNo,
872 ephemeral: Option<(Arc<Memtable>, SeqNo)>,
873 ) -> impl DoubleEndedIterator<Item = crate::Result<KvPair>> + 'static {
874 self.create_range::<UserKey, _>(&.., seqno, ephemeral)
875 }
876
877 #[doc(hidden)]
878 pub fn create_range<'a, K: AsRef<[u8]> + 'a, R: RangeBounds<K> + 'a>(
879 &self,
880 range: &'a R,
881 seqno: SeqNo,
882 ephemeral: Option<(Arc<Memtable>, SeqNo)>,
883 ) -> impl DoubleEndedIterator<Item = crate::Result<KvPair>> + 'static {
884 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
885 let super_version = self
886 .version_history
887 .read()
888 .expect("lock is poisoned")
889 .get_version_for_snapshot(seqno);
890
891 Self::create_internal_range(super_version, range, seqno, ephemeral).map(|item| match item {
892 Ok(kv) => Ok((kv.key.user_key, kv.value)),
893 Err(e) => Err(e),
894 })
895 }
896
897 #[doc(hidden)]
898 pub fn create_prefix<'a, K: AsRef<[u8]> + 'a>(
899 &self,
900 prefix: K,
901 seqno: SeqNo,
902 ephemeral: Option<(Arc<Memtable>, SeqNo)>,
903 ) -> impl DoubleEndedIterator<Item = crate::Result<KvPair>> + 'static {
904 use crate::range::prefix_to_range;
905
906 let range = prefix_to_range(prefix.as_ref());
907 self.create_range(&range, seqno, ephemeral)
908 }
909
910 #[doc(hidden)]
914 #[must_use]
915 pub fn append_entry(&self, value: InternalValue) -> (u64, u64) {
916 #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")]
917 self.version_history
918 .read()
919 .expect("lock is poisoned")
920 .latest_version()
921 .active_memtable
922 .insert(value)
923 }
924
925 fn recover(mut config: Config) -> crate::Result<Self> {
931 use crate::stop_signal::StopSignal;
932 use inner::get_next_tree_id;
933
934 log::info!("Recovering LSM-tree at {}", config.path.display());
935
936 let tree_id = get_next_tree_id();
937
938 #[cfg(feature = "metrics")]
939 let metrics = Arc::new(Metrics::default());
940
941 let version = Self::recover_levels(
942 &config.path,
943 tree_id,
944 &config,
945 #[cfg(feature = "metrics")]
946 &metrics,
947 )?;
948
949 {
950 let manifest_path = config.path.join(format!("v{}", version.id()));
951 let reader = sfa::Reader::new(&manifest_path)?;
952 let manifest = Manifest::decode_from(&manifest_path, &reader)?;
953
954 if manifest.version != FormatVersion::V3 {
955 return Err(crate::Error::InvalidVersion(manifest.version.into()));
956 }
957
958 let requested_tree_type = match config.kv_separation_opts {
959 Some(_) => crate::TreeType::Blob,
960 None => crate::TreeType::Standard,
961 };
962
963 if version.tree_type() != requested_tree_type {
964 log::error!(
965 "Tried to open a {requested_tree_type:?}Tree, but the existing tree is of type {:?}Tree. This indicates a misconfiguration or corruption.",
966 version.tree_type(),
967 );
968 return Err(crate::Error::Unrecoverable);
969 }
970
971 config.level_count = manifest.level_count;
973 }
974
975 let highest_table_id = version
976 .iter_tables()
977 .map(Table::id)
978 .max()
979 .unwrap_or_default();
980
981 let inner = TreeInner {
982 id: tree_id,
983 memtable_id_counter: SequenceNumberCounter::new(1),
984 table_id_counter: SequenceNumberCounter::new(highest_table_id + 1),
985 blob_file_id_counter: SequenceNumberCounter::default(),
986 version_history: Arc::new(RwLock::new(SuperVersions::new(version))),
987 stop_signal: StopSignal::default(),
988 config: Arc::new(config),
989 major_compaction_lock: RwLock::default(),
990 flush_lock: Mutex::default(),
991 compaction_state: Arc::new(Mutex::new(CompactionState::default())),
992
993 #[cfg(feature = "metrics")]
994 metrics,
995 };
996
997 Ok(Self(Arc::new(inner)))
998 }
999
1000 fn create_new(config: Config) -> crate::Result<Self> {
1002 use crate::file::{fsync_directory, TABLES_FOLDER};
1003 use std::fs::create_dir_all;
1004
1005 let path = config.path.clone();
1006 log::trace!("Creating LSM-tree at {}", path.display());
1007
1008 create_dir_all(&path)?;
1009
1010 let table_folder_path = path.join(TABLES_FOLDER);
1011 create_dir_all(&table_folder_path)?;
1012
1013 fsync_directory(&table_folder_path)?;
1015 fsync_directory(&path)?;
1016
1017 let inner = TreeInner::create_new(config)?;
1018 Ok(Self(Arc::new(inner)))
1019 }
1020
1021 fn recover_levels<P: AsRef<Path>>(
1023 tree_path: P,
1024 tree_id: TreeId,
1025 config: &Config,
1026 #[cfg(feature = "metrics")] metrics: &Arc<Metrics>,
1027 ) -> crate::Result<Version> {
1028 use crate::{file::fsync_directory, file::TABLES_FOLDER, TableId};
1029
1030 let tree_path = tree_path.as_ref();
1031
1032 let recovery = recover(tree_path)?;
1033
1034 let table_map = {
1035 let mut result: crate::HashMap<TableId, (u8 , Checksum, SeqNo)> =
1036 crate::HashMap::default();
1037
1038 for (level_idx, table_ids) in recovery.table_ids.iter().enumerate() {
1039 for run in table_ids {
1040 for table in run {
1041 #[expect(
1042 clippy::expect_used,
1043 reason = "there are always less than 256 levels"
1044 )]
1045 result.insert(
1046 table.id,
1047 (
1048 level_idx
1049 .try_into()
1050 .expect("there are less than 256 levels"),
1051 table.checksum,
1052 table.global_seqno,
1053 ),
1054 );
1055 }
1056 }
1057 }
1058
1059 result
1060 };
1061
1062 let cnt = table_map.len();
1063
1064 log::debug!("Recovering {cnt} tables from {}", tree_path.display());
1065
1066 let progress_mod = match cnt {
1067 _ if cnt <= 20 => 1,
1068 _ if cnt <= 100 => 10,
1069 _ => 100,
1070 };
1071
1072 let mut tables = vec![];
1073
1074 let table_base_folder = tree_path.join(TABLES_FOLDER);
1075
1076 if !table_base_folder.try_exists()? {
1077 std::fs::create_dir_all(&table_base_folder)?;
1078 fsync_directory(&table_base_folder)?;
1079 }
1080
1081 let mut orphaned_tables = vec![];
1082
1083 for (idx, dirent) in std::fs::read_dir(&table_base_folder)?.enumerate() {
1084 let dirent = dirent?;
1085 let file_name = dirent.file_name();
1086
1087 if file_name == ".DS_Store" {
1089 continue;
1090 }
1091
1092 if file_name.to_string_lossy().starts_with("._") {
1094 continue;
1095 }
1096
1097 let table_file_name = file_name.to_str().ok_or_else(|| {
1098 log::error!("invalid table file name {}", file_name.display());
1099 crate::Error::Unrecoverable
1100 })?;
1101
1102 let table_file_path = dirent.path();
1103 assert!(!table_file_path.is_dir());
1104
1105 let table_id = table_file_name.parse::<TableId>().map_err(|e| {
1106 log::error!("invalid table file name {table_file_name:?}: {e:?}");
1107 crate::Error::Unrecoverable
1108 })?;
1109
1110 if let Some(&(level_idx, checksum, global_seqno)) = table_map.get(&table_id) {
1111 let pin_filter = config.filter_block_pinning_policy.get(level_idx.into());
1112 let pin_index = config.index_block_pinning_policy.get(level_idx.into());
1113
1114 let table = Table::recover(
1115 table_file_path,
1116 checksum,
1117 global_seqno,
1118 tree_id,
1119 config.cache.clone(),
1120 config.descriptor_table.clone(),
1121 pin_filter,
1122 pin_index,
1123 #[cfg(feature = "metrics")]
1124 metrics.clone(),
1125 )?;
1126
1127 tables.push(table);
1128
1129 if idx % progress_mod == 0 {
1130 log::debug!("Recovered {idx}/{cnt} tables");
1131 }
1132 } else {
1133 orphaned_tables.push(table_file_path);
1134 }
1135 }
1136
1137 if tables.len() < cnt {
1138 log::error!(
1139 "Recovered less tables than expected: {:?}",
1140 table_map.keys(),
1141 );
1142 return Err(crate::Error::Unrecoverable);
1143 }
1144
1145 log::debug!("Successfully recovered {} tables", tables.len());
1146
1147 let (blob_files, orphaned_blob_files) = crate::vlog::recover_blob_files(
1148 &tree_path.join(crate::file::BLOBS_FOLDER),
1149 &recovery.blob_file_ids,
1150 tree_id,
1151 config.descriptor_table.as_ref(),
1152 )?;
1153
1154 let version = Version::from_recovery(recovery, &tables, &blob_files)?;
1155
1156 Self::cleanup_orphaned_version(tree_path, version.id())?;
1159
1160 for table_path in orphaned_tables {
1161 log::debug!("Deleting orphaned table {}", table_path.display());
1162 std::fs::remove_file(&table_path)?;
1163 }
1164
1165 for blob_file_path in orphaned_blob_files {
1166 log::debug!("Deleting orphaned blob file {}", blob_file_path.display());
1167 std::fs::remove_file(&blob_file_path)?;
1168 }
1169
1170 Ok(version)
1171 }
1172
1173 fn cleanup_orphaned_version(
1174 path: &Path,
1175 latest_version_id: crate::version::VersionId,
1176 ) -> crate::Result<()> {
1177 let version_str = format!("v{latest_version_id}");
1178
1179 for file in std::fs::read_dir(path)? {
1180 let dirent = file?;
1181
1182 if dirent.file_type()?.is_dir() {
1183 continue;
1184 }
1185
1186 let name = dirent.file_name();
1187
1188 if name.to_string_lossy().starts_with('v') && *name != *version_str {
1189 log::trace!("Cleanup orphaned version {}", name.display());
1190 std::fs::remove_file(dirent.path())?;
1191 }
1192 }
1193
1194 Ok(())
1195 }
1196}