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