1use std::{
2 borrow::Cow,
3 collections::HashSet,
4 fmt::Display,
5 io::{BufWriter, Write},
6 mem::take,
7 ops::RangeInclusive,
8 path::{Path, PathBuf},
9 sync::{
10 OnceLock,
11 atomic::{AtomicBool, AtomicU32, Ordering},
12 },
13};
14
15use anyhow::{Context, Result, bail};
16use byteorder::{BE, ReadBytesExt, WriteBytesExt};
17use dashmap::DashSet;
18use fs_err::{self as fs, File, OpenOptions, ReadDir};
19use jiff::Timestamp;
20use memmap2::Mmap;
21use nohash_hasher::BuildNoHashHasher;
22use parking_lot::{Mutex, RwLock};
23use smallvec::SmallVec;
24use tracing::span::EnteredSpan;
25
26pub use crate::compaction::selector::CompactConfig;
27use crate::{
28 DbConfig, FamilyKind, QueryKey,
29 arc_bytes::ArcBytes,
30 compaction::selector::{Compactable, get_merge_segments},
31 compression::{checksum_block, decompress_into_arc},
32 constants::{
33 DATA_THRESHOLD_PER_COMPACTED_FILE, KEY_BLOCK_AVG_SIZE, KEY_BLOCK_CACHE_SIZE,
34 MAX_ENTRIES_PER_COMPACTED_FILE, VALUE_BLOCK_AVG_SIZE, VALUE_BLOCK_CACHE_SIZE,
35 },
36 key::{StoreKey, hash_key},
37 lookup_entry::{IterValue, LookupEntry, LookupValue},
38 merge_iter::MergeIter,
39 meta_file::{MetaEntryFlags, MetaFile, MetaLookupResult, StaticSortedFileRange},
40 meta_file_builder::MetaFileBuilder,
41 mmap_helper::advise_mmap_for_persistence,
42 parallel_scheduler::ParallelScheduler,
43 rc_bytes::RcBytes,
44 sst_filter::SstFilter,
45 static_sorted_file::{BlockCache, SstLookupResult, StaticSortedFileIter},
46 static_sorted_file_builder::{StaticSortedFileBuilderMeta, StreamingSstWriter},
47 write_batch::{FinishResult, NewFile, WriteBatch},
48};
49
50#[cfg(feature = "stats")]
51#[derive(Debug)]
52pub struct CacheStatistics {
53 pub hit_rate: f32,
54 pub fill: f32,
55 pub items: usize,
56 pub size: u64,
57 pub hits: u64,
58 pub misses: u64,
59}
60
61#[cfg(feature = "stats")]
62impl CacheStatistics {
63 fn new<Key, Val, We, B, L>(cache: &quick_cache::sync::Cache<Key, Val, We, B, L>) -> Self
64 where
65 Key: Eq + std::hash::Hash,
66 Val: Clone,
67 We: quick_cache::Weighter<Key, Val> + Clone,
68 B: std::hash::BuildHasher + Clone,
69 L: quick_cache::Lifecycle<Key, Val> + Clone,
70 {
71 let size = cache.weight();
72 let hits = cache.hits();
73 let misses = cache.misses();
74 Self {
75 hit_rate: hits as f32 / (hits + misses) as f32,
76 fill: size as f32 / cache.capacity() as f32,
77 items: cache.len(),
78 size,
79 hits,
80 misses,
81 }
82 }
83}
84
85#[cfg(feature = "stats")]
86#[derive(Debug)]
87pub struct Statistics {
88 pub meta_files: usize,
89 pub sst_files: usize,
90 pub key_block_cache: CacheStatistics,
91 pub value_block_cache: CacheStatistics,
92 pub hits: u64,
93 pub misses: u64,
94 pub miss_family: u64,
95 pub miss_range: u64,
96 pub miss_amqf: u64,
97 pub miss_key: u64,
98}
99
100#[cfg(feature = "stats")]
101#[derive(Default)]
102struct TrackedStats {
103 hits_deleted: std::sync::atomic::AtomicU64,
104 hits_small: std::sync::atomic::AtomicU64,
105 hits_blob: std::sync::atomic::AtomicU64,
106 miss_family: std::sync::atomic::AtomicU64,
107 miss_range: std::sync::atomic::AtomicU64,
108 miss_amqf: std::sync::atomic::AtomicU64,
109 miss_key: std::sync::atomic::AtomicU64,
110 miss_global: std::sync::atomic::AtomicU64,
111}
112
113enum ActiveWriteState {
115 Active(&'static str),
118 Error,
121}
122
123enum DeferredDeletion {
129 Sst(u32),
130 Meta(u32),
131 Blob(u32),
132}
133
134pub(crate) struct WriteOperationGuard<'a> {
141 active: &'a Mutex<Option<ActiveWriteState>>,
143 path: &'a Path,
145 seq_before: u32,
149 succeeded: bool,
151}
152
153impl WriteOperationGuard<'_> {
154 pub(crate) fn success(&mut self) {
158 self.succeeded = true;
159 }
160}
161
162fn commit_current(path: &Path, seq: u32) -> Result<()> {
172 let next_path = path.join("CURRENT.next");
173 let mut next_file = File::create(&next_path)?;
174 next_file.write_u32::<BE>(seq)?;
175 next_file.sync_data()?;
176 drop(next_file);
177
178 fs::rename(&next_path, path.join("CURRENT"))?;
179
180 #[cfg(not(windows))]
192 File::open(path)
193 .and_then(|dir| dir.sync_data())
194 .context("Failed to sync database directory after updating CURRENT")?;
195 Ok(())
196}
197
198fn delete_orphan_files(path: &Path, seq_before: u32) -> Result<()> {
203 commit_current(path, seq_before).context("Unable to restore CURRENT file")?;
206
207 for entry in fs::read_dir(path)? {
208 let entry = entry?;
209 let path = entry.path();
210 if let Some(ext) = path.extension().and_then(|s| s.to_str())
211 && let Some(seq) = path
212 .file_stem()
213 .and_then(|s| s.to_str())
214 .and_then(|s| s.parse::<u32>().ok())
215 && seq > seq_before
216 {
217 match ext {
218 "sst" | "meta" | "blob" | "del" => fs::remove_file(&path)?,
219 _ => {}
220 }
221 }
222 }
223 Ok(())
224}
225
226impl Drop for WriteOperationGuard<'_> {
227 fn drop(&mut self) {
228 if self.succeeded {
229 *self.active.lock() = None;
231 return;
232 }
233
234 match delete_orphan_files(self.path, self.seq_before) {
237 Ok(()) => *self.active.lock() = None,
238 Err(_) => *self.active.lock() = Some(ActiveWriteState::Error),
239 }
240 }
241}
242
243pub struct TurboPersistence<S: ParallelScheduler, const FAMILIES: usize> {
246 parallel_scheduler: S,
247 path: PathBuf,
249 read_only: bool,
252 inner: RwLock<Inner<FAMILIES>>,
254 is_empty: AtomicBool,
257 active_write_operation: Mutex<Option<ActiveWriteState>>,
260 deferred_deletions: Mutex<Vec<DeferredDeletion>>,
264 key_block_cache: OnceLock<BlockCache>,
268 value_block_cache: OnceLock<BlockCache>,
271 config: DbConfig<FAMILIES>,
273 #[cfg(feature = "stats")]
275 stats: TrackedStats,
276}
277
278struct Inner<const FAMILIES: usize> {
280 meta_files: Vec<MetaFile>,
282 current_sequence_number: u32,
284 accessed_key_hashes: [DashSet<u64, BuildNoHashHasher<u64>>; FAMILIES],
288}
289
290pub struct CommitOptions {
291 new_meta_files: Vec<NewFile>,
292 new_sst_files: Vec<NewFile>,
293 new_blob_files: Vec<NewFile>,
294 sst_files_to_delete: Vec<DeletedFile>,
295 blob_seq_numbers_to_delete: Vec<u32>,
296 sequence_number: u32,
297 keys_written: u64,
298}
299
300#[derive(Clone, Copy)]
303struct DeletedFile {
304 seq: u32,
305 size: u64,
307}
308
309#[derive(Clone, Copy, Debug, Default)]
312pub struct CommitStats {
313 pub bytes_written: u64,
315 pub bytes_deleted: u64,
317}
318
319impl Display for CommitStats {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 let CommitStats {
322 bytes_written,
323 bytes_deleted,
324 } = self;
325 write!(
326 f,
327 "bytes_written={bytes_written} bytes_deleted={bytes_deleted}"
328 )
329 }
330}
331
332struct OpenOpts<S: ParallelScheduler, const FAMILIES: usize> {
333 path: PathBuf,
334 read_only: bool,
335 parallel_scheduler: S,
336 config: DbConfig<FAMILIES>,
337}
338
339impl<S: ParallelScheduler + Default, const FAMILIES: usize> TurboPersistence<S, FAMILIES> {
340 pub fn open(path: PathBuf) -> Result<Self> {
345 Self::open_with_parallel_scheduler(path, Default::default())
346 }
347
348 pub fn open_with_config(path: PathBuf, config: DbConfig<FAMILIES>) -> Result<Self> {
350 Self::open_with_config_and_parallel_scheduler(path, config, Default::default())
351 }
352
353 pub fn open_read_only_with_config(path: PathBuf, config: DbConfig<FAMILIES>) -> Result<Self> {
356 Self::open_read_only_with_parallel_scheduler(path, config, Default::default())
357 }
358
359 pub fn empty_in_memory_with_config(config: DbConfig<FAMILIES>) -> Self {
363 Self::new(OpenOpts {
366 path: PathBuf::new(),
367 read_only: true,
368 parallel_scheduler: Default::default(),
369 config,
370 })
371 }
372}
373
374impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES> {
375 fn new(
376 OpenOpts {
377 path,
378 read_only,
379 parallel_scheduler,
380 config,
381 }: OpenOpts<S, FAMILIES>,
382 ) -> Self {
383 Self {
384 parallel_scheduler,
385 path,
386 read_only,
387 inner: RwLock::new(Inner {
388 meta_files: Vec::new(),
389 current_sequence_number: 0,
390 accessed_key_hashes: [(); FAMILIES]
391 .map(|_| DashSet::with_hasher(BuildNoHashHasher::default())),
392 }),
393 is_empty: AtomicBool::new(true),
394 active_write_operation: Mutex::new(None),
395 deferred_deletions: Mutex::new(Vec::new()),
396 key_block_cache: OnceLock::new(),
397 value_block_cache: OnceLock::new(),
398 config,
399 #[cfg(feature = "stats")]
400 stats: TrackedStats::default(),
401 }
402 }
403
404 pub fn open_with_parallel_scheduler(path: PathBuf, parallel_scheduler: S) -> Result<Self> {
409 Self::open_with_config_and_parallel_scheduler(path, DbConfig::default(), parallel_scheduler)
410 }
411
412 pub fn open_with_config_and_parallel_scheduler(
414 path: PathBuf,
415 config: DbConfig<FAMILIES>,
416 parallel_scheduler: S,
417 ) -> Result<Self> {
418 let mut db = Self::new(OpenOpts {
419 path,
420 read_only: false,
421 parallel_scheduler,
422 config,
423 });
424 db.open_directory(false)?;
425 Ok(db)
426 }
427
428 fn open_read_only_with_parallel_scheduler(
431 path: PathBuf,
432 config: DbConfig<FAMILIES>,
433 parallel_scheduler: S,
434 ) -> Result<Self> {
435 let mut db = Self::new(OpenOpts {
436 path,
437 read_only: true,
438 parallel_scheduler,
439 config,
440 });
441 db.open_directory(false)?;
442 Ok(db)
443 }
444
445 fn open_directory(&mut self, read_only: bool) -> Result<()> {
447 match fs::read_dir(&self.path) {
448 Ok(entries) => {
449 if !self
450 .load_directory(entries, read_only)
451 .context("Loading persistence directory failed")?
452 {
453 if read_only {
454 bail!("Failed to open database");
455 }
456 commit_current(&self.path, 0)
457 .context("Initializing persistence directory failed")?;
458 }
459 Ok(())
460 }
461 Err(e) => {
462 if !read_only && e.kind() == std::io::ErrorKind::NotFound {
463 self.create_and_init_directory()
464 .context("Creating and initializing persistence directory failed")?;
465 Ok(())
466 } else {
467 Err(e).context("Failed to open database")
468 }
469 }
470 }
471 }
472
473 fn create_and_init_directory(&mut self) -> Result<()> {
475 fs::create_dir_all(&self.path)?;
476 commit_current(&self.path, 0)
477 }
478
479 fn load_directory(&mut self, entries: ReadDir, read_only: bool) -> Result<bool> {
481 let mut meta_files = Vec::new();
482 let mut current_file = match File::open(self.path.join("CURRENT")) {
483 Ok(file) => file,
484 Err(e) => {
485 if !read_only && e.kind() == std::io::ErrorKind::NotFound {
486 return Ok(false);
487 } else {
488 return Err(e).context("Failed to open CURRENT file");
489 }
490 }
491 };
492 let current = current_file.read_u32::<BE>()?;
493 drop(current_file);
494
495 let mut deleted_files = HashSet::new();
496 for entry in entries {
497 let entry = entry?;
498 let path = entry.path();
499 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
500 if path.file_stem().and_then(|s| s.to_str()) == Some("CURRENT") {
504 if !read_only {
505 fs::remove_file(&path)?;
506 }
507 continue;
508 }
509 let seq: u32 = path
510 .file_stem()
511 .context("File has no file stem")?
512 .to_str()
513 .context("File stem is not valid utf-8")?
514 .parse()?;
515 if deleted_files.contains(&seq) {
516 continue;
517 }
518 if seq > current {
519 if !read_only {
520 fs::remove_file(&path)?;
521 }
522 } else {
523 match ext {
524 "meta" => {
525 meta_files.push(seq);
526 }
527 "del" => {
528 let mut content = &*fs::read(&path)?;
529 let mut no_existing_files = true;
530 while !content.is_empty() {
531 let seq = content.read_u32::<BE>()?;
532 deleted_files.insert(seq);
533 if !read_only {
534 let sst_file = self.path.join(format!("{seq:08}.sst"));
536 let meta_file = self.path.join(format!("{seq:08}.meta"));
537 let blob_file = self.path.join(format!("{seq:08}.blob"));
538 for path in [sst_file, meta_file, blob_file] {
539 if fs::exists(&path)? {
540 fs::remove_file(path)?;
541 no_existing_files = false;
542 }
543 }
544 }
545 }
546 if !read_only && no_existing_files {
547 fs::remove_file(&path)?;
548 }
549 }
550 "blob" | "sst" => {
551 }
553 _ => {
554 if !path
555 .file_name()
556 .is_some_and(|s| s.as_encoded_bytes().starts_with(b"."))
557 {
558 bail!("Unexpected file in persistence directory: {:?}", path);
559 }
560 }
561 }
562 }
563 } else {
564 match path.file_stem().and_then(|s| s.to_str()) {
565 Some("CURRENT") => {
566 }
568 Some("LOG") => {
569 }
571 _ => {
572 if !path
573 .file_name()
574 .is_some_and(|s| s.as_encoded_bytes().starts_with(b"."))
575 {
576 bail!("Unexpected file in persistence directory: {:?}", path);
577 }
578 }
579 }
580 }
581 }
582
583 meta_files.retain(|seq| !deleted_files.contains(seq));
584 meta_files.sort_unstable();
585 let mut meta_files = self
586 .parallel_scheduler
587 .parallel_map_collect::<_, _, Result<Vec<MetaFile>>>(&meta_files, |&seq| {
588 let meta_file = MetaFile::open(&self.path, seq)?;
589 Ok(meta_file)
590 })?;
591
592 let mut sst_filter = SstFilter::new();
593 for meta_file in meta_files.iter_mut().rev() {
594 sst_filter.apply_filter(meta_file);
595 }
596
597 let inner = self.inner.get_mut();
598 self.is_empty
599 .store(meta_files.is_empty(), Ordering::Relaxed);
600 inner.meta_files = meta_files;
601 inner.current_sequence_number = current;
602 Ok(true)
603 }
604
605 #[tracing::instrument(level = "info", name = "reading database blob", skip_all)]
607 fn read_blob(&self, seq: u32) -> Result<ArcBytes> {
608 let path = self.path.join(format!("{seq:08}.blob"));
609 let file = File::open(&path)?;
610 let mmap = unsafe { Mmap::map(file.file()) }.with_context(|| {
611 format!(
612 "Failed to mmap blob file {} ({} bytes)",
613 path.display(),
614 file.metadata().map(|m| m.len()).unwrap_or(0)
615 )
616 })?;
617 #[cfg(unix)]
618 mmap.advise(memmap2::Advice::Sequential)?;
619 #[cfg(unix)]
620 mmap.advise(memmap2::Advice::WillNeed)?;
621 advise_mmap_for_persistence(&mmap)?;
622 let mut reader = &mmap[..];
623 let uncompressed_length = reader
624 .read_u32::<BE>()
625 .context("Failed to read uncompressed length from blob file")?;
626 let expected_checksum = reader.read_u32::<BE>()?;
627
628 let actual_checksum = checksum_block(reader);
630 if actual_checksum != expected_checksum {
631 bail!(
632 "Cache corruption detected: checksum mismatch in blob file {:08}.blob (expected \
633 {:08x}, got {:08x})",
634 seq,
635 expected_checksum,
636 actual_checksum
637 );
638 }
639
640 let buffer = decompress_into_arc(uncompressed_length, reader)?;
641 Ok(ArcBytes::from(buffer))
642 }
643
644 pub fn is_empty(&self) -> bool {
646 self.is_empty.load(Ordering::Relaxed)
647 }
648
649 pub fn has_unrecoverable_write_error(&self) -> bool {
652 matches!(
653 *self.active_write_operation.lock(),
654 Some(ActiveWriteState::Error)
655 )
656 }
657
658 fn acquire_write_operation(&self, name: &'static str) -> Result<WriteOperationGuard<'_>> {
662 if self.read_only {
663 bail!("Cannot perform write operations on a read-only database");
664 }
665 let mut slot = self.active_write_operation.lock();
666 match &*slot {
667 Some(ActiveWriteState::Active(active_name)) => {
668 bail!(
669 "Another {active_name} is already active (only a single write operation is \
670 allowed at a time)"
671 );
672 }
673 Some(ActiveWriteState::Error) => {
674 bail!(
675 "A previous write operation failed with an unrecoverable error; no further \
676 writes are possible"
677 );
678 }
679 None => {}
680 }
681 *slot = Some(ActiveWriteState::Active(name));
682 drop(slot); let seq_before = self.inner.read().current_sequence_number;
684 Ok(WriteOperationGuard {
685 active: &self.active_write_operation,
686 path: &self.path,
687 seq_before,
688 succeeded: false,
689 })
690 }
691
692 pub fn write_batch<K: StoreKey + Send + Sync>(&self) -> Result<WriteBatch<'_, K, S, FAMILIES>> {
697 let guard = self.acquire_write_operation("write batch")?;
698 let current = guard.seq_before;
700 Ok(WriteBatch::new(
701 guard,
702 self.path.clone(),
703 current,
704 self.parallel_scheduler.clone(),
705 self.config.family_configs,
706 ))
707 }
708
709 fn key_block_cache(&self) -> &BlockCache {
710 self.key_block_cache.get_or_init(|| {
711 BlockCache::with(
712 KEY_BLOCK_CACHE_SIZE as usize / KEY_BLOCK_AVG_SIZE,
713 KEY_BLOCK_CACHE_SIZE,
714 Default::default(),
715 Default::default(),
716 Default::default(),
717 )
718 })
719 }
720
721 fn value_block_cache(&self) -> &BlockCache {
722 self.value_block_cache.get_or_init(|| {
723 BlockCache::with(
724 VALUE_BLOCK_CACHE_SIZE as usize / VALUE_BLOCK_AVG_SIZE,
725 VALUE_BLOCK_CACHE_SIZE,
726 Default::default(),
727 Default::default(),
728 Default::default(),
729 )
730 })
731 }
732
733 pub fn clear_cache(&self) {
735 self.clear_block_caches();
736 for meta in self.inner.write().meta_files.iter_mut() {
737 meta.clear_cache();
738 }
739 }
740
741 pub fn clear_block_caches(&self) {
744 if let Some(cache) = self.key_block_cache.get() {
745 cache.clear();
746 }
747 if let Some(cache) = self.value_block_cache.get() {
748 cache.clear();
749 }
750 }
751
752 pub fn prepare_all_sst_caches(&self) {
755 for meta in self.inner.write().meta_files.iter_mut() {
756 meta.prepare_sst_cache();
757 }
758 }
759
760 fn open_log(&self) -> Result<BufWriter<File>> {
761 if self.read_only {
762 unreachable!("Only write operations can open the log file");
763 }
764 let log_path = self.path.join("LOG");
765 let log_file = OpenOptions::new()
766 .create(true)
767 .append(true)
768 .open(log_path)?;
769 Ok(BufWriter::new(log_file))
770 }
771
772 pub fn commit_write_batch<K: StoreKey + Send + Sync>(
775 &self,
776 mut write_batch: WriteBatch<'_, K, S, FAMILIES>,
777 ) -> Result<CommitStats> {
778 if self.read_only {
779 unreachable!("It's not possible to create a write batch for a read-only database");
780 }
781 let FinishResult {
782 sequence_number,
783 new_meta_files,
784 new_sst_files,
785 new_blob_files,
786 keys_written,
787 } = write_batch.finish(|family| {
788 let inner = self.inner.read();
789 let set = &inner.accessed_key_hashes[family as usize];
790 let initial_capacity = set.len() * 20 / 19;
793 let mut amqf =
799 qfilter::Filter::with_fingerprint_size(initial_capacity as u64, u64::BITS as u8)
800 .unwrap();
801 set.retain(|hash| {
804 amqf.insert_fingerprint(false, *hash)
808 .expect("Failed to insert fingerprint");
809 false
810 });
811 amqf
812 })?;
813 let stats = self.commit(CommitOptions {
814 new_meta_files,
815 new_sst_files,
816 new_blob_files,
817 sst_files_to_delete: vec![],
818 blob_seq_numbers_to_delete: vec![],
819 sequence_number,
820 keys_written,
821 })?;
822 write_batch.mark_succeeded();
824 Ok(stats)
825 }
826
827 fn commit(
830 &self,
831 CommitOptions {
832 mut new_meta_files,
833 new_sst_files,
834 new_blob_files,
835 sst_files_to_delete,
836 mut blob_seq_numbers_to_delete,
837 sequence_number: mut seq,
838 keys_written,
839 }: CommitOptions,
840 ) -> Result<CommitStats, anyhow::Error> {
841 let time = Timestamp::now();
842
843 new_meta_files.sort_unstable_by_key(|f| f.seq);
844
845 let mut stats = CommitStats::default();
846
847 let sync_span = tracing::trace_span!("sync new files").entered();
848
849 enum SyncItem {
850 Meta(u32, File),
851 Sst(File),
852 Blob(u32, File),
853 }
854 enum SyncResult {
855 Meta(MetaFile),
856 Sst,
857 Blob(u32, File),
858 }
859
860 let mut sync_items: Vec<SyncItem> =
861 Vec::with_capacity(new_meta_files.len() + new_sst_files.len() + new_blob_files.len());
862 for NewFile { seq, file, size } in new_meta_files {
863 stats.bytes_written += size;
864 sync_items.push(SyncItem::Meta(seq, file));
865 }
866 for NewFile { file, size, .. } in new_sst_files {
867 stats.bytes_written += size;
868 sync_items.push(SyncItem::Sst(file));
869 }
870 for NewFile { seq, file, size } in new_blob_files {
871 stats.bytes_written += size;
872 sync_items.push(SyncItem::Blob(seq, file));
873 }
874
875 let results: Vec<SyncResult> = self
876 .parallel_scheduler
877 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(sync_items, |item| match item {
878 SyncItem::Meta(seq, file) => {
879 file.sync_data()?;
880 let meta_file = MetaFile::open(&self.path, seq)?;
881 Ok(SyncResult::Meta(meta_file))
882 }
883 SyncItem::Sst(file) => {
884 file.sync_data()?;
885 Ok(SyncResult::Sst)
886 }
887 SyncItem::Blob(seq, file) => {
888 file.sync_data()?;
889 Ok(SyncResult::Blob(seq, file))
890 }
891 })?;
892
893 let mut new_meta_files: Vec<MetaFile> = Vec::new();
894 let mut new_blob_files: Vec<(u32, File)> = Vec::new();
895 for result in results {
896 match result {
897 SyncResult::Meta(mf) => new_meta_files.push(mf),
898 SyncResult::Sst => {}
899 SyncResult::Blob(seq, file) => new_blob_files.push((seq, file)),
900 }
901 }
902
903 let mut sst_filter = SstFilter::new();
904 for meta_file in new_meta_files.iter_mut().rev() {
905 sst_filter.apply_filter(meta_file);
906 }
907
908 drop(sync_span);
913
914 let new_meta_info = new_meta_files
915 .iter()
916 .map(|meta| {
917 let ssts = meta
918 .entries()
919 .iter()
920 .map(|entry| {
921 let seq = entry.sequence_number();
922 let range = entry.range();
923 let size = entry.size();
924 let flags = entry.flags();
925 (seq, range.min_hash, range.max_hash, size, flags)
926 })
927 .collect::<Vec<_>>();
928 (
929 meta.sequence_number(),
930 meta.family(),
931 ssts,
932 meta.obsolete_sst_files().to_vec(),
933 )
934 })
935 .collect::<Vec<_>>();
936
937 let has_delete_file;
946 let mut meta_seq_numbers_to_delete = Vec::new();
947 let entries_to_remove;
948 stats.bytes_deleted += sst_files_to_delete.iter().map(|f| f.size).sum::<u64>();
951 let mut sst_seq_numbers_to_delete = sst_files_to_delete
953 .iter()
954 .map(|f| f.seq)
955 .collect::<Vec<_>>();
956
957 {
958 let inner = self.inner.read();
959
960 entries_to_remove = inner
966 .meta_files
967 .iter()
968 .rev()
969 .map(|meta_file| sst_filter.apply_filter_collect(meta_file))
970 .collect::<Vec<_>>();
971
972 for meta_file in new_meta_files.iter().rev() {
979 let should_remove = sst_filter.apply_and_get_remove(meta_file);
980 debug_assert!(
981 !should_remove,
982 "newly created meta file should never be a candidate for removal"
983 );
984 }
985 for i in (0..inner.meta_files.len()).rev() {
986 if sst_filter.apply_and_get_remove(&inner.meta_files[i]) {
987 meta_seq_numbers_to_delete.push(inner.meta_files[i].sequence_number());
988 stats.bytes_deleted += inner.meta_files[i].byte_size();
990 }
991 }
992
993 has_delete_file = !sst_files_to_delete.is_empty()
997 || !blob_seq_numbers_to_delete.is_empty()
998 || !meta_seq_numbers_to_delete.is_empty();
999 }
1000
1001 stats.bytes_deleted += blob_seq_numbers_to_delete
1007 .iter()
1008 .map(|seq| {
1009 fs::metadata(self.path.join(format!("{seq:08}.blob")))
1010 .map(|m| m.len())
1011 .unwrap_or(0)
1012 })
1013 .sum::<u64>();
1014
1015 if has_delete_file {
1016 seq += 1;
1017 }
1018
1019 self.parallel_scheduler.block_in_place(|| {
1020 if has_delete_file {
1021 sst_seq_numbers_to_delete.sort_unstable();
1022 meta_seq_numbers_to_delete.sort_unstable();
1023 blob_seq_numbers_to_delete.sort_unstable();
1024 let mut buf = Vec::with_capacity(
1026 (sst_seq_numbers_to_delete.len()
1027 + meta_seq_numbers_to_delete.len()
1028 + blob_seq_numbers_to_delete.len())
1029 * size_of::<u32>(),
1030 );
1031 for seq in sst_seq_numbers_to_delete.iter() {
1032 buf.write_u32::<BE>(*seq)?;
1033 }
1034 for seq in meta_seq_numbers_to_delete.iter() {
1035 buf.write_u32::<BE>(*seq)?;
1036 }
1037 for seq in blob_seq_numbers_to_delete.iter() {
1038 buf.write_u32::<BE>(*seq)?;
1039 }
1040 let del_path = self.path.join(format!("{seq:08}.del"));
1041 let mut file = File::create(&del_path)?;
1042 file.write_all(&buf)?;
1043 file.sync_data()?;
1044 }
1045
1046 commit_current(&self.path, seq).context("Committing CURRENT file failed")?;
1047
1048 if let Err(e) = (|| {
1066 let mut log = self.open_log()?;
1067 writeln!(log, "Time {time}")?;
1068 let span = time.until(Timestamp::now())?;
1069 writeln!(log, "Commit {seq:08} {keys_written} keys in {span:#}")?;
1070 writeln!(log, "FAM | META SEQ | SST SEQ | RANGE")?;
1071 for (meta_seq, family, ssts, obsolete) in new_meta_info {
1072 for (seq, min, max, size, flags) in ssts {
1073 writeln!(
1074 log,
1075 "{family:3} | {meta_seq:08} | {seq:08} SST | {} ({} MiB, {})",
1076 range_to_str(min, max),
1077 size / 1024 / 1024,
1078 flags
1079 )?;
1080 }
1081 for obsolete in obsolete.chunks(15) {
1082 write!(log, "{family:3} | {meta_seq:08} |")?;
1083 for seq in obsolete {
1084 write!(log, " {seq:08}")?;
1085 }
1086 writeln!(log, " OBSOLETE SST")?;
1087 }
1088 }
1089
1090 fn write_seq_numbers<W: std::io::Write, T>(
1091 log: &mut W,
1092 items: &[T],
1093 label: &str,
1094 extract_seq: fn(&T) -> u32,
1095 ) -> std::io::Result<()> {
1096 for chunk in items.chunks(15) {
1097 write!(log, " | |")?;
1098 for item in chunk {
1099 write!(log, " {:08}", extract_seq(item))?;
1100 }
1101 writeln!(log, " {}", label)?;
1102 }
1103 Ok(())
1104 }
1105
1106 new_blob_files.sort_unstable_by_key(|(seq, _)| *seq);
1107 write_seq_numbers(&mut log, &new_blob_files, "NEW BLOB", |&(seq, _)| seq)?;
1108 write_seq_numbers(
1109 &mut log,
1110 &blob_seq_numbers_to_delete,
1111 "BLOB DELETED",
1112 |&seq| seq,
1113 )?;
1114 write_seq_numbers(
1115 &mut log,
1116 &sst_seq_numbers_to_delete,
1117 "SST DELETED",
1118 |&seq| seq,
1119 )?;
1120 write_seq_numbers(
1121 &mut log,
1122 &meta_seq_numbers_to_delete,
1123 "META DELETED",
1124 |&seq| seq,
1125 )?;
1126 anyhow::Ok(())
1127 })() {
1128 eprintln!("turbo-persistence: failed to write LOG after commit {seq:08}: {e:#}");
1129 }
1130
1131 anyhow::Ok(())
1132 })?;
1133
1134 {
1140 let mut inner = self.inner.write();
1141
1142 for (meta_file, to_remove) in inner
1148 .meta_files
1149 .iter_mut()
1150 .zip(entries_to_remove.into_iter().rev())
1151 {
1152 if !to_remove.is_empty() {
1153 meta_file.retain_entries(|seq| !to_remove.contains(&seq));
1154 }
1155 }
1156
1157 inner.meta_files.append(&mut new_meta_files);
1158 if !meta_seq_numbers_to_delete.is_empty() {
1159 let to_delete: HashSet<u32> = meta_seq_numbers_to_delete.iter().copied().collect();
1160 inner
1161 .meta_files
1162 .retain(|meta| !to_delete.contains(&meta.sequence_number()));
1163 }
1164 inner.current_sequence_number = seq;
1165 self.is_empty
1166 .store(inner.meta_files.is_empty(), Ordering::Relaxed);
1167 }
1168
1169 self.deferred_deletions.lock().extend(
1174 Self::try_delete_files(&self.path, &sst_seq_numbers_to_delete, "sst")
1175 .map(DeferredDeletion::Sst)
1176 .chain(
1177 Self::try_delete_files(&self.path, &meta_seq_numbers_to_delete, "meta")
1178 .map(DeferredDeletion::Meta),
1179 )
1180 .chain(
1181 Self::try_delete_files(&self.path, &blob_seq_numbers_to_delete, "blob")
1182 .map(DeferredDeletion::Blob),
1183 ),
1184 );
1185
1186 self.retry_deferred_deletions();
1188
1189 #[cfg(feature = "verbose_log")]
1191 {
1192 let _: Result<(), _> = (|| -> anyhow::Result<()> {
1193 let mut log = self.open_log()?;
1194 writeln!(log, "New database state:")?;
1195 writeln!(log, "FAM | META SEQ | SST SEQ FLAGS | RANGE")?;
1196 let inner = self.inner.read();
1197 let families = inner.meta_files.iter().map(|meta| meta.family()).filter({
1198 let mut set = HashSet::new();
1199 move |family| set.insert(*family)
1200 });
1201 for family in families {
1202 for meta in inner.meta_files.iter() {
1203 if meta.family() != family {
1204 continue;
1205 }
1206 let meta_seq = meta.sequence_number();
1207 for entry in meta.entries().iter() {
1208 let seq = entry.sequence_number();
1209 let range = entry.range();
1210 writeln!(
1211 log,
1212 "{family:3} | {meta_seq:08} | {seq:08} {:>6} | {}",
1213 entry.flags(),
1214 range_to_str(range.min_hash, range.max_hash)
1215 )?;
1216 }
1217 }
1218 }
1219 Ok(())
1220 })();
1221 }
1222
1223 Ok(stats)
1224 }
1225
1226 pub fn full_compact(&self) -> Result<()> {
1229 self.compact(&CompactConfig {
1230 min_merge_count: 2,
1231 optimal_merge_count: usize::MAX,
1232 max_merge_count: usize::MAX,
1233 max_merge_bytes: u64::MAX,
1234 min_merge_duplication_bytes: 0,
1235 optimal_merge_duplication_bytes: u64::MAX,
1236 max_merge_segment_count: usize::MAX,
1237 })?;
1238 Ok(())
1239 }
1240
1241 pub fn compact(&self, compact_config: &CompactConfig) -> Result<Option<CommitStats>> {
1249 let mut guard = self.acquire_write_operation("compaction")?;
1250
1251 self.clear_cache();
1256
1257 let mut sequence_number;
1258 let mut new_meta_files = Vec::new();
1259 let mut new_sst_files = Vec::new();
1260 let mut sst_files_to_delete = Vec::new();
1261 let mut blob_seq_numbers_to_delete = Vec::new();
1262 let mut keys_written = 0;
1263
1264 {
1265 let inner = self.inner.read();
1266 sequence_number = AtomicU32::new(inner.current_sequence_number);
1267 self.compact_internal(
1268 &inner.meta_files,
1269 &sequence_number,
1270 &mut new_meta_files,
1271 &mut new_sst_files,
1272 &mut sst_files_to_delete,
1273 &mut blob_seq_numbers_to_delete,
1274 &mut keys_written,
1275 compact_config,
1276 )
1277 .context("Failed to compact database")?;
1278 }
1279
1280 let has_changes = !new_meta_files.is_empty();
1281 let stats = if has_changes {
1282 let stats = self
1283 .commit(CommitOptions {
1284 new_meta_files,
1285 new_sst_files,
1286 new_blob_files: Vec::new(),
1287 sst_files_to_delete,
1288 blob_seq_numbers_to_delete,
1289 sequence_number: *sequence_number.get_mut(),
1290 keys_written,
1291 })
1292 .context("Failed to commit the database compaction")?;
1293 Some(stats)
1294 } else {
1295 None
1296 };
1297
1298 guard.success();
1299 Ok(stats)
1300 }
1301
1302 fn compact_internal(
1304 &self,
1305 meta_files: &[MetaFile],
1306 sequence_number: &AtomicU32,
1307 new_meta_files: &mut Vec<NewFile>,
1308 new_sst_files: &mut Vec<NewFile>,
1309 sst_files_to_delete: &mut Vec<DeletedFile>,
1310 blob_seq_numbers_to_delete: &mut Vec<u32>,
1311 keys_written: &mut u64,
1312 compact_config: &CompactConfig,
1313 ) -> Result<()> {
1314 if meta_files.is_empty() {
1315 return Ok(());
1316 }
1317
1318 struct SstWithRange {
1319 meta_index: usize,
1320 index_in_meta: u32,
1321 seq: u32,
1322 range: StaticSortedFileRange,
1323 size: u64,
1324 flags: MetaEntryFlags,
1325 }
1326
1327 impl Compactable for SstWithRange {
1328 fn range(&self) -> RangeInclusive<u64> {
1329 self.range.min_hash..=self.range.max_hash
1330 }
1331
1332 fn size(&self) -> u64 {
1333 self.size
1334 }
1335
1336 fn category(&self) -> u8 {
1337 if self.flags.cold() { 1 } else { 0 }
1340 }
1341 }
1342
1343 let ssts_with_ranges = meta_files
1344 .iter()
1345 .enumerate()
1346 .flat_map(|(meta_index, meta)| {
1347 meta.entries()
1348 .iter()
1349 .enumerate()
1350 .map(move |(index_in_meta, entry)| SstWithRange {
1351 meta_index,
1352 index_in_meta: index_in_meta as u32,
1353 seq: entry.sequence_number(),
1354 range: entry.range(),
1355 size: entry.size(),
1356 flags: entry.flags(),
1357 })
1358 })
1359 .collect::<Vec<_>>();
1360
1361 let mut sst_by_family = [(); FAMILIES].map(|_| Vec::new());
1362
1363 for sst in ssts_with_ranges {
1364 sst_by_family[sst.range.family as usize].push(sst);
1365 }
1366
1367 let path = &self.path;
1368
1369 let log_mutex = Mutex::new(());
1370
1371 struct PartialResultPerFamily {
1372 new_meta_file: Option<NewFile>,
1373 new_sst_files: Vec<NewFile>,
1374 sst_files_to_delete: Vec<DeletedFile>,
1375 blob_seq_numbers_to_delete: Vec<u32>,
1376 keys_written: u64,
1377 }
1378
1379 let mut compact_config = compact_config.clone();
1380 let merge_jobs = sst_by_family
1381 .into_iter()
1382 .enumerate()
1383 .filter_map(|(family, ssts_with_ranges)| {
1384 if compact_config.max_merge_segment_count == 0 {
1385 return None;
1386 }
1387 let (merge_jobs, real_merge_job_size) =
1388 get_merge_segments(&ssts_with_ranges, &compact_config);
1389 compact_config.max_merge_segment_count -= real_merge_job_size;
1390 Some((family, ssts_with_ranges, merge_jobs))
1391 })
1392 .collect::<Vec<_>>();
1393
1394 let result = self
1395 .parallel_scheduler
1396 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(
1397 merge_jobs,
1398 |(family, ssts_with_ranges, merge_jobs)| {
1399 let family = family as u32;
1400
1401 if merge_jobs.is_empty() {
1402 return Ok(PartialResultPerFamily {
1403 new_meta_file: None,
1404 new_sst_files: Vec::new(),
1405 sst_files_to_delete: Vec::new(),
1406 blob_seq_numbers_to_delete: Vec::new(),
1407 keys_written: 0,
1408 });
1409 }
1410
1411 let used_key_hashes: Option<qfilter::Filter> = {
1416 let filters: Vec<qfilter::FilterRef<'_>> = meta_files
1417 .iter()
1418 .filter(|m| m.family() == family)
1419 .filter_map(|meta_file| {
1420 meta_file.deserialize_used_key_hashes_amqf().transpose()
1421 })
1422 .collect::<Result<Vec<_>>>()?
1423 .into_iter()
1424 .filter(|amqf| !amqf.is_empty())
1425 .collect();
1426 if filters.is_empty() {
1427 None
1428 } else if filters.len() == 1 {
1429 Some(filters[0].to_owned())
1431 } else {
1432 let total_len: u64 = filters.iter().map(|f| f.len()).sum();
1433 let mut merged =
1436 qfilter::Filter::with_fingerprint_size(total_len, u64::BITS as u8)
1437 .expect("Failed to create merged AMQF filter");
1438 for filter in &filters {
1439 merged
1440 .merge(false, filter)
1441 .expect("Failed to merge AMQF filters");
1442 }
1443 merged.shrink_to_fit();
1444 Some(merged)
1445 }
1446 };
1447
1448 let sst_files_to_delete = merge_jobs
1451 .iter()
1452 .filter(|l| l.len() > 1)
1453 .flat_map(|l| l.iter().copied())
1454 .map(|index| DeletedFile {
1455 seq: ssts_with_ranges[index].seq,
1456 size: ssts_with_ranges[index].size,
1457 })
1458 .collect::<Vec<_>>();
1459
1460 let span = tracing::trace_span!(
1462 "merge files",
1463 family = self.config.family_configs[family as usize].name
1464 );
1465 enum PartialMergeResult<'l> {
1466 Merged {
1467 new_sst_files: Vec<(u32, File, StaticSortedFileBuilderMeta<'static>)>,
1468 blob_seq_numbers_to_delete: Vec<u32>,
1469 keys_written: u64,
1470 indices: SmallVec<[usize; 1]>,
1471 },
1472 Move {
1473 seq: u32,
1474 meta: StaticSortedFileBuilderMeta<'l>,
1475 },
1476 }
1477 let merge_result = self
1478 .parallel_scheduler
1479 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(merge_jobs, |indices| {
1480 let _span = span.clone().entered();
1481 if indices.len() == 1 {
1482 let index = indices[0];
1484 let meta_index = ssts_with_ranges[index].meta_index;
1485 let index_in_meta = ssts_with_ranges[index].index_in_meta;
1486 let meta_file = &meta_files[meta_index];
1487 let entry = meta_file.entry(index_in_meta);
1488 let amqf = Cow::Borrowed(entry.raw_amqf(meta_file.amqf_data()));
1489 let meta = StaticSortedFileBuilderMeta {
1490 min_hash: entry.min_hash(),
1491 max_hash: entry.max_hash(),
1492 amqf,
1493 block_count: entry.block_count(),
1494 size: entry.size(),
1495 flags: entry.flags(),
1496 entries: 0,
1497 };
1498 return Ok(PartialMergeResult::Move {
1499 seq: entry.sequence_number(),
1500 meta,
1501 });
1502 }
1503
1504 let iters = indices
1508 .iter()
1509 .map(|&index| {
1510 let meta_index = ssts_with_ranges[index].meta_index;
1511 let index_in_meta = ssts_with_ranges[index].index_in_meta;
1512 let entry = meta_files[meta_index].entry(index_in_meta);
1513 StaticSortedFileIter::open(path, entry.sst_metadata())
1514 })
1515 .collect::<Result<Vec<_>>>()?;
1516
1517 let iter = MergeIter::new(iters.into_iter())?;
1518
1519 let mut blob_seq_numbers_to_delete: Vec<u32> = Vec::new();
1520
1521 struct Collector {
1522 writer: Option<(u32, StreamingSstWriter<LookupEntry>)>,
1529 flags: MetaEntryFlags,
1530 new_sst_files:
1531 Vec<(u32, File, StaticSortedFileBuilderMeta<'static>)>,
1532 last_hash: Option<u64>,
1535 }
1536 impl Collector {
1537 fn new(flags: MetaEntryFlags) -> Self {
1538 Self {
1539 writer: None,
1540 flags,
1541 new_sst_files: Vec::new(),
1542 last_hash: None,
1543 }
1544 }
1545
1546 fn ensure_writer(
1548 &mut self,
1549 path: &Path,
1550 sequence_number: &AtomicU32,
1551 ) -> Result<&mut StreamingSstWriter<LookupEntry>>
1552 {
1553 if self.writer.is_none() {
1554 let seq =
1555 sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
1556 let sst_path = path.join(format!("{seq:08}.sst"));
1557 let writer = StreamingSstWriter::new(
1558 &sst_path,
1559 self.flags,
1560 MAX_ENTRIES_PER_COMPACTED_FILE as u64,
1561 )?;
1562 self.writer = Some((seq, writer));
1563 }
1564 Ok(&mut self.writer.as_mut().unwrap().1)
1565 }
1566
1567 fn close_sst_file(&mut self, keys_written: &mut u64) -> Result<()> {
1571 if let Some((seq, writer)) = self.writer.take() {
1572 let _span =
1573 tracing::trace_span!("close merged sst file").entered();
1574 let (meta, file) = writer.close()?;
1575 *keys_written += meta.entries;
1576 self.new_sst_files.push((seq, file, meta));
1577 }
1578 Ok(())
1579 }
1580
1581 fn add_entry(
1585 &mut self,
1586 entry: LookupEntry,
1587 path: &Path,
1588 sequence_number: &AtomicU32,
1589 keys_written: &mut u64,
1590 ) -> Result<()> {
1591 let key_changed = self.last_hash != Some(entry.hash);
1592 if key_changed
1595 && let Some((_, ref writer)) = self.writer
1596 && writer.is_full(
1597 MAX_ENTRIES_PER_COMPACTED_FILE,
1598 DATA_THRESHOLD_PER_COMPACTED_FILE,
1599 )
1600 {
1601 self.close_sst_file(keys_written)?;
1602 }
1603 self.last_hash = Some(entry.hash);
1604 let writer = self.ensure_writer(path, sequence_number)?;
1605 writer.add(entry)?;
1606 Ok(())
1607 }
1608 }
1609 #[cfg(debug_assertions)]
1610 impl Drop for Collector {
1611 fn drop(&mut self) {
1612 if !std::thread::panicking() {
1613 assert!(
1614 self.writer.is_none(),
1615 "Collector dropped with an open writer"
1616 );
1617 }
1618 }
1619 }
1620 let mut used_collector = Collector::new(MetaEntryFlags::WARM);
1621 let mut unused_collector = Collector::new(MetaEntryFlags::COLD);
1622 let mut current_key: Option<RcBytes> = None;
1623 let mut keys_written = 0;
1624
1625 let mut skip_remaining_for_this_key = false;
1632 let family_config = &self.config.family_configs[family as usize];
1633
1634 for entry in iter {
1635 let entry = entry?;
1636 if current_key.as_ref() != Some(&entry.key) {
1637 skip_remaining_for_this_key = false;
1639 current_key = Some(entry.key.clone());
1640 }
1641 if !skip_remaining_for_this_key {
1642 let is_used = used_key_hashes
1643 .as_ref()
1644 .is_some_and(|amqf| amqf.contains_fingerprint(entry.hash));
1645 let collector = if is_used {
1646 &mut used_collector
1647 } else {
1648 &mut unused_collector
1649 };
1650 match family_config.kind {
1651 FamilyKind::MultiValue => {
1652 if matches!(entry.value, IterValue::Deleted) {
1655 skip_remaining_for_this_key = true;
1656 }
1657 }
1658 FamilyKind::SingleValue => {
1659 skip_remaining_for_this_key = true;
1662 }
1663 }
1664 collector.add_entry(
1665 entry,
1666 path,
1667 sequence_number,
1668 &mut keys_written,
1669 )?;
1670 } else {
1671 if let IterValue::Blob { sequence_number } = &entry.value {
1675 blob_seq_numbers_to_delete.push(*sequence_number);
1676 }
1677 }
1678 }
1679
1680 used_collector.close_sst_file(&mut keys_written)?;
1682 unused_collector.close_sst_file(&mut keys_written)?;
1683
1684 let mut new_sst_files = take(&mut unused_collector.new_sst_files);
1685 new_sst_files.append(&mut used_collector.new_sst_files);
1686 Ok(PartialMergeResult::Merged {
1687 new_sst_files,
1688 blob_seq_numbers_to_delete,
1689 keys_written,
1690 indices,
1691 })
1692 })
1693 .with_context(|| {
1694 format!("Failed to merge database files for family {family}")
1695 })?;
1696
1697 let Some((sst_files_len, blob_delete_len)) = merge_result
1698 .iter()
1699 .map(|r| {
1700 if let PartialMergeResult::Merged {
1701 new_sst_files,
1702 blob_seq_numbers_to_delete,
1703 indices: _,
1704 keys_written: _,
1705 } = r
1706 {
1707 (new_sst_files.len(), blob_seq_numbers_to_delete.len())
1708 } else {
1709 (0, 0)
1710 }
1711 })
1712 .reduce(|(a1, a2), (b1, b2)| (a1 + b1, a2 + b2))
1713 else {
1714 unreachable!()
1715 };
1716
1717 let mut new_sst_files = Vec::with_capacity(sst_files_len);
1718 let mut blob_seq_numbers_to_delete = Vec::with_capacity(blob_delete_len);
1719
1720 let meta_seq = sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
1721 let mut meta_file_builder = MetaFileBuilder::new(family);
1722
1723 let mut keys_written = 0;
1724 self.parallel_scheduler.block_in_place(|| {
1725 let guard = log_mutex.lock();
1726 let mut log = self.open_log()?;
1727 writeln!(log, "{family:3} | {meta_seq:08} | Compaction:",)?;
1728 for result in merge_result {
1729 match result {
1730 PartialMergeResult::Merged {
1731 new_sst_files: merged_new_sst_files,
1732 blob_seq_numbers_to_delete: merged_blob_seq_numbers_to_delete,
1733 keys_written: merged_keys_written,
1734 indices,
1735 } => {
1736 writeln!(
1737 log,
1738 "{family:3} | {meta_seq:08} | MERGE \
1739 ({merged_keys_written} keys):"
1740 )?;
1741 for i in indices.iter() {
1742 let seq = ssts_with_ranges[*i].seq;
1743 let (min, max) = ssts_with_ranges[*i].range().into_inner();
1744 writeln!(
1745 log,
1746 "{family:3} | {meta_seq:08} | {seq:08} INPUT | {}",
1747 range_to_str(min, max)
1748 )?;
1749 }
1750 for (seq, file, meta) in merged_new_sst_files {
1751 let min = meta.min_hash;
1752 let max = meta.max_hash;
1753 writeln!(
1754 log,
1755 "{family:3} | {meta_seq:08} | {seq:08} OUTPUT | {} \
1756 ({})",
1757 range_to_str(min, max),
1758 meta.flags
1759 )?;
1760
1761 let size = meta.size;
1762 meta_file_builder.add(seq, meta);
1763 new_sst_files.push(NewFile { seq, file, size });
1764 }
1765 blob_seq_numbers_to_delete
1766 .extend(merged_blob_seq_numbers_to_delete);
1767 keys_written += merged_keys_written;
1768 }
1769 PartialMergeResult::Move { seq, meta } => {
1770 let min = meta.min_hash;
1771 let max = meta.max_hash;
1772 writeln!(
1773 log,
1774 "{family:3} | {meta_seq:08} | {seq:08} MOVED | {}",
1775 range_to_str(min, max)
1776 )?;
1777
1778 meta_file_builder.add(seq, meta);
1779 }
1780 }
1781 }
1782 drop(log);
1783 drop(guard);
1784
1785 anyhow::Ok(())
1786 })?;
1787
1788 for f in sst_files_to_delete.iter() {
1789 meta_file_builder.add_obsolete_sst_file(f.seq);
1790 }
1791
1792 let new_meta_file = {
1793 let _span = tracing::trace_span!("write meta file").entered();
1794 let (file, size) = self
1795 .parallel_scheduler
1796 .block_in_place(|| meta_file_builder.write(&self.path, meta_seq))?;
1797 NewFile {
1798 seq: meta_seq,
1799 file,
1800 size,
1801 }
1802 };
1803
1804 Ok(PartialResultPerFamily {
1805 new_meta_file: Some(new_meta_file),
1806 new_sst_files,
1807 sst_files_to_delete,
1808 blob_seq_numbers_to_delete,
1809 keys_written,
1810 })
1811 },
1812 )?;
1813
1814 for PartialResultPerFamily {
1815 new_meta_file: inner_new_meta_file,
1816 new_sst_files: mut inner_new_sst_files,
1817 sst_files_to_delete: mut inner_sst_files_to_delete,
1818 blob_seq_numbers_to_delete: mut inner_blob_seq_numbers_to_delete,
1819 keys_written: inner_keys_written,
1820 } in result
1821 {
1822 new_meta_files.extend(inner_new_meta_file);
1823 new_sst_files.append(&mut inner_new_sst_files);
1824 sst_files_to_delete.append(&mut inner_sst_files_to_delete);
1825 blob_seq_numbers_to_delete.append(&mut inner_blob_seq_numbers_to_delete);
1826 *keys_written += inner_keys_written;
1827 }
1828
1829 Ok(())
1830 }
1831
1832 pub fn get<K: QueryKey>(&self, family: usize, key: &K) -> Result<Option<ArcBytes>> {
1835 debug_assert!(family < FAMILIES, "Family index out of bounds");
1836 if self.config.family_configs[family].kind != FamilyKind::SingleValue {
1837 panic!(
1839 "only single valued tables can be queried with `get', call `get_multiple` instead"
1840 )
1841 }
1842 let span = tracing::trace_span!(
1843 "database read",
1844 name = self.config.family_configs[family].name,
1845 result_size = tracing::field::Empty
1846 )
1847 .entered();
1848 let results = self.get_impl::<K, false>(family, key, &span)?;
1849 debug_assert!(results.len() <= 1, "get() should return at most one result");
1850 Ok(results.into_iter().next())
1851 }
1852
1853 pub fn get_multiple<K: QueryKey>(
1863 &self,
1864 family: usize,
1865 key: &K,
1866 ) -> Result<SmallVec<[ArcBytes; 1]>> {
1867 debug_assert!(family < FAMILIES, "Family index out of bounds");
1868 if self.config.family_configs[family].kind != FamilyKind::MultiValue {
1869 panic!("only multi-valued tables can be queried with `get_multiple`")
1871 }
1872 let span = tracing::trace_span!(
1873 "database read multiple",
1874 name = self.config.family_configs[family].name,
1875 result_count = tracing::field::Empty,
1876 result_size = tracing::field::Empty
1877 )
1878 .entered();
1879 let results = self.get_impl::<K, true>(family, key, &span)?;
1880 Ok(results)
1881 }
1882
1883 fn get_impl<K: QueryKey, const FIND_ALL: bool>(
1888 &self,
1889 family: usize,
1890 key: &K,
1891 span: &EnteredSpan,
1892 ) -> Result<SmallVec<[ArcBytes; 1]>> {
1893 let hash = hash_key(key);
1894 let inner = self.inner.read();
1895 let mut output: SmallVec<[ArcBytes; 1]> = SmallVec::new();
1896 #[cfg(feature = "stats")]
1899 let mut found_in_sst = false;
1900
1901 let mut size = 0;
1902
1903 for meta in inner.meta_files.iter().rev() {
1904 match meta.lookup::<K, FIND_ALL>(
1905 family as u32,
1906 hash,
1907 key,
1908 self.key_block_cache(),
1909 self.value_block_cache(),
1910 )? {
1911 MetaLookupResult::FamilyMiss => {
1912 #[cfg(feature = "stats")]
1913 self.stats.miss_family.fetch_add(1, Ordering::Relaxed);
1914 }
1915 MetaLookupResult::RangeMiss => {
1916 #[cfg(feature = "stats")]
1917 self.stats.miss_range.fetch_add(1, Ordering::Relaxed);
1918 }
1919 MetaLookupResult::QuickFilterMiss => {
1920 #[cfg(feature = "stats")]
1921 self.stats.miss_amqf.fetch_add(1, Ordering::Relaxed);
1922 }
1923 MetaLookupResult::SstLookup(result) => match result {
1924 SstLookupResult::Found(values) => {
1925 #[cfg(feature = "stats")]
1926 {
1927 found_in_sst = true;
1928 }
1929 inner.accessed_key_hashes[family].insert(hash);
1930 for value in values {
1933 match value {
1934 LookupValue::Deleted => {
1935 #[cfg(feature = "stats")]
1936 self.stats.hits_deleted.fetch_add(1, Ordering::Relaxed);
1937 if !FIND_ALL {
1938 span.record("result_size", "deleted");
1939 return Ok(SmallVec::new());
1940 }
1941 if output.is_empty() {
1945 span.record("result_size", "deleted");
1946 } else {
1947 span.record("result_size", size);
1948 }
1949 return Ok(output);
1950 }
1951 LookupValue::Slice { value } => {
1952 #[cfg(feature = "stats")]
1953 self.stats.hits_small.fetch_add(1, Ordering::Relaxed);
1954 if !FIND_ALL {
1955 span.record("result_size", value.len());
1956 return Ok(SmallVec::from_buf([value]));
1957 }
1958 size += value.len();
1959 output.push(value);
1960 }
1961 LookupValue::Blob { sequence_number } => {
1962 #[cfg(feature = "stats")]
1963 self.stats.hits_blob.fetch_add(1, Ordering::Relaxed);
1964 let blob = self.read_blob(sequence_number)?;
1965 if !FIND_ALL {
1966 span.record("result_size", blob.len());
1967 return Ok(SmallVec::from_buf([blob]));
1968 }
1969 size += blob.len();
1970 output.push(blob);
1971 }
1972 }
1973 }
1974 }
1975 SstLookupResult::NotFound => {
1976 #[cfg(feature = "stats")]
1977 self.stats.miss_key.fetch_add(1, Ordering::Relaxed);
1978 }
1979 },
1980 }
1981 }
1982
1983 #[cfg(feature = "stats")]
1984 if !found_in_sst {
1985 self.stats.miss_global.fetch_add(1, Ordering::Relaxed);
1986 }
1987
1988 if FIND_ALL {
1989 span.record("result_count", output.len());
1990 }
1991 if output.is_empty() {
1992 span.record("result_size", "not_found");
1993 } else {
1994 span.record("result_size", size);
1995 }
1996 Ok(output)
1997 }
1998
1999 pub fn batch_get<K: QueryKey>(
2000 &self,
2001 family: usize,
2002 keys: &[K],
2003 ) -> Result<Vec<Option<ArcBytes>>> {
2004 debug_assert!(family < FAMILIES, "Family index out of bounds");
2005 if self.config.family_configs[family].kind != FamilyKind::SingleValue {
2006 panic!("only single valued tables can be queried with `batch_get'")
2008 }
2009 let span = tracing::trace_span!(
2010 "database batch read",
2011 name = self.config.family_configs[family].name,
2012 keys = keys.len(),
2013 not_found = tracing::field::Empty,
2014 deleted = tracing::field::Empty,
2015 result_size = tracing::field::Empty
2016 )
2017 .entered();
2018 let mut cells: Vec<(u64, usize, Option<LookupValue>)> = Vec::with_capacity(keys.len());
2019 let mut empty_cells = keys.len();
2020 for (index, key) in keys.iter().enumerate() {
2021 let hash = hash_key(key);
2022 cells.push((hash, index, None));
2023 }
2024 cells.sort_by_key(|(hash, _, _)| *hash);
2025 let inner = self.inner.read();
2026 for meta in inner.meta_files.iter().rev() {
2027 let _result = meta.batch_lookup(
2028 family as u32,
2029 keys,
2030 &mut cells,
2031 &mut empty_cells,
2032 self.key_block_cache(),
2033 self.value_block_cache(),
2034 )?;
2035
2036 #[cfg(feature = "stats")]
2037 {
2038 let crate::meta_file::MetaBatchLookupResult {
2039 family_miss,
2040 range_misses,
2041 quick_filter_misses,
2042 sst_misses,
2043 hits: _,
2044 } = _result;
2045 if family_miss {
2046 self.stats.miss_family.fetch_add(1, Ordering::Relaxed);
2047 }
2048 if range_misses > 0 {
2049 self.stats
2050 .miss_range
2051 .fetch_add(range_misses as u64, Ordering::Relaxed);
2052 }
2053 if quick_filter_misses > 0 {
2054 self.stats
2055 .miss_amqf
2056 .fetch_add(quick_filter_misses as u64, Ordering::Relaxed);
2057 }
2058 if sst_misses > 0 {
2059 self.stats
2060 .miss_key
2061 .fetch_add(sst_misses as u64, Ordering::Relaxed);
2062 }
2063 }
2064
2065 if empty_cells == 0 {
2066 break;
2067 }
2068 }
2069 let mut deleted = 0;
2070 let mut not_found = 0;
2071 let mut result_size = 0;
2072 let mut results = vec![None; keys.len()];
2073 for (hash, index, result) in cells {
2074 if let Some(result) = result {
2075 inner.accessed_key_hashes[family].insert(hash);
2076 let result = match result {
2077 LookupValue::Deleted => {
2078 #[cfg(feature = "stats")]
2079 self.stats.hits_deleted.fetch_add(1, Ordering::Relaxed);
2080 deleted += 1;
2081 None
2082 }
2083 LookupValue::Slice { value } => {
2084 #[cfg(feature = "stats")]
2085 self.stats.hits_small.fetch_add(1, Ordering::Relaxed);
2086 result_size += value.len();
2087 Some(value)
2088 }
2089 LookupValue::Blob { sequence_number } => {
2090 #[cfg(feature = "stats")]
2091 self.stats.hits_blob.fetch_add(1, Ordering::Relaxed);
2092 let blob = self.read_blob(sequence_number)?;
2093 result_size += blob.len();
2094 Some(blob)
2095 }
2096 };
2097 results[index] = result;
2098 } else {
2099 #[cfg(feature = "stats")]
2100 self.stats.miss_global.fetch_add(1, Ordering::Relaxed);
2101 not_found += 1;
2102 }
2103 }
2104 span.record("not_found", not_found);
2105 span.record("deleted", deleted);
2106 span.record("result_size", result_size);
2107 Ok(results)
2108 }
2109
2110 #[cfg(feature = "stats")]
2112 pub fn statistics(&self) -> Statistics {
2113 let inner = self.inner.read();
2114 Statistics {
2115 meta_files: inner.meta_files.len(),
2116 sst_files: inner.meta_files.iter().map(|m| m.entries().len()).sum(),
2117 key_block_cache: CacheStatistics::new(self.key_block_cache()),
2118 value_block_cache: CacheStatistics::new(self.value_block_cache()),
2119 hits: self.stats.hits_deleted.load(Ordering::Relaxed)
2120 + self.stats.hits_small.load(Ordering::Relaxed)
2121 + self.stats.hits_blob.load(Ordering::Relaxed),
2122 misses: self.stats.miss_global.load(Ordering::Relaxed),
2123 miss_family: self.stats.miss_family.load(Ordering::Relaxed),
2124 miss_range: self.stats.miss_range.load(Ordering::Relaxed),
2125 miss_amqf: self.stats.miss_amqf.load(Ordering::Relaxed),
2126 miss_key: self.stats.miss_key.load(Ordering::Relaxed),
2127 }
2128 }
2129
2130 pub fn meta_info(&self) -> Result<Vec<MetaFileInfo>> {
2131 Ok(self
2132 .inner
2133 .read()
2134 .meta_files
2135 .iter()
2136 .rev()
2137 .map(|meta_file| {
2138 let entries = meta_file
2139 .entries()
2140 .iter()
2141 .map(|entry| {
2142 let amqf = entry.raw_amqf(meta_file.amqf_data());
2143 MetaFileEntryInfo {
2144 sequence_number: entry.sequence_number(),
2145 min_hash: entry.min_hash(),
2146 max_hash: entry.max_hash(),
2147 sst_size: entry.size(),
2148 flags: entry.flags(),
2149 amqf_size: entry.amqf_size(),
2150 amqf_entries: amqf.len(),
2151 block_count: entry.block_count(),
2152 }
2153 })
2154 .collect();
2155 MetaFileInfo {
2156 sequence_number: meta_file.sequence_number(),
2157 family: meta_file.family(),
2158 obsolete_sst_files: meta_file.obsolete_sst_files().to_vec(),
2159 entries,
2160 }
2161 })
2162 .collect())
2163 }
2164
2165 pub fn shutdown(&self) -> Result<()> {
2168 #[cfg(feature = "print_stats")]
2169 println!("{:#?}", self.statistics());
2170 self.retry_deferred_deletions();
2171 Ok(())
2172 }
2173
2174 fn try_delete_files<'a>(
2177 dir: &'a Path,
2178 seqs: &'a [u32],
2179 ext: &'a str,
2180 ) -> impl Iterator<Item = u32> + 'a {
2181 seqs.iter()
2182 .copied()
2183 .filter(move |&seq| fs::remove_file(dir.join(format!("{seq:08}.{ext}"))).is_err())
2184 }
2185
2186 fn retry_deferred_deletions(&self) {
2191 let mut deferred = self.deferred_deletions.lock();
2192 deferred.retain(|entry| {
2193 let (seq, ext) = match *entry {
2194 DeferredDeletion::Sst(seq) => (seq, "sst"),
2195 DeferredDeletion::Meta(seq) => (seq, "meta"),
2196 DeferredDeletion::Blob(seq) => (seq, "blob"),
2197 };
2198 fs::remove_file(self.path.join(format!("{seq:08}.{ext}"))).is_err()
2200 });
2201 }
2202}
2203
2204fn range_to_str(min: u64, max: u64) -> String {
2205 use std::fmt::Write;
2206 const DISPLAY_SIZE: usize = 100;
2207 const TOTAL_SIZE: u64 = u64::MAX;
2208 let start_pos = (min as u128 * DISPLAY_SIZE as u128 / TOTAL_SIZE as u128) as usize;
2209 let end_pos = (max as u128 * DISPLAY_SIZE as u128 / TOTAL_SIZE as u128) as usize;
2210 let mut range_str = String::new();
2211 for i in 0..DISPLAY_SIZE {
2212 if i == start_pos && i == end_pos {
2213 range_str.push('O');
2214 } else if i == start_pos {
2215 range_str.push('[');
2216 } else if i == end_pos {
2217 range_str.push(']');
2218 } else if i > start_pos && i < end_pos {
2219 range_str.push('=');
2220 } else {
2221 range_str.push(' ');
2222 }
2223 }
2224 write!(range_str, " | {min:016x}-{max:016x}").unwrap();
2225 range_str
2226}
2227
2228pub struct MetaFileInfo {
2229 pub sequence_number: u32,
2230 pub family: u32,
2231 pub obsolete_sst_files: Vec<u32>,
2232 pub entries: Vec<MetaFileEntryInfo>,
2233}
2234
2235pub struct MetaFileEntryInfo {
2236 pub sequence_number: u32,
2237 pub min_hash: u64,
2238 pub max_hash: u64,
2239 pub amqf_size: u32,
2240 pub amqf_entries: usize,
2241 pub sst_size: u64,
2242 pub flags: MetaEntryFlags,
2243 pub block_count: u16,
2244}