1use std::{
2 borrow::Cow,
3 collections::HashSet,
4 fmt::Display,
5 hash::BuildHasherDefault,
6 io::{BufWriter, ErrorKind, Write},
7 mem::take,
8 ops::RangeInclusive,
9 path::{Path, PathBuf},
10 sync::{
11 OnceLock,
12 atomic::{AtomicBool, AtomicU32, Ordering},
13 },
14};
15
16use anyhow::{Context, Result, bail};
17use auto_hash_map::AutoSet;
18use byteorder::{BE, ReadBytesExt, WriteBytesExt};
19use dashmap::DashSet;
20#[cfg(feature = "mmap")]
21use either::Either;
22use fs_err::{self as fs, File, OpenOptions, ReadDir};
23use jiff::Timestamp;
24#[cfg(feature = "mmap")]
25use memmap2::Mmap;
26use nohash_hasher::BuildNoHashHasher;
27use parking_lot::{Mutex, RwLock};
28use rustc_hash::{FxHashSet, FxHasher};
29use serde::{Deserialize, Serialize};
30use smallvec::SmallVec;
31use tracing::span::EnteredSpan;
32
33pub use crate::compaction::selector::CompactConfig;
34#[cfg(feature = "mmap")]
35use crate::{AccessMode, mmap_helper::advise_mmap_for_persistence};
36use crate::{
37 DbConfig, FamilyKind, QueryKey,
38 arc_bytes::ArcBytes,
39 compaction::selector::{Compactable, MergeJob, plan_compaction},
40 compression::{Compression, checksum_block, decompress_into_arc},
41 constants::{
42 DATA_THRESHOLD_PER_COMPACTED_FILE, KEY_BLOCK_AVG_SIZE, KEY_BLOCK_CACHE_SIZE,
43 MAX_ENTRIES_PER_COMPACTED_FILE, VALUE_BLOCK_AVG_SIZE, VALUE_BLOCK_CACHE_SIZE,
44 },
45 key::{StoreKey, hash_key},
46 lookup_entry::{IterValue, LookupEntry, LookupValue},
47 merge_iter::MergeIter,
48 meta_file::{MetaEntryFlags, MetaFile, MetaLookupResult, StaticSortedFileRange},
49 meta_file_builder::MetaFileBuilder,
50 parallel_scheduler::ParallelScheduler,
51 rc_bytes::RcBytes,
52 shard::{MetaEntryIndex, ShardBits, ShardIndex},
53 sst_filter::SstFilter,
54 static_sorted_file::{BlockCache, SstLookupResult, StaticSortedFileIter},
55 static_sorted_file_builder::{StaticSortedFileBuilderMeta, StreamingSstWriter},
56 write_batch::{FinishResult, NewFile, WriteBatch},
57};
58
59#[cfg(feature = "stats")]
60#[derive(Debug)]
61pub struct CacheStatistics {
62 pub hit_rate: f32,
63 pub fill: f32,
64 pub items: usize,
65 pub size: u64,
66 pub hits: u64,
67 pub misses: u64,
68}
69
70#[cfg(feature = "stats")]
71impl CacheStatistics {
72 fn new<Key, Val, We, B, L>(cache: &quick_cache::sync::Cache<Key, Val, We, B, L>) -> Self
73 where
74 Key: Eq + std::hash::Hash,
75 Val: Clone,
76 We: quick_cache::Weighter<Key, Val> + Clone,
77 B: std::hash::BuildHasher + Clone,
78 L: quick_cache::Lifecycle<Key, Val> + Clone,
79 {
80 let size = cache.weight();
81 let hits = cache.hits();
82 let misses = cache.misses();
83 Self {
84 hit_rate: hits as f32 / (hits + misses) as f32,
85 fill: size as f32 / cache.capacity() as f32,
86 items: cache.len(),
87 size,
88 hits,
89 misses,
90 }
91 }
92}
93
94#[cfg(feature = "stats")]
95#[derive(Debug)]
96pub struct Statistics {
97 pub meta_files: usize,
98 pub sst_files: usize,
99 pub key_block_cache: CacheStatistics,
100 pub value_block_cache: CacheStatistics,
101 pub hits: u64,
102 pub misses: u64,
103 pub miss_range: u64,
104 pub miss_amqf: u64,
105 pub miss_key: u64,
106}
107
108#[cfg(feature = "stats")]
109#[derive(Default)]
110struct TrackedStats {
111 hits_deleted: std::sync::atomic::AtomicU64,
112 hits_small: std::sync::atomic::AtomicU64,
113 hits_blob: std::sync::atomic::AtomicU64,
114 miss_range: std::sync::atomic::AtomicU64,
115 miss_amqf: std::sync::atomic::AtomicU64,
116 miss_key: std::sync::atomic::AtomicU64,
117 miss_global: std::sync::atomic::AtomicU64,
118}
119
120enum ActiveWriteState {
122 Active(&'static str),
125 Error,
128}
129
130enum DeferredDeletion {
136 Sst(u32),
137 Meta(u32),
138 Blob(u32),
139}
140
141pub(crate) struct WriteOperationGuard<'a> {
148 active: &'a Mutex<Option<ActiveWriteState>>,
150 path: &'a Path,
152 seq_before: u32,
156 succeeded: bool,
158}
159
160impl WriteOperationGuard<'_> {
161 pub(crate) fn success(&mut self) {
165 self.succeeded = true;
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187pub struct CurrentDbVersion {
188 pub max_sequence_number: u32,
190 pub commit_time: Timestamp,
192}
193
194pub fn read_current_version(path: &Path) -> Result<Option<CurrentDbVersion>> {
199 let current_path = path.join("CURRENT");
200 let content = match fs::read(¤t_path) {
201 Ok(content) => content,
202 Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None),
203 Err(e) => return Err(e).context("Failed to read CURRENT file"),
204 };
205
206 serde_json::from_slice::<CurrentDbVersion>(&content)
207 .with_context(|| {
208 format!(
209 "CURRENT file at {} is corrupt ({} bytes)",
210 current_path.display(),
211 content.len()
212 )
213 })
214 .map(Some)
215}
216
217fn commit_current(path: &Path, seq: u32) -> Result<()> {
226 let version: &CurrentDbVersion = &CurrentDbVersion {
227 max_sequence_number: seq,
228 commit_time: Timestamp::now(),
229 };
230 let mut contents =
231 serde_json::to_string(version).context("Failed to serialize the CURRENT file")?;
232 contents.push('\n');
233 let next_path = path.join("CURRENT.next");
234 let mut next_file = File::create(&next_path)?;
235 next_file.write_all(contents.as_bytes())?;
236 next_file.sync_data()?;
237 drop(next_file);
238 fs::rename(&next_path, path.join("CURRENT"))?;
239 #[cfg(not(any(windows, miri)))]
253 File::open(path)
254 .and_then(|dir| dir.sync_data())
255 .context("Failed to sync database directory after updating CURRENT")?;
256 Ok(())
257}
258
259fn delete_orphan_files(path: &Path, seq_before: u32) -> Result<()> {
264 commit_current(path, seq_before).context("Unable to restore CURRENT file")?;
267
268 for entry in fs::read_dir(path)? {
269 let entry = entry?;
270 let path = entry.path();
271 if let Some(ext) = path.extension().and_then(|s| s.to_str())
272 && let Some(seq) = path
273 .file_stem()
274 .and_then(|s| s.to_str())
275 .and_then(|s| s.parse::<u32>().ok())
276 && seq > seq_before
277 {
278 match ext {
279 "sst" | "meta" | "blob" | "del" => fs::remove_file(&path)?,
280 _ => {}
281 }
282 }
283 }
284 Ok(())
285}
286
287impl Drop for WriteOperationGuard<'_> {
288 fn drop(&mut self) {
289 if self.succeeded {
290 *self.active.lock() = None;
292 return;
293 }
294
295 match delete_orphan_files(self.path, self.seq_before) {
298 Ok(()) => *self.active.lock() = None,
299 Err(_) => *self.active.lock() = Some(ActiveWriteState::Error),
300 }
301 }
302}
303
304pub struct TurboPersistence<S: ParallelScheduler, const FAMILIES: usize> {
307 parallel_scheduler: S,
308 path: PathBuf,
310 read_only: bool,
313 inner: RwLock<Inner<FAMILIES>>,
315 is_empty: AtomicBool,
318 active_write_operation: Mutex<Option<ActiveWriteState>>,
321 deferred_deletions: Mutex<Vec<DeferredDeletion>>,
325 key_block_cache: OnceLock<BlockCache>,
329 value_block_cache: OnceLock<BlockCache>,
332 config: DbConfig<FAMILIES>,
334 #[cfg(feature = "stats")]
336 stats: TrackedStats,
337}
338
339struct Inner<const FAMILIES: usize> {
341 meta_files_by_family: [Vec<MetaFile>; FAMILIES],
345 shard_indices: [ShardIndex; FAMILIES],
348 accessed_key_hashes: [DashSet<u64, BuildNoHashHasher<u64>>; FAMILIES],
352 current_sequence_number: u32,
354}
355
356impl<const FAMILIES: usize> Inner<FAMILIES> {
357 fn is_empty(&self) -> bool {
358 self.meta_files_by_family.iter().all(Vec::is_empty)
359 }
360
361 fn push_meta_file(&mut self, meta_file: MetaFile) {
362 let family = meta_file.family() as usize;
363 debug_assert!(family < FAMILIES, "meta file family is out of bounds");
364 let shard = &mut self.meta_files_by_family[family];
365 debug_assert!(
366 shard.last().is_none_or(|previous| {
367 previous.sequence_number() < meta_file.sequence_number()
368 }),
369 "meta file appended out of sequence order for family {family}"
370 );
371 shard.push(meta_file);
372 }
373
374 #[cfg(debug_assertions)]
375 fn debug_assert_meta_invariants(&self) {
376 for (family, meta_files) in self.meta_files_by_family.iter().enumerate() {
377 debug_assert!(
378 meta_files
379 .iter()
380 .all(|meta| meta.family() as usize == family),
381 "meta file stored in the wrong family shard"
382 );
383 debug_assert!(
384 meta_files
385 .windows(2)
386 .all(|pair| pair[0].sequence_number() < pair[1].sequence_number()),
387 "meta files in family {family} are not in ascending sequence order"
388 );
389 }
390 }
391}
392
393pub struct CommitOptions {
394 new_meta_files: Vec<NewFile>,
395 new_sst_files: Vec<NewFile>,
396 new_blob_files: Vec<NewFile>,
397 sst_files_to_delete: Vec<DeletedFile>,
398 blob_seq_numbers_to_delete: Vec<u32>,
399 sequence_number: u32,
400 keys_written: u64,
401}
402
403#[derive(Clone, Copy)]
406struct DeletedFile {
407 seq: u32,
408 size: u64,
410}
411
412#[derive(Clone, Copy, Debug, Default)]
415pub struct CommitStats {
416 pub bytes_written: u64,
418 pub bytes_deleted: u64,
420}
421
422impl Display for CommitStats {
423 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424 let CommitStats {
425 bytes_written,
426 bytes_deleted,
427 } = self;
428 write!(
429 f,
430 "bytes_written={bytes_written} bytes_deleted={bytes_deleted}"
431 )
432 }
433}
434
435struct OpenOpts<S: ParallelScheduler, const FAMILIES: usize> {
436 path: PathBuf,
437 read_only: bool,
438 parallel_scheduler: S,
439 config: DbConfig<FAMILIES>,
440}
441
442impl<S: ParallelScheduler + Default, const FAMILIES: usize> TurboPersistence<S, FAMILIES> {
443 pub fn open(path: PathBuf) -> Result<Self> {
448 Self::open_with_parallel_scheduler(path, Default::default())
449 }
450
451 pub fn open_with_config(path: PathBuf, config: DbConfig<FAMILIES>) -> Result<Self> {
453 Self::open_with_config_and_parallel_scheduler(path, config, Default::default())
454 }
455
456 pub fn open_read_only_with_config(path: PathBuf, config: DbConfig<FAMILIES>) -> Result<Self> {
459 Self::open_read_only_with_parallel_scheduler(path, config, Default::default())
460 }
461
462 pub fn empty_in_memory_with_config(config: DbConfig<FAMILIES>) -> Self {
466 Self::new(OpenOpts {
469 path: PathBuf::new(),
470 read_only: true,
471 parallel_scheduler: Default::default(),
472 config,
473 })
474 }
475}
476
477impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES> {
478 fn new(
479 OpenOpts {
480 path,
481 read_only,
482 parallel_scheduler,
483 config,
484 }: OpenOpts<S, FAMILIES>,
485 ) -> Self {
486 Self {
487 parallel_scheduler,
488 path,
489 read_only,
490 inner: RwLock::new(Inner {
491 meta_files_by_family: [(); FAMILIES].map(|_| Vec::new()),
492 shard_indices: [(); FAMILIES].map(|_| ShardIndex::default()),
493 accessed_key_hashes: [(); FAMILIES]
494 .map(|_| DashSet::with_hasher(BuildNoHashHasher::default())),
495 current_sequence_number: 0,
496 }),
497 is_empty: AtomicBool::new(true),
498 active_write_operation: Mutex::new(None),
499 deferred_deletions: Mutex::new(Vec::new()),
500 key_block_cache: OnceLock::new(),
501 value_block_cache: OnceLock::new(),
502 config,
503 #[cfg(feature = "stats")]
504 stats: TrackedStats::default(),
505 }
506 }
507
508 pub fn open_with_parallel_scheduler(path: PathBuf, parallel_scheduler: S) -> Result<Self> {
513 Self::open_with_config_and_parallel_scheduler(path, DbConfig::default(), parallel_scheduler)
514 }
515
516 pub fn open_with_config_and_parallel_scheduler(
518 path: PathBuf,
519 config: DbConfig<FAMILIES>,
520 parallel_scheduler: S,
521 ) -> Result<Self> {
522 let mut db = Self::new(OpenOpts {
523 path,
524 read_only: false,
525 parallel_scheduler,
526 config,
527 });
528 db.open_directory(false)?;
529 Ok(db)
530 }
531
532 fn open_read_only_with_parallel_scheduler(
535 path: PathBuf,
536 config: DbConfig<FAMILIES>,
537 parallel_scheduler: S,
538 ) -> Result<Self> {
539 let mut db = Self::new(OpenOpts {
540 path,
541 read_only: true,
542 parallel_scheduler,
543 config,
544 });
545 db.open_directory(false)?;
546 Ok(db)
547 }
548
549 fn open_directory(&mut self, read_only: bool) -> Result<()> {
551 match fs::read_dir(&self.path) {
552 Ok(entries) => {
553 if !self
554 .load_directory(entries, read_only)
555 .context("Loading persistence directory failed")?
556 {
557 if read_only {
558 bail!("Failed to open database");
559 }
560 commit_current(&self.path, 0)
561 .context("Initializing persistence directory failed")?;
562 }
563 Ok(())
564 }
565 Err(e) => {
566 if !read_only && e.kind() == ErrorKind::NotFound {
567 self.create_and_init_directory()
568 .context("Creating and initializing persistence directory failed")?;
569 Ok(())
570 } else {
571 Err(e).context("Failed to open database")
572 }
573 }
574 }
575 }
576
577 fn create_and_init_directory(&mut self) -> Result<()> {
579 fs::create_dir_all(&self.path)?;
580 commit_current(&self.path, 0)
581 }
582
583 fn load_directory(&mut self, entries: ReadDir, read_only: bool) -> Result<bool> {
585 let mut meta_files = Vec::new();
586 let current = match read_current_version(&self.path)? {
587 Some(version) => version.max_sequence_number,
588 None if !read_only => return Ok(false),
589 None => bail!("Failed to open database: CURRENT file is missing"),
590 };
591
592 let mut deleted_files = HashSet::new();
593 for entry in entries {
594 let entry = entry?;
595 let path = entry.path();
596 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
597 if path.file_stem().and_then(|s| s.to_str()) == Some("CURRENT") {
601 if !read_only {
602 fs::remove_file(&path)?;
603 }
604 continue;
605 }
606 let seq: u32 = path
607 .file_stem()
608 .context("File has no file stem")?
609 .to_str()
610 .context("File stem is not valid utf-8")?
611 .parse()?;
612 if deleted_files.contains(&seq) {
613 continue;
614 }
615 if seq > current {
616 if !read_only {
617 fs::remove_file(&path)?;
618 }
619 } else {
620 match ext {
621 "meta" => {
622 meta_files.push(seq);
623 }
624 "del" => {
625 let mut content = &*fs::read(&path)?;
626 let mut no_existing_files = true;
627 while !content.is_empty() {
628 let seq = content.read_u32::<BE>()?;
629 deleted_files.insert(seq);
630 if !read_only {
631 let sst_file = self.path.join(format!("{seq:08}.sst"));
633 let meta_file = self.path.join(format!("{seq:08}.meta"));
634 let blob_file = self.path.join(format!("{seq:08}.blob"));
635 for path in [sst_file, meta_file, blob_file] {
636 if fs::exists(&path)? {
637 fs::remove_file(path)?;
638 no_existing_files = false;
639 }
640 }
641 }
642 }
643 if !read_only && no_existing_files {
644 fs::remove_file(&path)?;
645 }
646 }
647 "blob" | "sst" => {
648 }
650 _ => {
651 if !path
652 .file_name()
653 .is_some_and(|s| s.as_encoded_bytes().starts_with(b"."))
654 {
655 bail!("Unexpected file in persistence directory: {:?}", path);
656 }
657 }
658 }
659 }
660 } else {
661 match path.file_stem().and_then(|s| s.to_str()) {
662 Some("CURRENT") => {
663 }
665 Some("LOG") => {
666 }
668 _ => {
669 if !path
670 .file_name()
671 .is_some_and(|s| s.as_encoded_bytes().starts_with(b"."))
672 {
673 bail!("Unexpected file in persistence directory: {:?}", path);
674 }
675 }
676 }
677 }
678 }
679
680 meta_files.retain(|seq| !deleted_files.contains(seq));
681 meta_files.sort_unstable();
682 let mut meta_files = self
683 .parallel_scheduler
684 .parallel_map_collect::<_, _, Result<Vec<MetaFile>>>(&meta_files, |&seq| {
685 let meta_file = MetaFile::open(
686 &self.path,
687 seq,
688 Some(&self.config.family_configs),
689 self.config.access_mode,
690 )?;
691 Ok(meta_file)
692 })?;
693
694 let mut sst_filter = SstFilter::new();
695 for meta_file in meta_files.iter_mut().rev() {
696 sst_filter.apply_filter(meta_file);
697 }
698
699 let inner = self.inner.get_mut();
700 for meta_file in meta_files {
701 inner.push_meta_file(meta_file);
702 }
703 #[cfg(debug_assertions)]
704 inner.debug_assert_meta_invariants();
705 rebuild_shard_index(&self.config, inner);
706 inner.current_sequence_number = current;
707 self.is_empty.store(inner.is_empty(), Ordering::Relaxed);
708 Ok(true)
709 }
710
711 #[tracing::instrument(level = "info", name = "reading database blob", skip_all)]
713 fn read_blob(&self, seq: u32, compression: Compression) -> Result<ArcBytes> {
714 let path = self.path.join(format!("{seq:08}.blob"));
715 #[cfg(feature = "mmap")]
716 let file = File::open(&path)?;
717 #[cfg(feature = "mmap")]
718 let data: Either<Mmap, Vec<u8>> = match self.config.access_mode {
719 AccessMode::Mmap => {
720 let mmap = unsafe { Mmap::map(file.file()) }.with_context(|| {
721 format!(
722 "Failed to mmap blob file {} ({} bytes)",
723 path.display(),
724 file.metadata().map(|m| m.len()).unwrap_or(0)
725 )
726 })?;
727 #[cfg(unix)]
728 mmap.advise(memmap2::Advice::Sequential)?;
729 #[cfg(unix)]
730 mmap.advise(memmap2::Advice::WillNeed)?;
731 advise_mmap_for_persistence(&mmap)?;
732 Either::Left(mmap)
733 }
734 AccessMode::File => Either::Right(fs::read(&path)?),
735 };
736 #[cfg(feature = "mmap")]
737 let mut reader: &[u8] = match &data {
738 Either::Left(mmap) => mmap,
739 Either::Right(bytes) => bytes,
740 };
741 #[cfg(not(feature = "mmap"))]
743 let data = fs::read(&path)?;
744 #[cfg(not(feature = "mmap"))]
745 let mut reader: &[u8] = &data;
746 let uncompressed_length = reader
747 .read_u32::<BE>()
748 .context("Failed to read uncompressed length from blob file")?;
749 let expected_checksum = reader.read_u32::<BE>()?;
750
751 let actual_checksum = checksum_block(reader);
753 if actual_checksum != expected_checksum {
754 bail!(
755 "Cache corruption detected: checksum mismatch in blob file {:08}.blob (expected \
756 {:08x}, got {:08x})",
757 seq,
758 expected_checksum,
759 actual_checksum
760 );
761 }
762
763 let buffer = decompress_into_arc(compression, uncompressed_length, reader)?;
764 Ok(ArcBytes::from(buffer))
765 }
766
767 pub fn is_empty(&self) -> bool {
769 self.is_empty.load(Ordering::Relaxed)
770 }
771
772 pub fn has_unrecoverable_write_error(&self) -> bool {
775 matches!(
776 *self.active_write_operation.lock(),
777 Some(ActiveWriteState::Error)
778 )
779 }
780
781 fn acquire_write_operation(&self, name: &'static str) -> Result<WriteOperationGuard<'_>> {
785 if self.read_only {
786 bail!("Cannot perform write operations on a read-only database");
787 }
788 let mut slot = self.active_write_operation.lock();
789 match &*slot {
790 Some(ActiveWriteState::Active(active_name)) => {
791 bail!(
792 "Another {active_name} is already active (only a single write operation is \
793 allowed at a time)"
794 );
795 }
796 Some(ActiveWriteState::Error) => {
797 bail!(
798 "A previous write operation failed with an unrecoverable error; no further \
799 writes are possible"
800 );
801 }
802 None => {}
803 }
804 *slot = Some(ActiveWriteState::Active(name));
805 drop(slot); let seq_before = self.inner.read().current_sequence_number;
807 Ok(WriteOperationGuard {
808 active: &self.active_write_operation,
809 path: &self.path,
810 seq_before,
811 succeeded: false,
812 })
813 }
814
815 pub fn write_batch<K: StoreKey + Send + Sync>(&self) -> Result<WriteBatch<'_, K, S, FAMILIES>> {
820 let guard = self.acquire_write_operation("write batch")?;
821 let current = guard.seq_before;
823 let shard_bits = shard_bits(&self.config, &self.inner.read().meta_files_by_family);
824 Ok(WriteBatch::new(
825 guard,
826 self.path.clone(),
827 current,
828 self.parallel_scheduler.clone(),
829 self.config.family_configs,
830 shard_bits,
831 ))
832 }
833
834 fn key_block_cache(&self) -> &BlockCache {
835 self.key_block_cache.get_or_init(|| {
836 BlockCache::with(
837 KEY_BLOCK_CACHE_SIZE as usize / KEY_BLOCK_AVG_SIZE,
838 KEY_BLOCK_CACHE_SIZE,
839 Default::default(),
840 Default::default(),
841 Default::default(),
842 )
843 })
844 }
845
846 fn value_block_cache(&self) -> &BlockCache {
847 self.value_block_cache.get_or_init(|| {
848 BlockCache::with(
849 VALUE_BLOCK_CACHE_SIZE as usize / VALUE_BLOCK_AVG_SIZE,
850 VALUE_BLOCK_CACHE_SIZE,
851 Default::default(),
852 Default::default(),
853 Default::default(),
854 )
855 })
856 }
857
858 pub fn clear_cache(&self) {
860 self.clear_block_caches();
861 for meta in self.inner.write().meta_files_by_family.iter_mut().flatten() {
862 meta.clear_cache();
863 }
864 }
865
866 pub fn clear_block_caches(&self) {
869 if let Some(cache) = self.key_block_cache.get() {
870 cache.clear();
871 }
872 if let Some(cache) = self.value_block_cache.get() {
873 cache.clear();
874 }
875 }
876
877 pub fn prepare_all_sst_caches(&self) {
880 for meta in self.inner.write().meta_files_by_family.iter_mut().flatten() {
881 meta.prepare_sst_cache();
882 }
883 }
884
885 fn open_log(&self) -> Result<BufWriter<File>> {
886 if self.read_only {
887 unreachable!("Only write operations can open the log file");
888 }
889 let log_path = self.path.join("LOG");
890 let log_file = OpenOptions::new()
891 .create(true)
892 .append(true)
893 .open(log_path)?;
894 Ok(BufWriter::new(log_file))
895 }
896
897 pub fn commit_write_batch<K: StoreKey + Send + Sync>(
900 &self,
901 mut write_batch: WriteBatch<'_, K, S, FAMILIES>,
902 ) -> Result<CommitStats> {
903 if self.read_only {
904 unreachable!("It's not possible to create a write batch for a read-only database");
905 }
906 let FinishResult {
907 sequence_number,
908 new_meta_files,
909 new_sst_files,
910 new_blob_files,
911 keys_written,
912 } = write_batch.finish(|family| {
913 let inner = self.inner.read();
916 let set = &inner.accessed_key_hashes[family as usize];
917 let initial_capacity = set.len() * 20 / 19;
920 let mut amqf = qfilter::Filter::with_fingerprint_size(
921 initial_capacity as u64,
922 USED_KEYS_FINGERPRINT_BITS,
923 )
924 .unwrap();
925 set.retain(|hash| {
928 amqf.insert_fingerprint(false, *hash)
932 .expect("Failed to insert fingerprint");
933 false
934 });
935 amqf
936 })?;
937 let stats = self.commit(CommitOptions {
938 new_meta_files,
939 new_sst_files,
940 new_blob_files,
941 sst_files_to_delete: vec![],
942 blob_seq_numbers_to_delete: vec![],
943 sequence_number,
944 keys_written,
945 })?;
946 write_batch.mark_succeeded();
948 Ok(stats)
949 }
950
951 fn commit(
954 &self,
955 CommitOptions {
956 mut new_meta_files,
957 new_sst_files,
958 new_blob_files,
959 sst_files_to_delete,
960 mut blob_seq_numbers_to_delete,
961 sequence_number: mut seq,
962 keys_written,
963 }: CommitOptions,
964 ) -> Result<CommitStats, anyhow::Error> {
965 let time = Timestamp::now();
966
967 new_meta_files.sort_unstable_by_key(|f| f.seq);
968
969 let mut stats = CommitStats::default();
970
971 let sync_span = tracing::trace_span!("sync new files").entered();
972
973 enum SyncItem {
974 Meta(u32, File),
975 Sst(File),
976 Blob(u32, File),
977 }
978 enum SyncResult {
979 Meta(MetaFile),
980 Sst,
981 Blob(u32, File),
982 }
983
984 let mut sync_items: Vec<SyncItem> =
985 Vec::with_capacity(new_meta_files.len() + new_sst_files.len() + new_blob_files.len());
986 for NewFile { seq, file, size } in new_meta_files {
987 stats.bytes_written += size;
988 sync_items.push(SyncItem::Meta(seq, file));
989 }
990 for NewFile { file, size, .. } in new_sst_files {
991 stats.bytes_written += size;
992 sync_items.push(SyncItem::Sst(file));
993 }
994 for NewFile { seq, file, size } in new_blob_files {
995 stats.bytes_written += size;
996 sync_items.push(SyncItem::Blob(seq, file));
997 }
998
999 let results: Vec<SyncResult> = self
1000 .parallel_scheduler
1001 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(sync_items, |item| match item {
1002 SyncItem::Meta(seq, file) => {
1003 file.sync_data()?;
1004 let meta_file = MetaFile::open(
1005 &self.path,
1006 seq,
1007 Some(&self.config.family_configs),
1008 self.config.access_mode,
1009 )?;
1010 Ok(SyncResult::Meta(meta_file))
1011 }
1012 SyncItem::Sst(file) => {
1013 file.sync_data()?;
1014 Ok(SyncResult::Sst)
1015 }
1016 SyncItem::Blob(seq, file) => {
1017 file.sync_data()?;
1018 Ok(SyncResult::Blob(seq, file))
1019 }
1020 })?;
1021
1022 let mut new_meta_files: Vec<MetaFile> = Vec::new();
1023 let mut new_blob_files: Vec<(u32, File)> = Vec::new();
1024 for result in results {
1025 match result {
1026 SyncResult::Meta(mf) => new_meta_files.push(mf),
1027 SyncResult::Sst => {}
1028 SyncResult::Blob(seq, file) => new_blob_files.push((seq, file)),
1029 }
1030 }
1031
1032 let mut sst_filter = SstFilter::new();
1033 for meta_file in new_meta_files.iter_mut().rev() {
1034 sst_filter.apply_filter(meta_file);
1035 }
1036
1037 drop(sync_span);
1042
1043 let new_meta_info = new_meta_files
1044 .iter()
1045 .map(|meta| {
1046 let ssts = meta
1047 .entries()
1048 .iter()
1049 .zip(meta.hash_ranges())
1050 .map(|(entry, range)| {
1051 let seq = entry.sequence_number();
1052 let size = entry.size();
1053 let flags = entry.flags();
1054 let counts = (entry.entry_count(), entry.tombstone_count());
1055 (seq, range.min_hash, range.max_hash, size, flags, counts)
1056 })
1057 .collect::<Vec<_>>();
1058 (
1059 meta.sequence_number(),
1060 meta.family(),
1061 ssts,
1062 meta.obsolete_sst_files().to_vec(),
1063 )
1064 })
1065 .collect::<Vec<_>>();
1066
1067 let has_delete_file;
1076 let mut meta_seq_numbers_to_delete = [(); FAMILIES].map(|_| Vec::new());
1077 let mut entries_to_remove = [(); FAMILIES].map(|_| Vec::new());
1078 stats.bytes_deleted += sst_files_to_delete.iter().map(|f| f.size).sum::<u64>();
1081 let mut sst_seq_numbers_to_delete = sst_files_to_delete
1083 .iter()
1084 .map(|f| f.seq)
1085 .collect::<Vec<_>>();
1086
1087 {
1088 let inner = self.inner.read();
1089
1090 for (family, meta_files) in inner.meta_files_by_family.iter().enumerate() {
1094 entries_to_remove[family].extend(
1095 meta_files
1096 .iter()
1097 .rev()
1098 .map(|meta_file| sst_filter.apply_filter_collect(meta_file)),
1099 );
1100 }
1101
1102 for meta_file in new_meta_files.iter().rev() {
1109 let should_remove = sst_filter.apply_and_get_remove(meta_file);
1110 debug_assert!(
1111 !should_remove,
1112 "newly created meta file should never be a candidate for removal"
1113 );
1114 }
1115 for (family, meta_files) in inner.meta_files_by_family.iter().enumerate() {
1116 for i in (0..meta_files.len()).rev() {
1117 let to_remove = &entries_to_remove[family][meta_files.len() - 1 - i];
1119 if sst_filter.apply_and_get_remove_after_removing(&meta_files[i], to_remove) {
1120 meta_seq_numbers_to_delete[family].push(meta_files[i].sequence_number());
1121 stats.bytes_deleted += meta_files[i].byte_size();
1123 }
1124 }
1125 }
1126
1127 has_delete_file = !sst_files_to_delete.is_empty()
1131 || !blob_seq_numbers_to_delete.is_empty()
1132 || meta_seq_numbers_to_delete
1133 .iter()
1134 .any(|seqs| !seqs.is_empty());
1135 }
1136
1137 stats.bytes_deleted += blob_seq_numbers_to_delete
1143 .iter()
1144 .map(|seq| {
1145 fs::metadata(self.path.join(format!("{seq:08}.blob")))
1146 .map(|m| m.len())
1147 .unwrap_or(0)
1148 })
1149 .sum::<u64>();
1150
1151 if has_delete_file {
1152 seq += 1;
1153 }
1154
1155 self.parallel_scheduler.block_in_place(|| {
1156 if has_delete_file {
1157 sst_seq_numbers_to_delete.sort_unstable();
1158 for seqs in &mut meta_seq_numbers_to_delete {
1159 seqs.sort_unstable();
1160 }
1161 blob_seq_numbers_to_delete.sort_unstable();
1162 let mut buf = Vec::with_capacity(
1164 (sst_seq_numbers_to_delete.len()
1165 + meta_seq_numbers_to_delete
1166 .iter()
1167 .map(Vec::len)
1168 .sum::<usize>()
1169 + blob_seq_numbers_to_delete.len())
1170 * size_of::<u32>(),
1171 );
1172 for seq in sst_seq_numbers_to_delete.iter() {
1173 buf.write_u32::<BE>(*seq)?;
1174 }
1175 for seq in meta_seq_numbers_to_delete.iter().flatten() {
1176 buf.write_u32::<BE>(*seq)?;
1177 }
1178 for seq in blob_seq_numbers_to_delete.iter() {
1179 buf.write_u32::<BE>(*seq)?;
1180 }
1181 let del_path = self.path.join(format!("{seq:08}.del"));
1182 let mut file = File::create(&del_path)?;
1183 file.write_all(&buf)?;
1184 file.sync_data()?;
1185 }
1186
1187 commit_current(&self.path, seq).context("Committing CURRENT file failed")?;
1188
1189 if let Err(e) = (|| {
1207 let mut log = self.open_log()?;
1208 writeln!(log, "Time {time}")?;
1209 let span = time.until(Timestamp::now())?;
1210 writeln!(log, "Commit {seq:08} {keys_written} keys in {span:#}")?;
1211 writeln!(log, "FAM | META SEQ | SST SEQ | RANGE")?;
1212 for (meta_seq, family, ssts, obsolete) in new_meta_info {
1213 for (seq, min, max, size, flags, (entries, tombstones)) in ssts {
1214 writeln!(
1215 log,
1216 "{family:3} | {meta_seq:08} | {seq:08} SST | {} ({} MiB, {}, \
1217 {size} bytes, {entries} entries, {tombstones} tombstones)",
1218 range_to_str(min, max),
1219 size / 1024 / 1024,
1220 flags
1221 )?;
1222 }
1223 for obsolete in obsolete.chunks(15) {
1224 write!(log, "{family:3} | {meta_seq:08} |")?;
1225 for seq in obsolete {
1226 write!(log, " {seq:08}")?;
1227 }
1228 writeln!(log, " OBSOLETE SST")?;
1229 }
1230 }
1231
1232 fn write_seq_numbers<W: std::io::Write, T>(
1233 log: &mut W,
1234 items: &[T],
1235 label: &str,
1236 extract_seq: fn(&T) -> u32,
1237 ) -> std::io::Result<()> {
1238 for chunk in items.chunks(15) {
1239 write!(log, " | |")?;
1240 for item in chunk {
1241 write!(log, " {:08}", extract_seq(item))?;
1242 }
1243 writeln!(log, " {}", label)?;
1244 }
1245 Ok(())
1246 }
1247
1248 new_blob_files.sort_unstable_by_key(|(seq, _)| *seq);
1249 write_seq_numbers(&mut log, &new_blob_files, "NEW BLOB", |&(seq, _)| seq)?;
1250 write_seq_numbers(
1251 &mut log,
1252 &blob_seq_numbers_to_delete,
1253 "BLOB DELETED",
1254 |&seq| seq,
1255 )?;
1256 write_seq_numbers(
1257 &mut log,
1258 &sst_seq_numbers_to_delete,
1259 "SST DELETED",
1260 |&seq| seq,
1261 )?;
1262 for seqs in &meta_seq_numbers_to_delete {
1263 write_seq_numbers(&mut log, seqs, "META DELETED", |&seq| seq)?;
1264 }
1265 anyhow::Ok(())
1266 })() {
1267 eprintln!("turbo-persistence: failed to write LOG after commit {seq:08}: {e:#}");
1268 }
1269
1270 anyhow::Ok(())
1271 })?;
1272
1273 {
1279 let mut inner = self.inner.write();
1280
1281 for (meta_files, family_removals) in
1283 inner.meta_files_by_family.iter_mut().zip(entries_to_remove)
1284 {
1285 for (meta_file, to_remove) in
1286 meta_files.iter_mut().zip(family_removals.into_iter().rev())
1287 {
1288 if !to_remove.is_empty() {
1289 meta_file.retain_entries(|seq| !to_remove.contains(&seq));
1290 }
1291 }
1292 }
1293
1294 for meta_file in new_meta_files.drain(..) {
1295 inner.push_meta_file(meta_file);
1296 }
1297 for (meta_files, seqs_to_delete) in inner
1298 .meta_files_by_family
1299 .iter_mut()
1300 .zip(&meta_seq_numbers_to_delete)
1301 {
1302 if !seqs_to_delete.is_empty() {
1303 let to_delete: HashSet<u32> = seqs_to_delete.iter().copied().collect();
1304 meta_files.retain(|meta| !to_delete.contains(&meta.sequence_number()));
1305 }
1306 }
1307 #[cfg(debug_assertions)]
1308 inner.debug_assert_meta_invariants();
1309 rebuild_shard_index(&self.config, &mut inner);
1310 inner.current_sequence_number = seq;
1311 self.is_empty.store(inner.is_empty(), Ordering::Relaxed);
1312 drop(inner);
1314 }
1315
1316 self.deferred_deletions.lock().extend(
1321 Self::try_delete_files(&self.path, &sst_seq_numbers_to_delete, "sst")
1322 .map(DeferredDeletion::Sst)
1323 .chain(
1324 meta_seq_numbers_to_delete
1325 .iter()
1326 .flat_map(|seqs| Self::try_delete_files(&self.path, seqs, "meta"))
1327 .map(DeferredDeletion::Meta),
1328 )
1329 .chain(
1330 Self::try_delete_files(&self.path, &blob_seq_numbers_to_delete, "blob")
1331 .map(DeferredDeletion::Blob),
1332 ),
1333 );
1334
1335 self.retry_deferred_deletions();
1337
1338 #[cfg(feature = "verbose_log")]
1340 {
1341 let _: Result<(), _> = (|| -> anyhow::Result<()> {
1342 let mut log = self.open_log()?;
1343 writeln!(log, "New database state:")?;
1344 writeln!(log, "FAM | META SEQ | SST SEQ FLAGS | RANGE")?;
1345 let inner = self.inner.read();
1346 for (family, meta_files) in inner.meta_files_by_family.iter().enumerate() {
1347 for meta in meta_files {
1348 let meta_seq = meta.sequence_number();
1349 for (entry, range) in meta.entries().iter().zip(meta.hash_ranges()) {
1350 let seq = entry.sequence_number();
1351 writeln!(
1352 log,
1353 "{family:3} | {meta_seq:08} | {seq:08} {:>6} | {}",
1354 entry.flags(),
1355 range_to_str(range.min_hash, range.max_hash)
1356 )?;
1357 }
1358 }
1359 }
1360 Ok(())
1361 })();
1362 }
1363
1364 Ok(stats)
1365 }
1366
1367 pub fn full_compact(&self) -> Result<()> {
1370 self.compact(&CompactConfig::full())?;
1371 Ok(())
1372 }
1373
1374 pub fn compact(&self, compact_config: &CompactConfig) -> Result<Option<CommitStats>> {
1382 if self.has_unrecoverable_write_error() {
1383 return Ok(None);
1385 }
1386 let mut guard = self.acquire_write_operation("compaction")?;
1387
1388 self.clear_cache();
1392
1393 let mut sequence_number;
1394 let mut new_meta_files = Vec::new();
1395 let mut new_sst_files = Vec::new();
1396 let mut sst_files_to_delete = Vec::new();
1397 let mut blob_seq_numbers_to_delete = Vec::new();
1398 let mut keys_written = 0;
1399
1400 {
1401 let inner = self.inner.read();
1402 sequence_number = AtomicU32::new(inner.current_sequence_number);
1403 self.compact_internal(
1404 &inner.meta_files_by_family,
1405 &sequence_number,
1406 &mut new_meta_files,
1407 &mut new_sst_files,
1408 &mut sst_files_to_delete,
1409 &mut blob_seq_numbers_to_delete,
1410 &mut keys_written,
1411 compact_config,
1412 )
1413 .context("Failed to compact database")?;
1414 }
1415
1416 let has_changes = !new_meta_files.is_empty();
1417 let stats = if has_changes {
1418 let stats = self
1419 .commit(CommitOptions {
1420 new_meta_files,
1421 new_sst_files,
1422 new_blob_files: Vec::new(),
1423 sst_files_to_delete,
1424 blob_seq_numbers_to_delete,
1425 sequence_number: *sequence_number.get_mut(),
1426 keys_written,
1427 })
1428 .context("Failed to commit the database compaction")?;
1429 Some(stats)
1430 } else {
1431 None
1432 };
1433
1434 guard.success();
1435 Ok(stats)
1436 }
1437
1438 fn compact_internal(
1440 &self,
1441 meta_files_by_family: &[Vec<MetaFile>; FAMILIES],
1442 sequence_number: &AtomicU32,
1443 new_meta_files: &mut Vec<NewFile>,
1444 new_sst_files: &mut Vec<NewFile>,
1445 sst_files_to_delete: &mut Vec<DeletedFile>,
1446 blob_seq_numbers_to_delete: &mut Vec<u32>,
1447 keys_written: &mut u64,
1448 compact_config: &CompactConfig,
1449 ) -> Result<()> {
1450 if meta_files_by_family.iter().all(Vec::is_empty) {
1451 return Ok(());
1452 }
1453
1454 struct SstWithRange {
1455 meta_index: usize,
1457 index_in_meta: u32,
1458 seq: u32,
1459 range: StaticSortedFileRange,
1460 size: u64,
1461 flags: MetaEntryFlags,
1462 entry_count: u32,
1463 tombstone_count: u32,
1464 }
1465
1466 impl Compactable for SstWithRange {
1467 fn range(&self) -> RangeInclusive<u64> {
1468 self.range.min_hash..=self.range.max_hash
1469 }
1470
1471 fn size(&self) -> u64 {
1472 self.size
1473 }
1474
1475 fn is_bottom(&self) -> bool {
1476 self.flags.bottom()
1477 }
1478
1479 fn is_fresh(&self) -> bool {
1480 self.flags.fresh()
1481 }
1482
1483 fn entry_count(&self) -> u64 {
1484 self.entry_count.into()
1485 }
1486
1487 fn tombstone_count(&self) -> u64 {
1488 self.tombstone_count.into()
1489 }
1490 }
1491
1492 let sst_by_family = meta_files_by_family
1493 .iter()
1494 .enumerate()
1495 .map(|(family, meta_files)| {
1496 meta_files
1497 .iter()
1498 .enumerate()
1499 .flat_map(|(meta_index, meta)| {
1500 debug_assert_eq!(
1501 meta.family() as usize,
1502 family,
1503 "meta file stored in the wrong family shard during compaction"
1504 );
1505 meta.entries()
1506 .iter()
1507 .enumerate()
1508 .map(move |(index_in_meta, entry)| SstWithRange {
1509 meta_index,
1510 index_in_meta: index_in_meta as u32,
1511 seq: entry.sequence_number(),
1512 range: meta.range(index_in_meta as u32),
1513 size: entry.size(),
1514 flags: entry.flags(),
1515 entry_count: entry.entry_count(),
1516 tombstone_count: entry.tombstone_count(),
1517 })
1518 })
1519 .collect::<Vec<_>>()
1520 })
1521 .collect::<Vec<_>>();
1522
1523 let path = &self.path;
1524
1525 let log_mutex = Mutex::new(());
1526
1527 struct PartialResultPerFamily {
1528 new_meta_file: Option<NewFile>,
1529 new_sst_files: Vec<NewFile>,
1530 sst_files_to_delete: Vec<DeletedFile>,
1531 blob_seq_numbers_to_delete: Vec<u32>,
1532 keys_written: u64,
1533 }
1534
1535 let shard_bits = shard_bits(&self.config, meta_files_by_family);
1536 let planned = plan_compaction(
1537 &sst_by_family
1538 .iter()
1539 .zip(shard_bits)
1540 .map(|(ssts, shard_bits)| (&ssts[..], shard_bits))
1541 .collect::<Vec<_>>(),
1542 compact_config,
1543 );
1544 let merge_jobs = sst_by_family
1545 .into_iter()
1546 .zip(planned)
1547 .zip(shard_bits)
1548 .enumerate()
1549 .map(|(family, ((ssts_with_ranges, merge_jobs), shard_bits))| {
1550 (family, ssts_with_ranges, merge_jobs, shard_bits)
1551 })
1552 .collect::<Vec<_>>();
1553
1554 let result = self
1555 .parallel_scheduler
1556 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(
1557 merge_jobs,
1558 |(family, ssts_with_ranges, merge_jobs, shard_bits)| {
1559 let meta_files = &meta_files_by_family[family];
1560 let family = family as u32;
1561 debug_assert!(
1562 meta_files.iter().all(|meta| meta.family() == family),
1563 "compaction received a meta file from the wrong family shard"
1564 );
1565
1566 if merge_jobs.is_empty() {
1567 return Ok(PartialResultPerFamily {
1568 new_meta_file: None,
1569 new_sst_files: Vec::new(),
1570 sst_files_to_delete: Vec::new(),
1571 blob_seq_numbers_to_delete: Vec::new(),
1572 keys_written: 0,
1573 });
1574 }
1575 let sst_strictly_in_shard = |index: usize| {
1576 let sst = &ssts_with_ranges[index];
1577 let range = meta_files[sst.meta_index].hash_range(sst.index_in_meta);
1578 shard_bits.shard_of(range.min_hash) == shard_bits.shard_of(range.max_hash)
1579 };
1580
1581 let used_key_hashes = if merge_jobs.iter().any(|job| {
1585 job.bottom
1586 && (job.members.len() > 1 || !sst_strictly_in_shard(job.members[0]))
1587 }) {
1588 union_used_key_hashes(meta_files)?
1589 } else {
1590 None
1591 };
1592
1593 let sst_files_to_delete = merge_jobs
1596 .iter()
1597 .filter(|job| job.members.len() > 1)
1598 .flat_map(|job| job.members.iter().copied())
1599 .map(|index| DeletedFile {
1600 seq: ssts_with_ranges[index].seq,
1601 size: ssts_with_ranges[index].size,
1602 })
1603 .collect::<Vec<_>>();
1604
1605 let span = tracing::trace_span!(
1607 "merge files",
1608 family = self.config.family_configs[family as usize].name
1609 );
1610 enum PartialMergeResult<'l> {
1611 Merged {
1612 new_sst_files: Vec<(u32, File, StaticSortedFileBuilderMeta<'static>)>,
1613 blob_seq_numbers_to_delete: Vec<u32>,
1614 keys_written: u64,
1615 indices: Vec<usize>,
1616 },
1617 Move {
1618 seq: u32,
1619 meta: StaticSortedFileBuilderMeta<'l>,
1620 },
1621 }
1622 let merge_result = self
1623 .parallel_scheduler
1624 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(merge_jobs, |job| {
1625 let _span = span.clone().entered();
1626 let MergeJob {
1627 members: indices,
1628 bottom,
1629 } = job;
1630 let output_flags = if bottom {
1631 MetaEntryFlags::COLD_BOTTOM
1632 } else {
1633 MetaEntryFlags::COMPACTED
1634 };
1635
1636 if indices.len() == 1 && sst_strictly_in_shard(indices[0]) {
1641 debug_assert!(bottom, "a single file merge must be a bottom merge");
1642 let index = indices[0];
1643 let meta_index = ssts_with_ranges[index].meta_index;
1644 let index_in_meta = ssts_with_ranges[index].index_in_meta;
1645 let meta_file = &meta_files[meta_index];
1646 let entry = meta_file.entry(index_in_meta);
1647 let amqf = Cow::Borrowed(entry.raw_amqf(meta_file.amqf_data()));
1648 let hash_range = meta_file.hash_range(index_in_meta);
1649 let meta = StaticSortedFileBuilderMeta {
1650 min_hash: hash_range.min_hash,
1651 max_hash: hash_range.max_hash,
1652 amqf,
1653 block_count: entry.block_count(),
1654 size: entry.size(),
1655 flags: output_flags,
1656 entries: entry.entry_count().into(),
1657 tombstones: entry.tombstone_count().into(),
1658 };
1659 return Ok(PartialMergeResult::Move {
1660 seq: entry.sequence_number(),
1661 meta,
1662 });
1663 }
1664
1665 let tombstone_is_dead = {
1670 let oldest_index_in_job = indices
1678 .iter()
1679 .copied()
1680 .min()
1681 .expect("merge jobs are not empty");
1682 let older_filters = ssts_with_ranges[..oldest_index_in_job]
1683 .iter()
1684 .map(|sst| {
1685 let meta_file = &meta_files[sst.meta_index];
1686 let entry = meta_file.entry(sst.index_in_meta);
1687 let range = meta_file.hash_range(sst.index_in_meta);
1688 (range.min_hash, range.max_hash, entry.amqf())
1689 })
1690 .collect::<Vec<_>>();
1691 move |hash: u64| {
1692 !older_filters.iter().any(|(min, max, amqf)| {
1693 hash >= *min
1694 && hash <= *max
1695 && amqf.contains_fingerprint(hash)
1696 })
1697 }
1698 };
1699 let iters = indices
1704 .iter()
1705 .map(|&index| {
1706 let meta_index = ssts_with_ranges[index].meta_index;
1707 let index_in_meta = ssts_with_ranges[index].index_in_meta;
1708 let meta_file = &meta_files[meta_index];
1709 let entry = meta_file.entry(index_in_meta);
1710 StaticSortedFileIter::open(
1711 path,
1712 entry.sst_metadata(),
1713 meta_file.compression(),
1714 )
1715 })
1716 .collect::<Result<Vec<_>>>()?;
1717
1718 let iter = MergeIter::new(iters.into_iter())?;
1719
1720 let mut blob_seq_numbers_to_delete: Vec<u32> = Vec::new();
1721
1722 struct Collector {
1723 writer: Option<(u32, StreamingSstWriter<LookupEntry>)>,
1728 flags: MetaEntryFlags,
1729 compression: Compression,
1730 new_sst_files:
1731 Vec<(u32, File, StaticSortedFileBuilderMeta<'static>)>,
1732 last_hash: Option<u64>,
1735 shard_bits: ShardBits,
1738 }
1739 impl Collector {
1740 fn new(
1741 flags: MetaEntryFlags,
1742 compression: Compression,
1743 shard_bits: ShardBits,
1744 ) -> Self {
1745 Self {
1746 writer: None,
1747 flags,
1748 compression,
1749 new_sst_files: Vec::new(),
1750 last_hash: None,
1751 shard_bits,
1752 }
1753 }
1754
1755 fn ensure_writer(
1757 &mut self,
1758 path: &Path,
1759 sequence_number: &AtomicU32,
1760 ) -> Result<&mut StreamingSstWriter<LookupEntry>>
1761 {
1762 if self.writer.is_none() {
1763 let seq =
1764 sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
1765 let sst_path = path.join(format!("{seq:08}.sst"));
1766 let writer = StreamingSstWriter::new(
1767 &sst_path,
1768 self.flags,
1769 MAX_ENTRIES_PER_COMPACTED_FILE as u64,
1770 self.compression,
1771 )?;
1772 self.writer = Some((seq, writer));
1773 }
1774 Ok(&mut self.writer.as_mut().unwrap().1)
1775 }
1776
1777 fn close_sst_file(&mut self, keys_written: &mut u64) -> Result<()> {
1781 if let Some((seq, writer)) = self.writer.take() {
1782 let _span =
1783 tracing::trace_span!("close merged sst file").entered();
1784 let (meta, file) = writer.close()?;
1785 *keys_written += meta.entries;
1786 self.new_sst_files.push((seq, file, meta));
1787 }
1788 Ok(())
1789 }
1790
1791 fn cancel(&mut self) {
1793 if let Some((_, writer)) = self.writer.take() {
1794 writer.cancel();
1795 }
1796 }
1797
1798 fn add_entry(
1802 &mut self,
1803 entry: LookupEntry,
1804 path: &Path,
1805 sequence_number: &AtomicU32,
1806 keys_written: &mut u64,
1807 ) -> Result<()> {
1808 let (key_changed, shard_changed) =
1809 if let Some(last) = self.last_hash {
1810 if last == entry.hash {
1811 (false, false)
1812 } else {
1813 (
1814 true,
1815 self.shard_bits.shard_of(last)
1816 != self.shard_bits.shard_of(entry.hash),
1817 )
1818 }
1819 } else {
1820 (false, false)
1821 };
1822
1823 if key_changed
1826 && let Some((_, ref writer)) = self.writer
1827 && (shard_changed
1828 || writer.is_full(
1829 MAX_ENTRIES_PER_COMPACTED_FILE,
1830 DATA_THRESHOLD_PER_COMPACTED_FILE,
1831 ))
1832 {
1833 self.close_sst_file(keys_written)?;
1834 }
1835 self.last_hash = Some(entry.hash);
1836 let writer = self.ensure_writer(path, sequence_number)?;
1837 if let Err(err) = writer.add(entry) {
1838 self.cancel();
1839 return Err(err);
1840 }
1841 Ok(())
1842 }
1843 }
1844 #[cfg(debug_assertions)]
1845 impl Drop for Collector {
1846 fn drop(&mut self) {
1847 if !std::thread::panicking() {
1848 assert!(
1849 self.writer.is_none(),
1850 "Collector dropped with an open writer"
1851 );
1852 }
1853 }
1854 }
1855 let compression =
1856 self.config.family_configs[family as usize].compression;
1857 let mut collector =
1858 Collector::new(output_flags, compression, shard_bits);
1859 let mut hot_collector =
1862 (bottom && used_key_hashes.is_some()).then(|| {
1863 Collector::new(
1864 MetaEntryFlags::HOT_BOTTOM,
1865 compression,
1866 shard_bits,
1867 )
1868 });
1869 let mut current_key: Option<RcBytes> = None;
1870 let mut keys_written = 0;
1871
1872 let mut skip_remaining_for_this_key = false;
1879 let mut deleted_values_for_this_key: AutoSet<
1882 RcBytes,
1883 BuildHasherDefault<FxHasher>,
1884 1,
1885 > = AutoSet::default();
1886 let family_config = &self.config.family_configs[family as usize];
1887
1888 let result: Result<_> = (|| {
1889 for entry in iter {
1890 let entry = entry?;
1891 if current_key.as_ref() != Some(&entry.key) {
1892 skip_remaining_for_this_key = false;
1894 deleted_values_for_this_key.clear();
1895 current_key = Some(entry.key.clone());
1896 }
1897 if let IterValue::KeyValueDeleted { value } = &entry.value {
1901 deleted_values_for_this_key.insert(value.clone());
1902 if tombstone_is_dead(entry.hash) {
1906 continue;
1907 }
1908 } else if !deleted_values_for_this_key.is_empty()
1909 && let IterValue::Slice { value } = &entry.value
1911 && deleted_values_for_this_key.contains(value)
1912 {
1913 continue;
1916 }
1917 if !skip_remaining_for_this_key {
1918 match family_config.kind {
1919 FamilyKind::MultiValue => {
1920 if matches!(entry.value, IterValue::KeyDeleted) {
1924 skip_remaining_for_this_key = true;
1925 }
1926 }
1927 FamilyKind::SingleValue => {
1928 skip_remaining_for_this_key = true;
1931 }
1932 }
1933 if matches!(entry.value, IterValue::KeyDeleted)
1936 && tombstone_is_dead(entry.hash)
1937 {
1938 continue;
1939 }
1940 let collector = if let Some(hot_collector) =
1941 &mut hot_collector
1942 && used_key_hashes
1943 .as_ref()
1944 .expect(
1945 "if we have a hot collector used key hashes \
1946 must be computed",
1947 )
1948 .contains_fingerprint(entry.hash)
1949 {
1950 hot_collector
1951 } else {
1952 &mut collector
1953 };
1954 collector.add_entry(
1955 entry,
1956 path,
1957 sequence_number,
1958 &mut keys_written,
1959 )?;
1960 } else {
1961 if let IterValue::Blob { sequence_number } = &entry.value {
1965 blob_seq_numbers_to_delete.push(*sequence_number);
1966 }
1967 }
1968 }
1969
1970 collector.close_sst_file(&mut keys_written)?;
1972 let mut new_sst_files = take(&mut collector.new_sst_files);
1973 if let Some(hot_collector) = &mut hot_collector {
1974 hot_collector.close_sst_file(&mut keys_written)?;
1975 new_sst_files.append(&mut hot_collector.new_sst_files);
1976 }
1977 Ok(PartialMergeResult::Merged {
1978 new_sst_files,
1979 blob_seq_numbers_to_delete,
1980 keys_written,
1981 indices,
1982 })
1983 })();
1984 if result.is_err() {
1985 collector.cancel();
1986 if let Some(hot_collector) = &mut hot_collector {
1987 hot_collector.cancel();
1988 }
1989 }
1990 result
1991 })
1992 .with_context(|| {
1993 format!("Failed to merge database files for family {family}")
1994 })?;
1995
1996 let Some((sst_files_len, blob_delete_len)) = merge_result
1997 .iter()
1998 .map(|r| {
1999 if let PartialMergeResult::Merged {
2000 new_sst_files,
2001 blob_seq_numbers_to_delete,
2002 indices: _,
2003 keys_written: _,
2004 } = r
2005 {
2006 (new_sst_files.len(), blob_seq_numbers_to_delete.len())
2007 } else {
2008 (0, 0)
2009 }
2010 })
2011 .reduce(|(a1, a2), (b1, b2)| (a1 + b1, a2 + b2))
2012 else {
2013 unreachable!()
2014 };
2015
2016 let mut new_sst_files = Vec::with_capacity(sst_files_len);
2017 let mut blob_seq_numbers_to_delete = Vec::with_capacity(blob_delete_len);
2018
2019 let meta_seq = sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
2020 let mut meta_file_builder = MetaFileBuilder::new(
2021 family,
2022 self.config.family_configs[family as usize].compression,
2023 shard_bits,
2024 );
2025
2026 let mut keys_written = 0;
2027 self.parallel_scheduler.block_in_place(|| {
2028 let guard = log_mutex.lock();
2029 let mut log = self.open_log()?;
2030 writeln!(log, "{family:3} | {meta_seq:08} | Compaction:",)?;
2031
2032 for result in merge_result {
2033 match result {
2034 PartialMergeResult::Merged {
2035 new_sst_files: merged_new_sst_files,
2036 blob_seq_numbers_to_delete: merged_blob_seq_numbers_to_delete,
2037 keys_written: merged_keys_written,
2038 indices,
2039 } => {
2040 writeln!(
2041 log,
2042 "{family:3} | {meta_seq:08} | MERGE \
2043 ({merged_keys_written} keys):"
2044 )?;
2045 for i in indices.iter() {
2046 let seq = ssts_with_ranges[*i].seq;
2047 let (min, max) = ssts_with_ranges[*i].range().into_inner();
2048 writeln!(
2049 log,
2050 "{family:3} | {meta_seq:08} | {seq:08} INPUT | {}",
2051 range_to_str(min, max)
2052 )?;
2053 }
2054 for (seq, file, meta) in merged_new_sst_files {
2055 let min = meta.min_hash;
2056 let max = meta.max_hash;
2057 writeln!(
2058 log,
2059 "{family:3} | {meta_seq:08} | {seq:08} OUTPUT | {} \
2060 ({})",
2061 range_to_str(min, max),
2062 meta.flags
2063 )?;
2064
2065 let size = meta.size;
2066 meta_file_builder.add(seq, meta);
2067 new_sst_files.push(NewFile { seq, file, size });
2068 }
2069 blob_seq_numbers_to_delete
2070 .extend(merged_blob_seq_numbers_to_delete);
2071 keys_written += merged_keys_written;
2072 }
2073 PartialMergeResult::Move { seq, meta } => {
2074 let min = meta.min_hash;
2075 let max = meta.max_hash;
2076 writeln!(
2077 log,
2078 "{family:3} | {meta_seq:08} | {seq:08} MOVED | {}",
2079 range_to_str(min, max)
2080 )?;
2081
2082 meta_file_builder.add(seq, meta);
2083 }
2084 }
2085 }
2086 drop(log);
2087 drop(guard);
2088
2089 anyhow::Ok(())
2090 })?;
2091
2092 for f in sst_files_to_delete.iter() {
2093 meta_file_builder.add_obsolete_sst_file(f.seq);
2094 }
2095
2096 let new_meta_file = {
2097 let _span = tracing::trace_span!("write meta file").entered();
2098 let (file, size) = self
2099 .parallel_scheduler
2100 .block_in_place(|| meta_file_builder.write(&self.path, meta_seq))?;
2101 NewFile {
2102 seq: meta_seq,
2103 file,
2104 size,
2105 }
2106 };
2107
2108 Ok(PartialResultPerFamily {
2109 new_meta_file: Some(new_meta_file),
2110 new_sst_files,
2111 sst_files_to_delete,
2112 blob_seq_numbers_to_delete,
2113 keys_written,
2114 })
2115 },
2116 )?;
2117
2118 for PartialResultPerFamily {
2119 new_meta_file: inner_new_meta_file,
2120 new_sst_files: mut inner_new_sst_files,
2121 sst_files_to_delete: mut inner_sst_files_to_delete,
2122 blob_seq_numbers_to_delete: mut inner_blob_seq_numbers_to_delete,
2123 keys_written: inner_keys_written,
2124 } in result
2125 {
2126 new_meta_files.extend(inner_new_meta_file);
2127 new_sst_files.append(&mut inner_new_sst_files);
2128 sst_files_to_delete.append(&mut inner_sst_files_to_delete);
2129 blob_seq_numbers_to_delete.append(&mut inner_blob_seq_numbers_to_delete);
2130 *keys_written += inner_keys_written;
2131 }
2132
2133 Ok(())
2134 }
2135
2136 pub fn get<K: QueryKey>(&self, family: usize, key: &K) -> Result<Option<ArcBytes>> {
2139 debug_assert!(family < FAMILIES, "Family index out of bounds");
2140 if self.config.family_configs[family].kind != FamilyKind::SingleValue {
2141 panic!(
2143 "only single valued tables can be queried with `get', call `get_multiple` instead"
2144 )
2145 }
2146 let span = tracing::trace_span!(
2147 "database read",
2148 name = self.config.family_configs[family].name,
2149 result_size = tracing::field::Empty
2150 )
2151 .entered();
2152 let results = self.get_impl::<K, false>(family, key, &span)?;
2153 debug_assert!(results.len() <= 1, "get() should return at most one result");
2154 Ok(results.into_iter().next())
2155 }
2156
2157 pub fn get_multiple<K: QueryKey>(
2167 &self,
2168 family: usize,
2169 key: &K,
2170 ) -> Result<SmallVec<[ArcBytes; 1]>> {
2171 debug_assert!(family < FAMILIES, "Family index out of bounds");
2172 if self.config.family_configs[family].kind != FamilyKind::MultiValue {
2173 panic!("only multi-valued tables can be queried with `get_multiple`")
2175 }
2176 let span = tracing::trace_span!(
2177 "database read multiple",
2178 name = self.config.family_configs[family].name,
2179 result_count = tracing::field::Empty,
2180 result_size = tracing::field::Empty
2181 )
2182 .entered();
2183 let results = self.get_impl::<K, true>(family, key, &span)?;
2184 Ok(results)
2185 }
2186
2187 fn get_impl<K: QueryKey, const FIND_ALL: bool>(
2192 &self,
2193 family: usize,
2194 key: &K,
2195 span: &EnteredSpan,
2196 ) -> Result<SmallVec<[ArcBytes; 1]>> {
2197 let hash = hash_key(key);
2198 let inner = self.inner.read();
2199 let mut output: SmallVec<[ArcBytes; 1]> = SmallVec::new();
2200 #[cfg(feature = "stats")]
2203 let mut found_in_sst = false;
2204
2205 let mut deleted_values: AutoSet<ArcBytes, BuildHasherDefault<FxHasher>, 1> =
2209 AutoSet::default();
2210
2211 let mut size = 0;
2212
2213 let key_block_cache = self.key_block_cache();
2214 let value_block_cache = self.value_block_cache();
2215 debug_assert!(
2216 inner.meta_files_by_family[family]
2217 .iter()
2218 .all(|meta| meta.family() as usize == family),
2219 "meta file stored in the wrong family shard while querying family {family}"
2220 );
2221 let meta_files = &inner.meta_files_by_family[family];
2223 let shard_files = inner.shard_indices[family].candidates(hash);
2224 for (
2225 range,
2226 &MetaEntryIndex {
2227 meta_index,
2228 entry_index,
2229 },
2230 ) in shard_files.ranges.iter().zip(&shard_files.locations)
2231 {
2232 let result = if range.contains(hash) {
2233 meta_files[meta_index as usize].lookup_entry::<K, FIND_ALL>(
2234 entry_index,
2235 hash,
2236 key,
2237 key_block_cache,
2238 value_block_cache,
2239 )?
2240 } else {
2241 MetaLookupResult::RangeMiss
2242 };
2243 match result {
2244 MetaLookupResult::RangeMiss => {
2245 #[cfg(feature = "stats")]
2246 self.stats.miss_range.fetch_add(1, Ordering::Relaxed);
2247 }
2248 MetaLookupResult::QuickFilterMiss => {
2249 #[cfg(feature = "stats")]
2250 self.stats.miss_amqf.fetch_add(1, Ordering::Relaxed);
2251 }
2252 MetaLookupResult::SstLookup(result) => match result {
2253 SstLookupResult::Found(values) => {
2254 #[cfg(feature = "stats")]
2255 {
2256 found_in_sst = true;
2257 }
2258 inner.accessed_key_hashes[family].insert(hash);
2259 for value in values {
2260 match value {
2261 LookupValue::KeyDeleted => {
2262 #[cfg(feature = "stats")]
2263 self.stats.hits_deleted.fetch_add(1, Ordering::Relaxed);
2264 if !FIND_ALL {
2265 span.record("result_size", "deleted");
2266 return Ok(SmallVec::new());
2267 }
2268 if output.is_empty() {
2272 span.record("result_size", "deleted");
2273 } else {
2274 span.record("result_size", size);
2275 }
2276 return Ok(output);
2277 }
2278 LookupValue::KeyValueDeleted { value } => {
2279 #[cfg(feature = "stats")]
2280 self.stats.hits_deleted.fetch_add(1, Ordering::Relaxed);
2281 deleted_values.insert(value);
2284 }
2285 LookupValue::Slice { value } => {
2286 #[cfg(feature = "stats")]
2287 self.stats.hits_small.fetch_add(1, Ordering::Relaxed);
2288 if deleted_values.contains(&value) {
2289 continue;
2290 }
2291 if !FIND_ALL {
2292 span.record("result_size", value.len());
2293 return Ok(SmallVec::from_buf([value]));
2294 }
2295 size += value.len();
2296 output.push(value);
2297 }
2298 LookupValue::Blob { sequence_number } => {
2299 #[cfg(feature = "stats")]
2300 self.stats.hits_blob.fetch_add(1, Ordering::Relaxed);
2301 let blob = self.read_blob(
2302 sequence_number,
2303 self.config.family_configs[family].compression,
2304 )?;
2305 if deleted_values.iter().any(|d| **d == *blob) {
2306 continue;
2307 }
2308 if !FIND_ALL {
2309 span.record("result_size", blob.len());
2310 return Ok(SmallVec::from_buf([blob]));
2311 }
2312 size += blob.len();
2313 output.push(blob);
2314 }
2315 }
2316 }
2317 }
2318 SstLookupResult::NotFound => {
2319 #[cfg(feature = "stats")]
2320 self.stats.miss_key.fetch_add(1, Ordering::Relaxed);
2321 }
2322 },
2323 }
2324 }
2325
2326 #[cfg(feature = "stats")]
2327 if !found_in_sst {
2328 self.stats.miss_global.fetch_add(1, Ordering::Relaxed);
2329 }
2330
2331 if FIND_ALL {
2332 span.record("result_count", output.len());
2333 }
2334 if output.is_empty() {
2335 span.record("result_size", "not_found");
2336 } else {
2337 span.record("result_size", size);
2338 }
2339 Ok(output)
2340 }
2341
2342 pub fn batch_get<K: QueryKey>(
2343 &self,
2344 family: usize,
2345 keys: &[K],
2346 ) -> Result<Vec<Option<ArcBytes>>> {
2347 debug_assert!(family < FAMILIES, "Family index out of bounds");
2348 if self.config.family_configs[family].kind != FamilyKind::SingleValue {
2349 panic!("only single valued tables can be queried with `batch_get'")
2351 }
2352 let span = tracing::trace_span!(
2353 "database batch read",
2354 name = self.config.family_configs[family].name,
2355 keys = keys.len(),
2356 not_found = tracing::field::Empty,
2357 deleted = tracing::field::Empty,
2358 result_size = tracing::field::Empty
2359 )
2360 .entered();
2361 let mut cells: Vec<(u64, usize, Option<LookupValue>)> = Vec::with_capacity(keys.len());
2362 for (index, key) in keys.iter().enumerate() {
2363 let hash = hash_key(key);
2364 cells.push((hash, index, None));
2365 }
2366 cells.sort_by_key(|(hash, _, _)| *hash);
2367 let inner = self.inner.read();
2368 let key_block_cache = self.key_block_cache();
2369 let value_block_cache = self.value_block_cache();
2370 debug_assert!(
2371 inner.meta_files_by_family[family]
2372 .iter()
2373 .all(|meta| meta.family() as usize == family),
2374 "meta file stored in the wrong family shard while querying family {family}"
2375 );
2376 let shard_index = &inner.shard_indices[family];
2379 let mut run_start = 0;
2380 while run_start < cells.len() {
2381 let shard = shard_index.bits().shard_of(cells[run_start].0);
2382 let run_len = cells[run_start..]
2383 .partition_point(|(hash, _, _)| shard_index.bits().shard_of(*hash) == shard);
2384 let run = &mut cells[run_start..run_start + run_len];
2385 run_start += run_len;
2386 let mut empty_run_cells = run_len;
2387 let shard_files = shard_index.shard(shard);
2388 for (
2389 range,
2390 &MetaEntryIndex {
2391 meta_index,
2392 entry_index,
2393 },
2394 ) in shard_files.ranges.iter().zip(&shard_files.locations)
2395 {
2396 let _result = inner.meta_files_by_family[family][meta_index as usize]
2397 .batch_lookup_entry(
2398 entry_index,
2399 range,
2400 keys,
2401 run,
2402 &mut empty_run_cells,
2403 key_block_cache,
2404 value_block_cache,
2405 )?;
2406 #[cfg(feature = "stats")]
2407 self.record_batch_lookup_stats(_result);
2408 if empty_run_cells == 0 {
2409 break;
2410 }
2411 }
2412 }
2413 let mut deleted = 0;
2414 let mut not_found = 0;
2415 let mut result_size = 0;
2416 let mut results = vec![None; keys.len()];
2417 for (hash, index, result) in cells {
2418 if let Some(result) = result {
2419 inner.accessed_key_hashes[family].insert(hash);
2420 let result = match result {
2421 LookupValue::KeyDeleted => {
2422 #[cfg(feature = "stats")]
2423 self.stats.hits_deleted.fetch_add(1, Ordering::Relaxed);
2424 deleted += 1;
2425 None
2426 }
2427 LookupValue::KeyValueDeleted { .. } => {
2428 bail!(
2431 "unexpected key-value tombstone in SingleValue family {}",
2432 self.config.family_configs[family].name
2433 )
2434 }
2435 LookupValue::Slice { value } => {
2436 #[cfg(feature = "stats")]
2437 self.stats.hits_small.fetch_add(1, Ordering::Relaxed);
2438 result_size += value.len();
2439 Some(value)
2440 }
2441 LookupValue::Blob { sequence_number } => {
2442 #[cfg(feature = "stats")]
2443 self.stats.hits_blob.fetch_add(1, Ordering::Relaxed);
2444 let blob = self.read_blob(
2445 sequence_number,
2446 self.config.family_configs[family].compression,
2447 )?;
2448 result_size += blob.len();
2449 Some(blob)
2450 }
2451 };
2452 results[index] = result;
2453 } else {
2454 #[cfg(feature = "stats")]
2455 self.stats.miss_global.fetch_add(1, Ordering::Relaxed);
2456 not_found += 1;
2457 }
2458 }
2459 span.record("not_found", not_found);
2460 span.record("deleted", deleted);
2461 span.record("result_size", result_size);
2462 Ok(results)
2463 }
2464
2465 #[cfg(feature = "stats")]
2466 fn record_batch_lookup_stats(&self, result: crate::meta_file::MetaBatchLookupResult) {
2467 let crate::meta_file::MetaBatchLookupResult {
2468 range_misses,
2469 quick_filter_misses,
2470 sst_misses,
2471 hits: _,
2472 } = result;
2473 if range_misses > 0 {
2474 self.stats
2475 .miss_range
2476 .fetch_add(range_misses as u64, Ordering::Relaxed);
2477 }
2478 if quick_filter_misses > 0 {
2479 self.stats
2480 .miss_amqf
2481 .fetch_add(quick_filter_misses as u64, Ordering::Relaxed);
2482 }
2483 if sst_misses > 0 {
2484 self.stats
2485 .miss_key
2486 .fetch_add(sst_misses as u64, Ordering::Relaxed);
2487 }
2488 }
2489
2490 #[cfg(feature = "stats")]
2492 pub fn statistics(&self) -> Statistics {
2493 let inner = self.inner.read();
2494 Statistics {
2495 meta_files: inner.meta_files_by_family.iter().map(Vec::len).sum(),
2496 sst_files: inner
2497 .meta_files_by_family
2498 .iter()
2499 .flatten()
2500 .map(|meta| meta.entries().len())
2501 .sum(),
2502 key_block_cache: CacheStatistics::new(self.key_block_cache()),
2503 value_block_cache: CacheStatistics::new(self.value_block_cache()),
2504 hits: self.stats.hits_deleted.load(Ordering::Relaxed)
2505 + self.stats.hits_small.load(Ordering::Relaxed)
2506 + self.stats.hits_blob.load(Ordering::Relaxed),
2507 misses: self.stats.miss_global.load(Ordering::Relaxed),
2508 miss_range: self.stats.miss_range.load(Ordering::Relaxed),
2509 miss_amqf: self.stats.miss_amqf.load(Ordering::Relaxed),
2510 miss_key: self.stats.miss_key.load(Ordering::Relaxed),
2511 }
2512 }
2513
2514 pub fn meta_info(&self) -> Result<Vec<MetaFileInfo>> {
2515 Ok(self
2516 .inner
2517 .read()
2518 .meta_files_by_family
2519 .iter()
2520 .flat_map(|meta_files| meta_files.iter().rev())
2521 .map(|meta_file| {
2522 let entries = meta_file
2523 .entries()
2524 .iter()
2525 .zip(meta_file.hash_ranges())
2526 .map(|(entry, range)| {
2527 let amqf = entry.raw_amqf(meta_file.amqf_data());
2528 MetaFileEntryInfo {
2529 sequence_number: entry.sequence_number(),
2530 min_hash: range.min_hash,
2531 max_hash: range.max_hash,
2532 sst_size: entry.size(),
2533 flags: entry.flags(),
2534 amqf_size: entry.amqf_size(),
2535 amqf_entries: amqf.len(),
2536 block_count: entry.block_count(),
2537 entry_count: entry.entry_count(),
2538 }
2539 })
2540 .collect();
2541 MetaFileInfo {
2542 sequence_number: meta_file.sequence_number(),
2543 shard_bits: meta_file.shard_bits().get(),
2544 family: meta_file.family(),
2545 obsolete_sst_files: meta_file.obsolete_sst_files().to_vec(),
2546 entries,
2547 }
2548 })
2549 .collect())
2550 }
2551
2552 pub fn shutdown(&self) {
2555 #[cfg(feature = "print_stats")]
2556 println!("{:#?}", self.statistics());
2557 self.retry_deferred_deletions();
2558 }
2559
2560 fn try_delete_files<'a>(
2563 dir: &'a Path,
2564 seqs: &'a [u32],
2565 ext: &'a str,
2566 ) -> impl Iterator<Item = u32> + 'a {
2567 seqs.iter()
2568 .copied()
2569 .filter(move |&seq| fs::remove_file(dir.join(format!("{seq:08}.{ext}"))).is_err())
2570 }
2571
2572 fn retry_deferred_deletions(&self) {
2577 let mut deferred = self.deferred_deletions.lock();
2578 deferred.retain(|entry| {
2579 let (seq, ext) = match *entry {
2580 DeferredDeletion::Sst(seq) => (seq, "sst"),
2581 DeferredDeletion::Meta(seq) => (seq, "meta"),
2582 DeferredDeletion::Blob(seq) => (seq, "blob"),
2583 };
2584 fs::remove_file(self.path.join(format!("{seq:08}.{ext}"))).is_err()
2586 });
2587 }
2588}
2589
2590const USED_KEYS_FINGERPRINT_BITS: u8 = 32;
2595
2596fn union_used_key_hashes(meta_files: &[MetaFile]) -> Result<Option<qfilter::Filter>> {
2598 let filters = meta_files
2599 .iter()
2600 .filter_map(|meta_file| meta_file.deserialize_used_key_hashes_amqf().transpose())
2601 .collect::<Result<Vec<_>>>()?
2602 .into_iter()
2603 .filter(|amqf| !amqf.is_empty())
2604 .collect::<Vec<_>>();
2605 Ok(match &filters[..] {
2606 [] => None,
2607 [single] => Some(single.to_owned()),
2608 _ => {
2609 let total_len = filters.iter().map(|f| f.len()).sum::<u64>();
2610 let mut merged =
2612 qfilter::Filter::with_fingerprint_size(total_len, USED_KEYS_FINGERPRINT_BITS)
2613 .context("Failed to create the merged used keys AMQF")?;
2614 for filter in &filters {
2615 merged
2616 .merge(false, filter)
2617 .context("Failed to merge used keys AMQFs")?;
2618 }
2619 merged.shrink_to_fit();
2620 Some(merged)
2621 }
2622 })
2623}
2624
2625fn rebuild_shard_index<const FAMILIES: usize>(
2627 config: &DbConfig<FAMILIES>,
2628 inner: &mut Inner<FAMILIES>,
2629) {
2630 let shard_bits = shard_bits(config, &inner.meta_files_by_family);
2631 for ((index, meta_files), shard_bits) in inner
2632 .shard_indices
2633 .iter_mut()
2634 .zip(&inner.meta_files_by_family)
2635 .zip(shard_bits)
2636 {
2637 *index = ShardIndex::build(shard_bits, meta_files.iter().map(MetaFile::hash_ranges));
2638 }
2639}
2640
2641fn shard_bits<const FAMILIES: usize>(
2647 config: &DbConfig<FAMILIES>,
2648 meta_files_by_family: &[Vec<MetaFile>; FAMILIES],
2649) -> [ShardBits; FAMILIES] {
2650 std::array::from_fn(|family| {
2651 let meta_files = &meta_files_by_family[family];
2652 let Some(newest) = meta_files.last() else {
2653 return config.family_configs[family].initial_shard_bits;
2654 };
2655 let current = newest.shard_bits();
2656 let mut bottom_bytes = 0u64;
2660 let mut all_bytes = 0u64;
2661 let mut covered_shards = FxHashSet::default();
2662 for (entry, range) in meta_files
2663 .iter()
2664 .flat_map(|meta| meta.entries().iter().zip(meta.hash_ranges()))
2665 {
2666 all_bytes = all_bytes.saturating_add(entry.size());
2667 if entry.flags().bottom() {
2668 bottom_bytes = bottom_bytes.saturating_add(entry.size());
2669 covered_shards
2670 .extend(current.shard_of(range.min_hash)..=current.shard_of(range.max_hash));
2671 }
2672 }
2673 if covered_shards.is_empty() {
2682 return current.maybe_grow(all_bytes, config.target_shard_size);
2683 }
2684 let estimated_bytes =
2685 bottom_bytes.saturating_mul(u64::from(current.count())) / covered_shards.len() as u64;
2686 current.maybe_reshard(estimated_bytes, config.target_shard_size)
2687 })
2688}
2689
2690fn range_to_str(min: u64, max: u64) -> String {
2691 use std::fmt::Write;
2692 const DISPLAY_SIZE: usize = 100;
2693 const TOTAL_SIZE: u64 = u64::MAX;
2694 let start_pos = (min as u128 * DISPLAY_SIZE as u128 / TOTAL_SIZE as u128) as usize;
2695 let end_pos = (max as u128 * DISPLAY_SIZE as u128 / TOTAL_SIZE as u128) as usize;
2696 let mut range_str = String::new();
2697 for i in 0..DISPLAY_SIZE {
2698 if i == start_pos && i == end_pos {
2699 range_str.push('O');
2700 } else if i == start_pos {
2701 range_str.push('[');
2702 } else if i == end_pos {
2703 range_str.push(']');
2704 } else if i > start_pos && i < end_pos {
2705 range_str.push('=');
2706 } else {
2707 range_str.push(' ');
2708 }
2709 }
2710 write!(range_str, " | {min:016x}-{max:016x}").unwrap();
2711 range_str
2712}
2713
2714pub struct MetaFileInfo {
2715 pub sequence_number: u32,
2716 pub shard_bits: u8,
2718 pub family: u32,
2719 pub obsolete_sst_files: Vec<u32>,
2720 pub entries: Vec<MetaFileEntryInfo>,
2721}
2722
2723pub struct MetaFileEntryInfo {
2724 pub sequence_number: u32,
2725 pub min_hash: u64,
2726 pub max_hash: u64,
2727 pub amqf_size: u32,
2728 pub amqf_entries: usize,
2729 pub sst_size: u64,
2730 pub flags: MetaEntryFlags,
2731 pub block_count: u16,
2732 pub entry_count: u32,
2734}