lsm_tree/abstract_tree.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-present, fjall-rs
3// Copyright (c) 2026-present, Dmitry Prudnikov
4
5use crate::tree::inner::{FlushGuard, VersionsWriteGuard};
6use crate::{
7 AnyTree, BlobTree, Config, Guard, InternalValue, KvPair, Memtable, SeqNo, TableId, Tree,
8 UserKey, UserValue,
9 iter_guard::{IterGuardImpl, SeekableGuardIter},
10 table::Table,
11 version::Version,
12 vlog::BlobFile,
13};
14use alloc::sync::Arc;
15#[cfg(not(feature = "std"))]
16use alloc::{boxed::Box, vec::Vec};
17use core::ops::RangeBounds;
18
19pub type RangeItem = crate::Result<KvPair>;
20
21type FlushToTablesResult = (Vec<Table>, Option<Vec<BlobFile>>);
22
23/// Summary of a checkpoint produced by
24/// [`AbstractTree::create_checkpoint`].
25///
26/// All byte counts are *logical* file sizes — hard links share the
27/// underlying inode storage, so a checkpoint's marginal disk usage is
28/// typically zero until the original files are compacted away.
29#[derive(Debug, Clone, Copy)]
30pub struct CheckpointInfo {
31 /// Number of SST files captured.
32 pub sst_files: usize,
33 /// Number of blob (value-log) files captured. Always `0` for a
34 /// standard [`Tree`].
35 pub blob_files: usize,
36 /// Sum of the logical file sizes of every file the checkpoint captured:
37 /// the SSTs, the blob files, and the `.restrict-bound` sidecar that belongs
38 /// to a tight-space-restricted SST (per-table state, written beside its
39 /// table, that a restore needs to recover the exact bound). Tree-level
40 /// metadata — the manifest, the version pointer, the config — is NOT
41 /// counted: this figure describes the captured data, not the whole
42 /// directory.
43 ///
44 /// It measures the same SET of files as
45 /// [`StorageStats::used_bytes`](crate::StorageStats::used_bytes), which
46 /// reports them PHYSICALLY, so the two differ by exactly the bytes a
47 /// tight-space reclaim punched out and agree everywhere else.
48 pub total_bytes: u64,
49 /// The version ID embedded in the checkpoint's `current` pointer.
50 pub version_id: u64,
51 /// Lower-bound visible-seqno watermark for the snapshot.
52 ///
53 /// Captured from the tree's `visible_seqno` generator BEFORE
54 /// [`AbstractTree::current_version`]. Following the standard
55 /// "lowest-excluded" watermark convention, `info.seqno = N` means
56 /// every record with `seqno < N` was committed at sample time and
57 /// is therefore guaranteed to be present in the snapshot. Records
58 /// with `seqno == N` may or may not be included (writers can hold
59 /// a record in the memtable for an instant before publishing the
60 /// next watermark); records with `seqno > N` may also be present
61 /// (writers can advance the counter between sample and version
62 /// snapshot, and those keys still land in the captured memtable).
63 ///
64 /// PITR consumers MUST use `seqno < info.seqno` as the inclusion
65 /// gate. Using `<=` (treating this as a max-included ceiling)
66 /// could move a recovery cutoff past data still needed from WAL
67 /// or replication; the field is a strict lower-exclusive watermark,
68 /// not a max-included ceiling.
69 pub seqno: SeqNo,
70}
71
72// Sealed on purpose: this trait is still public as a consumer-side bound
73// (`&impl AbstractTree`), but external implementations are no longer part of
74// the supported extension surface. Internal flush/version hooks keep evolving
75// with crate-owned tree types and must not create downstream semver traps.
76//
77// `sealed` stays `pub` only so sibling modules in this crate can write
78// `crate::abstract_tree::sealed::Sealed` in their impls. The parent module
79// `abstract_tree` is not publicly exported from the crate root, so downstream
80// crates still cannot name or implement this trait.
81pub mod sealed {
82 pub trait Sealed {}
83}
84
85/// Outcome of [`AbstractTree::refresh_table_checksum`].
86#[cfg(feature = "std")]
87#[doc(hidden)]
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum ChecksumRefreshOutcome {
90 /// The digest was installed into the manifest.
91 Refreshed,
92 /// Legitimate no-op: the table was compacted away, or its restriction no
93 /// longer matches the one the digest was computed for; the current view
94 /// carries its own (compaction-installed) digest for the next patrol to
95 /// reconcile.
96 Stale,
97 /// The install lock was held by a concurrent compaction (blocking on it
98 /// would invert the heal-lock / compaction-state order and deadlock). The
99 /// manifest digest stays stale against durable healed bytes: the caller
100 /// must surface this as a finding, keep the attestation, and let a later
101 /// patrol retry.
102 Contended,
103}
104
105/// Generic Tree API
106#[enum_dispatch::enum_dispatch]
107pub trait AbstractTree: sealed::Sealed {
108 /// Debug method for tracing the MVCC history of a key.
109 #[doc(hidden)]
110 fn print_trace(&self, key: &[u8]) -> crate::Result<()>;
111
112 /// Returns the number of cached table file descriptors.
113 fn table_file_cache_size(&self) -> usize;
114
115 // TODO: remove
116 #[doc(hidden)]
117 fn version_memtable_size_sum(&self) -> u64 {
118 self.get_version_history_lock().memtable_size_sum()
119 }
120
121 #[doc(hidden)]
122 fn next_table_id(&self) -> TableId;
123
124 #[doc(hidden)]
125 fn id(&self) -> crate::TreeId;
126
127 /// Like [`AbstractTree::get`], but returns the actual internal entry, not just the user value.
128 ///
129 /// Used in tests.
130 #[doc(hidden)]
131 fn get_internal_entry(&self, key: &[u8], seqno: SeqNo) -> crate::Result<Option<InternalValue>>;
132
133 #[doc(hidden)]
134 fn current_version(&self) -> Version;
135
136 /// Records that the table with `table_id` now has the digest `checksum`,
137 /// persisting it to the manifest through a version upgrade.
138 ///
139 /// Called after an in-place heal rewrites a table's bytes: the healed
140 /// file's digest no longer matches the one captured at recovery, and
141 /// without the refresh a later [`verify`](crate::verify) pass (or a
142 /// repair) would flag the healed file as corrupted against the stale
143 /// manifest digest. A no-op when the table is no longer part of the
144 /// current version (compacted away while the heal ran — the old file is
145 /// on its way out).
146 ///
147 /// `expected_restriction` is the tight-space restriction bound the CALLER
148 /// computed `checksum` for (its captured view's [`restrict_lower_bound`]).
149 /// This method holds the install lock, so it re-checks it against the CURRENT
150 /// view under that lock and refuses the install (a no-op) if a compaction
151 /// swapped the view to a different restriction meanwhile: a whole-file digest
152 /// installed into a suffix-only restricted manifest (or vice versa) could
153 /// never match the punched file.
154 ///
155 /// Returns [`ChecksumRefreshOutcome::Refreshed`] when the digest was
156 /// installed, [`ChecksumRefreshOutcome::Stale`] on a legitimate no-op (the
157 /// table was compacted away, or its restriction no longer matches
158 /// `expected_restriction` — the current view carries its own digest), and
159 /// [`ChecksumRefreshOutcome::Contended`] when the install lock was held by
160 /// a concurrent compaction. The caller uses this to decide whether to clear
161 /// the heal attestation (only `Refreshed` may) and whether the pass stayed
162 /// clean: a contended skip leaves the manifest digest stale against durable
163 /// healed bytes, which must surface as a finding rather than a clean
164 /// report.
165 ///
166 /// [`restrict_lower_bound`]: crate::table::Table::restrict_lower_bound
167 #[cfg(feature = "std")]
168 #[doc(hidden)]
169 fn refresh_table_checksum(
170 &self,
171 table_id: TableId,
172 checksum: crate::checksum::Checksum,
173 expected_restriction: Option<&crate::UserKey>,
174 ) -> crate::Result<ChecksumRefreshOutcome>;
175
176 /// The tree's configured durability mode
177 /// ([`Config::sync_mode`](crate::config::Config::sync_mode)). Maintenance
178 /// paths that write outside the flush pipeline (the in-place heal) read
179 /// it here so their syncs honor the same durability the tree's own
180 /// writes use.
181 #[doc(hidden)]
182 fn sync_mode(&self) -> crate::fs::SyncMode;
183
184 /// The tree's configured prefix extractor, or `None` when it indexes no
185 /// prefixes. The patrol scrub's filter cross-check reads it here so it
186 /// can verify a rebuilt full filter carries the source's prefix hashes,
187 /// not just its complete-key hashes.
188 #[doc(hidden)]
189 fn prefix_extractor(&self) -> Option<alloc::sync::Arc<dyn crate::prefix::PrefixExtractor>>;
190
191 /// Returns a read-only snapshot of the tree's on-disk storage footprint:
192 /// total used bytes, entry count, the average shape of a stored entry
193 /// (average key / value bytes), and an estimate of how many more
194 /// average-shaped entries fit in a byte budget (see
195 /// [`StorageStats::estimated_remaining_entries`](crate::StorageStats::estimated_remaining_entries)).
196 ///
197 /// Computed from the live version's table + blob metadata plus one
198 /// size-stat per live file; it never reads a data block. The default
199 /// implementation reports [`StorageStatus::Healthy`](crate::StorageStatus::Healthy);
200 /// the standard tree overrides it to report
201 /// [`StorageStatus::CompactionInProgress`](crate::StorageStatus::CompactionInProgress) while a
202 /// compaction runs.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// # use lsm_tree::Error as TreeError;
208 /// use lsm_tree::{AbstractTree, Config};
209 ///
210 /// let folder = tempfile::tempdir()?;
211 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
212 ///
213 /// for i in 0..100u64 {
214 /// tree.insert(format!("key{i:04}"), "value", i);
215 /// }
216 /// tree.flush_active_memtable(0)?;
217 ///
218 /// let stats = tree.storage_stats()?;
219 /// assert_eq!(stats.item_count, 100);
220 /// // Roughly how many more average-shaped entries fit in another 1 MiB.
221 /// let _headroom = stats.estimated_remaining_entries(1024 * 1024);
222 /// #
223 /// # Ok::<(), TreeError>(())
224 /// ```
225 ///
226 /// # Errors
227 ///
228 /// Returns an error if a live file's size cannot be stat-ed.
229 fn storage_stats(&self) -> crate::Result<crate::StorageStats> {
230 crate::storage_stats::compute_storage_stats(&self.current_version(), false, true)
231 }
232
233 /// Per-LSM-level and per-segment size + entry-count stats, for tiering and
234 /// erasure-coding placement decisions (which level / segment is large enough
235 /// to demote, EC-encode, or migrate).
236 ///
237 /// Cheap: derived from the live version's metadata plus one file-size stat
238 /// per segment (no data-block scan). The per-level totals reconcile with
239 /// [`storage_stats`](Self::storage_stats): summed across levels they equal
240 /// the SST portion of [`StorageStats::used_bytes`](crate::StorageStats::used_bytes)
241 /// and [`StorageStats::item_count`](crate::StorageStats::item_count).
242 ///
243 /// # Examples
244 ///
245 /// ```
246 /// # use lsm_tree::Error as TreeError;
247 /// use lsm_tree::{AbstractTree, Config};
248 ///
249 /// let folder = tempfile::tempdir()?;
250 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
251 /// for i in 0..100u32 {
252 /// tree.insert(format!("k{i:04}"), "v", 0);
253 /// }
254 /// tree.flush_active_memtable(0)?;
255 ///
256 /// let levels = tree.level_segment_stats()?;
257 /// let total: u64 = levels.iter().map(|l| l.item_count).sum();
258 /// assert_eq!(total, tree.storage_stats()?.item_count);
259 /// #
260 /// # Ok::<(), TreeError>(())
261 /// ```
262 ///
263 /// # Errors
264 ///
265 /// Returns an error if a segment's file size cannot be stat-ed.
266 fn level_segment_stats(&self) -> crate::Result<Vec<crate::LevelStats>> {
267 crate::storage_stats::compute_level_segment_stats(&self.current_version())
268 }
269
270 /// Estimated bytes pending compaction under `strategy`: on-disk data above
271 /// its level's target that must eventually be rewritten downward (a `RocksDB`
272 /// `estimate-pending-compaction-bytes` analog), a compaction-debt signal for a
273 /// scheduler / tiering consumer.
274 ///
275 /// The strategy is supplied by the caller because the engine does not own a
276 /// configured compaction strategy (it is injected per compaction run); a
277 /// `&dyn` keeps this object-safe. Returns `0` for strategies without a
278 /// size-target notion of debt (FIFO, drop-range), or when the tree is at or
279 /// below its target shape. See
280 /// [`CompactionStrategy::pending_compaction_bytes`](crate::compaction::CompactionStrategy::pending_compaction_bytes).
281 fn compaction_debt(&self, strategy: &dyn crate::compaction::CompactionStrategy) -> u64 {
282 strategy.pending_compaction_bytes(&self.current_version())
283 }
284
285 /// Computed write-backpressure verdict from the live L0 table count and the
286 /// strategy's pending-compaction bytes, against the configured
287 /// [`RuntimeConfig::backpressure`](crate::runtime_config::RuntimeConfig)
288 /// thresholds.
289 ///
290 /// Advisory, mirroring [`write_admission`](Self::write_admission): the caller
291 /// consults it and throttles in its own write loop (sleep `suggested_delay`
292 /// at [`Slowdown`](crate::Backpressure::Slowdown), pause at
293 /// [`Stop`](crate::Backpressure::Stop)). The engine never blocks on it,
294 /// because it does not own the compaction that drains the debt — an internal
295 /// stall could deadlock the very thread that would compact.
296 ///
297 /// The strategy is supplied by the caller for the same reason as
298 /// [`compaction_debt`](Self::compaction_debt). Returns
299 /// [`Backpressure::None`](crate::Backpressure::None) by default (no
300 /// thresholds configured).
301 fn write_backpressure(
302 &self,
303 _strategy: &dyn crate::compaction::CompactionStrategy,
304 ) -> crate::Backpressure {
305 crate::Backpressure::None
306 }
307
308 /// Storage admission gate: `Ok(())` if a write may proceed, or
309 /// [`Error::StorageFull`](crate::Error::StorageFull) if the tree is
310 /// over budget and should be treated as read-only.
311 ///
312 /// Opt-in: returns `Ok(())` unless
313 /// [`storage_admission_check`](crate::runtime_config::RuntimeConfig::storage_admission_check)
314 /// is enabled. The predicate is computed (not latched), so once space is
315 /// freed — the budget raised, a compaction reclaiming space, or disk freed —
316 /// the next call admits again with no restart.
317 ///
318 /// Intended as a cheap pre-check the caller consults before applying a
319 /// write batch. The footprint is cached per version, so the check is a
320 /// constant-time read on the common path and only re-measures when a new
321 /// version is installed (flush / compaction). Internal flush / compaction
322 /// are never gated, so the engine can always reclaim.
323 ///
324 /// # Errors
325 ///
326 /// [`Error::StorageFull`](crate::Error::StorageFull) when the live footprint
327 /// plus reserved headroom exceeds the effective budget.
328 fn write_admission(&self) -> crate::Result<()> {
329 Ok(())
330 }
331
332 /// `true` when the admission gate is currently closed (see
333 /// [`write_admission`](Self::write_admission)). Convenience for callers that
334 /// want a boolean rather than a `Result`. Always `false` unless admission
335 /// control is enabled and the tree is over budget.
336 ///
337 /// Only [`Error::StorageFull`](crate::Error::StorageFull) counts as
338 /// read-only: an unrelated admission error (e.g. an I/O failure while
339 /// measuring the footprint) is NOT an out-of-space condition and must not be
340 /// reported as one.
341 fn is_read_only(&self) -> bool {
342 matches!(
343 self.write_admission(),
344 Err(crate::Error::StorageFull { .. })
345 )
346 }
347
348 /// Proactively verifies every block's XXH3 checksum across every SST in
349 /// the tree's current version — a scrubber for catching bit rot before it
350 /// surfaces as a user-visible read failure (cron / scrub jobs).
351 ///
352 /// Reports at block granularity and never aborts early. The returned
353 /// [`BlockVerifyReport`](crate::verify::BlockVerifyReport) records
354 /// block-corruption findings with `(file, offset)`, while file-level errors
355 /// (e.g. [`BlockVerifyError::SstFileUnreadable`](crate::verify::BlockVerifyError::SstFileUnreadable))
356 /// carry the file only (no offset). It does not surface per-entry indices or
357 /// ECC-correction counts (when ECC-at-rest is enabled a within-budget corrupt
358 /// block may still be healed on read as a side effect of the scan, but the
359 /// report does not tally corrections).
360 ///
361 /// Filesystems with native per-block integrity (ZFS, Btrfs, `ReFS`, S3 —
362 /// see [`Fs::capabilities`](crate::fs::Fs::capabilities)) already detect
363 /// corruption on read; this scrub is the portable check for the rest.
364 ///
365 /// Use [`Self::verify_checksum_with`] for parallelism / throttle control.
366 #[cfg(feature = "std")]
367 fn verify_checksum(&self) -> crate::verify::BlockVerifyReport
368 where
369 Self: Sized,
370 {
371 crate::verify::verify_block_checksums(self)
372 }
373
374 /// Like [`Self::verify_checksum`] but with configurable parallelism and
375 /// I/O throttle (see [`VerifyOptions`](crate::verify::VerifyOptions)).
376 #[cfg(feature = "std")]
377 fn verify_checksum_with(
378 &self,
379 options: &crate::verify::VerifyOptions,
380 ) -> crate::verify::BlockVerifyReport
381 where
382 Self: Sized,
383 {
384 crate::verify::verify_block_checksums_with(self, options)
385 }
386
387 #[doc(hidden)]
388 fn get_version_history_lock(&self) -> VersionsWriteGuard<'_>;
389
390 /// Creates a hard-linked checkpoint of the tree's on-disk state in
391 /// `target_path` for point-in-time recovery (PITR) backup.
392 ///
393 /// The checkpoint is a fully functional tree that can be opened
394 /// independently via [`Config::open`](crate::Config::open). For the
395 /// common single-filesystem case all SST files (and blob files, for
396 /// [`BlobTree`]) are hard-linked rather than copied, so the operation
397 /// is O(1) per file and consumes zero additional disk space until the
398 /// original files are compacted away — at which point the inode is
399 /// kept alive by the checkpoint link.
400 ///
401 /// # Cross-filesystem / cross-backend fall-back
402 ///
403 /// When a source file lives on a different filesystem than the
404 /// checkpoint target — e.g. an SST routed to a hot tier via
405 /// [`level_routes`](crate::Config::level_routes) on a separate volume,
406 /// or a backup directory on a foreign mount — the hard link cannot
407 /// be created (Unix `EXDEV`). In that case the checkpoint silently
408 /// falls back to a streamed byte copy, which:
409 ///
410 /// - takes time linear in the file size instead of O(1), and
411 /// - consumes disk space equal to the copied bytes on the target
412 /// volume (no inode sharing across filesystems).
413 ///
414 /// Each fall-back call emits one [`log::debug`] line (deliberately not
415 /// `warn`: a misconfigured tier could trigger this path once per SST
416 /// and per blob — thousands of times per snapshot — and per-file
417 /// warnings would drown real signal). Operators wanting hard-visibility
418 /// of unexpected full copies should enable debug logging on the `fs`
419 /// module or watch the `CheckpointInfo.total_bytes` figure (≫ inode
420 /// link cost means the fallback fired). The same `debug` policy applies
421 /// when source and target use entirely different [`Fs`](crate::fs::Fs)
422 /// backends (e.g. [`MemFs`](crate::fs::MemFs) → [`StdFs`](crate::fs::StdFs)
423 /// in tests).
424 ///
425 /// # Concurrency
426 ///
427 /// While the checkpoint is being built, compaction continues normally
428 /// but the physical removal of obsolete files is deferred until the
429 /// checkpoint hard-links are in place. This is implemented by an
430 /// internal reference-counted deletion gate; callers do not have to
431 /// pause compaction themselves.
432 ///
433 /// # Errors
434 ///
435 /// Returns an error if:
436 /// - the active memtable could not be flushed,
437 /// - `target_path` already exists (to prevent accidental overwrites),
438 /// - a hard link / copy fall-back could not be created, or
439 /// - the manifest / version pointer files could not be replicated.
440 ///
441 /// On error any partial checkpoint files are removed automatically
442 /// (best-effort) so callers can safely retry against the same path.
443 // std-only: checkpoint creation hard-links / copies files via std::fs.
444 #[cfg(feature = "std")]
445 fn create_checkpoint(&self, target_path: &crate::path::Path) -> crate::Result<CheckpointInfo>;
446
447 /// Seals the active memtable and flushes to table(s).
448 ///
449 /// If there are already other sealed memtables lined up, those will be flushed as well.
450 ///
451 /// Only used in tests.
452 #[doc(hidden)]
453 fn flush_active_memtable(&self, eviction_seqno: SeqNo) -> crate::Result<()> {
454 let lock = self.get_flush_lock();
455 self.rotate_memtable();
456 self.flush(&lock, eviction_seqno)?;
457 Ok(())
458 }
459
460 /// Synchronously flushes pending sealed memtables to tables.
461 ///
462 /// Returns the sum of flushed memtable sizes that were flushed.
463 ///
464 /// The function may not return a result, if nothing was flushed.
465 ///
466 /// # Errors
467 ///
468 /// Returns `Err` on an I/O error, or on a memtable residence-verification
469 /// failure under [`KvChecksumComputePoint::AtInsert`](crate::runtime_config::KvChecksumComputePoint::AtInsert):
470 /// a sealed memtable whose insert-time per-KV digests do not verify before
471 /// flush surfaces [`crate::Error::MemtableKvChecksumMismatch`],
472 /// [`crate::Error::MemtableKvChecksumCorruptAlgorithm`], or
473 /// [`crate::Error::InvalidTag`] (corrupt `value_type`).
474 fn flush(&self, _lock: &FlushGuard<'_>, seqno_threshold: SeqNo) -> crate::Result<Option<u64>> {
475 use crate::{
476 compaction::stream::CompactionStream, merge::Merger, range_tombstone::RangeTombstone,
477 };
478
479 let version_history = self.get_version_history_lock();
480 let latest = version_history.latest_version();
481
482 if latest.sealed_memtables.len() == 0 {
483 return Ok(None);
484 }
485
486 let sealed_ids = latest
487 .sealed_memtables
488 .iter()
489 .map(|mt| mt.id)
490 .collect::<Vec<_>>();
491
492 let flushed_size = latest.sealed_memtables.iter().map(|mt| mt.size()).sum();
493
494 // AtInsert residence check: verify each sealed memtable's insert-time
495 // per-KV digests against a recompute over the entries' current bytes
496 // before writing them out. A divergence means an entry was corrupted
497 // (a RAM bit-flip) while it sat in the memtable. Memtables with no
498 // insert digests (the default) return immediately without walking.
499 //
500 // This is a SEPARATE pass over the as-inserted memtable entries, not
501 // fused into the writer's per-KV footer encode, and deliberately so:
502 // the footer digest is computed over POST-merge / post-seqno-filter
503 // bytes (the CompactionStream below applies the merge operator), so a
504 // merge operator's combined value differs from any single inserted
505 // value. Comparing the carried insert digest against the footer digest
506 // would false-positive on every legitimate merge. Residence corruption
507 // is a property of what was inserted (pre-merge); it must be checked
508 // here, against the raw memtable entries.
509 for mt in latest.sealed_memtables.iter() {
510 mt.verify_kv_residence()?;
511 }
512
513 // Collect range tombstones from sealed memtables
514 let mut range_tombstones: Vec<RangeTombstone> = Vec::new();
515 for mt in latest.sealed_memtables.iter() {
516 range_tombstones.extend(mt.range_tombstones_sorted());
517 }
518 range_tombstones
519 .sort_by(|a, b| a.cmp_with_comparator(b, self.tree_config().comparator.as_ref()));
520 range_tombstones.dedup();
521
522 let merger = Merger::new(
523 latest
524 .sealed_memtables
525 .iter()
526 .map(|mt| mt.iter().map(Ok))
527 .collect::<Vec<_>>(),
528 self.tree_config().comparator.clone(),
529 );
530 // RT suppression is not needed here: flush writes both entries and RTs
531 // to the output tables. Suppression happens at read time, not write time.
532 let stream = CompactionStream::new(merger, seqno_threshold)
533 .with_merge_operator(self.tree_config().merge_operator.clone());
534
535 drop(version_history);
536
537 // Clone needed: flush_to_tables_with_rt consumes the Vec, but on the
538 // RT-only path (no KV data, tables.is_empty()) we re-insert RTs into the
539 // active memtable. Flush is infrequent and RT count is small.
540 if let Some((tables, blob_files)) =
541 self.flush_to_tables_with_rt(stream, range_tombstones.clone())?
542 {
543 // If no tables were produced (RT-only memtable), re-insert RTs
544 // into active memtable so they aren't lost
545 if tables.is_empty() && !range_tombstones.is_empty() {
546 let active = self.active_memtable();
547 for rt in &range_tombstones {
548 let _ =
549 active.insert_range_tombstone(rt.start.clone(), rt.end.clone(), rt.seqno);
550 }
551 }
552
553 self.register_tables(
554 &tables,
555 blob_files.as_deref(),
556 None,
557 &sealed_ids,
558 seqno_threshold,
559 )?;
560 }
561
562 Ok(Some(flushed_size))
563 }
564
565 /// Returns an iterator that scans through the entire tree.
566 ///
567 /// Avoid using this function, or limit it as otherwise it may scan a lot of items.
568 ///
569 /// A snapshot the history no longer retains (see
570 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
571 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
572 /// as the iterator's first and only item.
573 fn iter(
574 &self,
575 seqno: SeqNo,
576 index: Option<(Arc<Memtable>, SeqNo)>,
577 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
578 self.range::<&[u8], _>(.., seqno, index)
579 }
580
581 /// Returns an iterator over a prefixed set of items.
582 ///
583 /// Avoid using an empty prefix as it may scan a lot of items (unless limited).
584 ///
585 /// A snapshot the history no longer retains (see
586 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
587 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
588 /// as the iterator's first and only item.
589 fn prefix<K: AsRef<[u8]>>(
590 &self,
591 prefix: K,
592 seqno: SeqNo,
593 index: Option<(Arc<Memtable>, SeqNo)>,
594 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static>;
595
596 /// Returns an iterator over a range of items.
597 ///
598 /// Avoid using full or unbounded ranges as they may scan a lot of items (unless limited).
599 ///
600 /// A snapshot the history no longer retains (see
601 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
602 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
603 /// as the iterator's first and only item.
604 fn range<K: AsRef<[u8]>, R: RangeBounds<K>>(
605 &self,
606 range: R,
607 seqno: SeqNo,
608 index: Option<(Arc<Memtable>, SeqNo)>,
609 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static>;
610
611 /// Returns a range iterator that can reposition (seek) in place without
612 /// reopening its per-SST readers.
613 ///
614 /// Unlike [`range`](Self::range)'s boxed `DoubleEndedIterator`, the returned
615 /// [`SeekableGuardIter`] additionally exposes `seek_to` / `seek_to_for_prev`,
616 /// so a consumer can jump a live iterator to any key (`RocksDB` `Seek` /
617 /// `SeekForPrev`) — enabling data-dependent scans (joins, skip-scan) without
618 /// reopening per-SST readers per jump.
619 ///
620 /// A snapshot the history no longer retains (see
621 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
622 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
623 /// as the iterator's first and only item (also through `peek_key`); seeks
624 /// on such an iterator are no-ops.
625 fn range_seekable<K: AsRef<[u8]>, R: RangeBounds<K>>(
626 &self,
627 range: R,
628 seqno: SeqNo,
629 index: Option<(Arc<Memtable>, SeqNo)>,
630 ) -> Box<dyn SeekableGuardIter + 'static>;
631
632 /// Scans a sequence of disjoint, ascending key sub-intervals, reusing one set
633 /// of per-SST readers across all of them.
634 ///
635 /// The per-SST setup is paid once (when the underlying seekable iterator is
636 /// opened); each interval is served by repositioning that iterator. This
637 /// amortizes the setup across `N` intervals instead of paying it per
638 /// interval, so multi-interval scan throughput scales with the total rows
639 /// returned rather than the interval count.
640 ///
641 /// The interval source is pulled lazily, so intervals may be produced on
642 /// demand (e.g. computed from rows already returned).
643 ///
644 /// A snapshot the history no longer retains (see
645 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
646 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
647 /// as the iterator's first and only item.
648 fn batch_range_scan<K: AsRef<[u8]>, R: RangeBounds<K> + 'static, I: IntoIterator<Item = R>>(
649 &self,
650 intervals: I,
651 seqno: SeqNo,
652 index: Option<(Arc<Memtable>, SeqNo)>,
653 ) -> Box<dyn Iterator<Item = IterGuardImpl> + Send + 'static>
654 where
655 I::IntoIter: Send + 'static;
656
657 /// Returns the approximate number of tombstones in the tree.
658 fn tombstone_count(&self) -> u64;
659
660 /// Returns the approximate number of weak tombstones (single deletes) in the tree.
661 fn weak_tombstone_count(&self) -> u64;
662
663 /// Returns the approximate number of values reclaimable once weak tombstones can be GC'd.
664 fn weak_tombstone_reclaimable_count(&self) -> u64;
665
666 /// Drops tables that are fully contained in a given range.
667 ///
668 /// Accepts any `RangeBounds`, including unbounded or exclusive endpoints.
669 /// If the normalized lower bound is greater than the upper bound, the
670 /// method returns without performing any work.
671 ///
672 /// # Errors
673 ///
674 /// Will return `Err` only if an IO error occurs.
675 fn drop_range<K: AsRef<[u8]>, R: RangeBounds<K>>(&self, range: R) -> crate::Result<()>;
676
677 /// Drops all tables and clears all memtables atomically.
678 ///
679 /// # Errors
680 ///
681 /// Will return `Err` only if an IO error occurs.
682 fn clear(&self) -> crate::Result<()>;
683
684 /// Performs major compaction, blocking the caller until it's done.
685 ///
686 /// Returns a [`crate::compaction::CompactionResult`] describing what action was taken.
687 ///
688 /// # Garbage-collection / merge-fold watermark (`seqno_threshold`)
689 ///
690 /// `seqno_threshold` is the MVCC garbage-collection watermark: the engine may
691 /// collapse history that no snapshot reading at a seqno `< seqno_threshold`
692 /// can still observe. Concretely, only entries whose seqno is `< seqno_threshold`
693 /// are eligible for:
694 ///
695 /// - dropping shadowed versions / GC-ing tombstones, and
696 /// - **folding merge operands** via the [`crate::MergeOperator`]: a key written
697 /// only through [`Self::merge`] (no base value) accumulates one operand per
698 /// call, and reads re-apply the whole chain (`O(operands)` per read) until
699 /// compaction folds it. Folding a chain into a single value is only
700 /// MVCC-safe when no live snapshot reads *between* the operands, which is
701 /// exactly what `seqno_threshold` certifies.
702 ///
703 /// The engine does **not** track snapshots (unlike a `RocksDB`-style
704 /// snapshot list); the caller owns snapshot lifecycle and must supply this
705 /// watermark:
706 ///
707 /// - To fold/GC everything (no active snapshots), pass a value **above every
708 /// live seqno** (e.g. the next value from the [`crate::SequenceNumberCounter`]).
709 /// - With outstanding snapshots, pass the **oldest** snapshot's seqno so their
710 /// reads stay correct.
711 /// - `seqno_threshold == 0` certifies nothing as collapsible, so **no folding
712 /// or GC happens** — `major_compact(target, 0)` only restructures tables and
713 /// leaves a merge-only key's full operand chain intact.
714 ///
715 /// The same watermark prunes the version history: every version older
716 /// than the newest one installed below `seqno_threshold` is released
717 /// (and the tables only those versions referenced become deletable).
718 /// Afterwards a snapshot at or below the oldest retained version's seqno
719 /// (see [`oldest_retained_seqno`](Self::oldest_retained_seqno)) can no
720 /// longer be read and fails with
721 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention),
722 /// which is why the watermark must not exceed the oldest snapshot still in
723 /// use.
724 ///
725 /// # Errors
726 ///
727 /// Will return `Err` if an IO error occurs.
728 fn major_compact(
729 &self,
730 target_size: u64,
731 seqno_threshold: SeqNo,
732 ) -> crate::Result<crate::compaction::CompactionResult>;
733
734 /// Returns the disk space used by stale blobs.
735 fn stale_blob_bytes(&self) -> u64 {
736 0
737 }
738
739 /// Gets the space usage of all filters in the tree.
740 ///
741 /// May not correspond to the actual memory size because filter blocks may be paged out.
742 fn filter_size(&self) -> u64;
743
744 /// Gets the memory usage of all pinned filters in the tree.
745 fn pinned_filter_size(&self) -> usize;
746
747 /// Gets the memory usage of all pinned index blocks in the tree.
748 fn pinned_block_index_size(&self) -> usize;
749
750 /// Gets the length of the version free list.
751 fn version_free_list_len(&self) -> usize;
752
753 /// Returns the metrics structure.
754 #[cfg(feature = "metrics")]
755 fn metrics(&self) -> &Arc<crate::Metrics>;
756
757 /// A point-in-time [`CacheStats`](crate::CacheStats) snapshot of block-cache
758 /// effectiveness (cumulative hit / miss counts and rate) and occupancy
759 /// (current size against capacity).
760 ///
761 /// The stable, owned observability view over the block cache, so a consumer
762 /// can read cache health without holding the mutable
763 /// [`metrics`](Self::metrics) handle. Counts are cumulative since process
764 /// start; derive a rate over an interval from the delta between two polls.
765 #[cfg(feature = "metrics")]
766 fn cache_stats(&self) -> crate::CacheStats;
767
768 /// Acquires the flush lock which is required to call [`Tree::flush`].
769 fn get_flush_lock(&self) -> FlushGuard<'_>;
770
771 /// Synchronously flushes a memtable to a table.
772 ///
773 /// This method will not make the table immediately available,
774 /// use [`AbstractTree::register_tables`] for that.
775 ///
776 /// # Errors
777 ///
778 /// Will return `Err` if an IO error occurs.
779 fn flush_to_tables(
780 &self,
781 stream: impl Iterator<Item = crate::Result<InternalValue>>,
782 ) -> crate::Result<Option<FlushToTablesResult>> {
783 self.flush_to_tables_with_rt(stream, Vec::new())
784 }
785
786 /// Like [`AbstractTree::flush_to_tables`], but also writes range tombstones.
787 ///
788 /// This is an internal extension hook on the crate's sealed tree types and
789 /// is hidden from generated documentation.
790 ///
791 /// # Errors
792 ///
793 /// Will return `Err` if an IO error occurs.
794 #[doc(hidden)]
795 fn flush_to_tables_with_rt(
796 &self,
797 stream: impl Iterator<Item = crate::Result<InternalValue>>,
798 range_tombstones: Vec<crate::range_tombstone::RangeTombstone>,
799 ) -> crate::Result<Option<FlushToTablesResult>>;
800
801 /// Atomically registers flushed tables into the tree, removing their associated sealed memtables.
802 ///
803 /// # Errors
804 ///
805 /// Will return `Err` if an IO error occurs.
806 fn register_tables(
807 &self,
808 tables: &[Table],
809 blob_files: Option<&[BlobFile]>,
810 frag_map: Option<crate::blob_tree::FragmentationMap>,
811 sealed_memtables_to_delete: &[crate::tree::inner::MemtableId],
812 gc_watermark: SeqNo,
813 ) -> crate::Result<()>;
814
815 /// Clears the active memtable atomically.
816 fn clear_active_memtable(&self);
817
818 /// Returns the number of sealed memtables.
819 fn sealed_memtable_count(&self) -> usize;
820
821 /// Performs compaction on the tree's levels, blocking the caller until it's done.
822 ///
823 /// Returns a [`crate::compaction::CompactionResult`] describing what action was taken.
824 ///
825 /// # Errors
826 ///
827 /// Will return `Err` if an IO error occurs.
828 fn compact(
829 &self,
830 strategy: Arc<dyn crate::compaction::CompactionStrategy>,
831 seqno_threshold: SeqNo,
832 ) -> crate::Result<crate::compaction::CompactionResult>;
833
834 /// Returns the next table's ID.
835 fn get_next_table_id(&self) -> TableId;
836
837 /// Returns the tree config.
838 fn tree_config(&self) -> &Config;
839
840 /// Returns the highest sequence number.
841 fn get_highest_seqno(&self) -> Option<SeqNo> {
842 let memtable_seqno = self.get_highest_memtable_seqno();
843 let table_seqno = self.get_highest_persisted_seqno();
844 memtable_seqno.max(table_seqno)
845 }
846
847 /// Returns the active memtable.
848 fn active_memtable(&self) -> Arc<Memtable>;
849
850 /// Returns the tree type.
851 fn tree_type(&self) -> crate::TreeType {
852 if self.tree_config().kv_separation_opts.is_some() {
853 crate::TreeType::Blob
854 } else {
855 crate::TreeType::Standard
856 }
857 }
858
859 /// Seals the active memtable.
860 fn rotate_memtable(&self) -> Option<Arc<Memtable>>;
861
862 /// Returns the number of tables currently in the tree.
863 fn table_count(&self) -> usize;
864
865 /// Returns the number of tables in `levels[idx]`.
866 ///
867 /// Returns `None` if the level does not exist (if idx >= 7).
868 fn level_table_count(&self, idx: usize) -> Option<usize>;
869
870 /// Returns the number of disjoint runs in L0.
871 ///
872 /// Can be used to determine whether to write stall.
873 fn l0_run_count(&self) -> usize;
874
875 /// Returns the number of blob files currently in the tree.
876 fn blob_file_count(&self) -> usize;
877
878 /// Approximates the number of items in the tree.
879 fn approximate_len(&self) -> usize;
880
881 /// Returns the disk space usage.
882 fn disk_space(&self) -> u64;
883
884 /// Estimates the on-disk bytes and entry count contained in `range` at
885 /// `seqno`, WITHOUT reading any data block.
886 ///
887 /// The estimate interpolates each overlapping SST's data-block offsets at
888 /// the range boundaries (block granularity) and adds the active + sealed
889 /// memtables' in-range share. For a KV-separated tree the per-SST blob
890 /// bytes are apportioned by the same in-range fraction (blob files are not
891 /// key-indexed, so a finer estimate is impossible without reading data).
892 /// Intended for query planning (split-point selection, cost-based join
893 /// ordering), not exact accounting; accuracy is typically within ~10-15%
894 /// on roughly-uniform data.
895 ///
896 /// # Examples
897 ///
898 /// ```
899 /// # use lsm_tree::Error as TreeError;
900 /// use lsm_tree::{AbstractTree, Config};
901 ///
902 /// let folder = tempfile::tempdir()?;
903 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
904 /// for i in 0..100u32 {
905 /// tree.insert(format!("k{i:04}"), "value", 0);
906 /// }
907 /// tree.flush_active_memtable(0)?;
908 ///
909 /// // The full range covers every entry exactly once it is all flushed —
910 /// // estimated without reading a single data block.
911 /// let all = tree.approximate_range_stats::<&str, _>(.., 1)?;
912 /// assert_eq!(all.key_count, tree.approximate_len() as u64);
913 /// assert!(all.bytes > 0);
914 ///
915 /// // A range past every key is empty.
916 /// let none = tree.approximate_range_stats("zzzz".."zzzzz", 1)?;
917 /// assert_eq!(none.key_count, 0);
918 /// assert_eq!(none.bytes, 0);
919 /// #
920 /// # Ok::<(), TreeError>(())
921 /// ```
922 ///
923 /// # Errors
924 ///
925 /// Returns an error if a block index or table metadata read fails.
926 fn approximate_range_stats<K: AsRef<[u8]>, R: RangeBounds<K>>(
927 &self,
928 range: R,
929 seqno: SeqNo,
930 ) -> crate::Result<crate::ApproximateRangeStats>;
931
932 /// Estimates the row cardinality and selectivity of `range` at `seqno`,
933 /// WITHOUT reading any data block.
934 ///
935 /// Uses the per-data-block zone map (per-block row counts + key ranges) for a
936 /// block-granularity row count: every data block whose key range overlaps the
937 /// query contributes its recorded row count, plus the active + sealed
938 /// memtables' in-range counts. When a table has no zone map the row count
939 /// falls back to the byte-fraction estimate of [`approximate_range_stats`].
940 /// Selectivity is `rows / total_rows`, monotonic in predicate tightness, for
941 /// cost-based planning (join ordering, scan-vs-seek). Not exact accounting.
942 ///
943 /// [`approximate_range_stats`]: AbstractTree::approximate_range_stats
944 ///
945 /// # Examples
946 ///
947 /// ```
948 /// # use lsm_tree::Error as TreeError;
949 /// use lsm_tree::{AbstractTree, Config};
950 ///
951 /// let folder = tempfile::tempdir()?;
952 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
953 /// for i in 0..100u32 {
954 /// tree.insert(format!("k{i:04}"), "v", 0);
955 /// }
956 /// tree.flush_active_memtable(0)?;
957 ///
958 /// // The full range covers every row; selectivity is 1.0.
959 /// let all = tree.approximate_range_cardinality::<&str, _>(.., 1)?;
960 /// assert_eq!(all.rows, tree.approximate_len() as u64);
961 /// assert!((all.selectivity - 1.0).abs() < 1e-9);
962 ///
963 /// // A tighter range selects fewer rows.
964 /// let part = tree.approximate_range_cardinality("k0000".."k0050", 1)?;
965 /// assert!(part.selectivity <= all.selectivity);
966 /// #
967 /// # Ok::<(), TreeError>(())
968 /// ```
969 ///
970 /// # Errors
971 ///
972 /// Returns an error if a block index, zone map, or table metadata read fails.
973 fn approximate_range_cardinality<K: AsRef<[u8]>, R: RangeBounds<K>>(
974 &self,
975 range: R,
976 seqno: SeqNo,
977 ) -> crate::Result<crate::RangeCardinality>;
978
979 /// Returns the highest sequence number of the active memtable.
980 fn get_highest_memtable_seqno(&self) -> Option<SeqNo>;
981
982 /// Returns the highest sequence number that is flushed to disk.
983 fn get_highest_persisted_seqno(&self) -> Option<SeqNo>;
984
985 /// Returns the seqno of the oldest version the history still retains:
986 /// the lower bound of the readable snapshot window.
987 ///
988 /// A read at snapshot `seqno` is served from the newest retained version
989 /// installed below it, so a snapshot is servable iff it is `0` (sees
990 /// nothing from any version) or strictly above this seqno; a read at
991 /// `0 < seqno <= oldest_retained_seqno()` fails with
992 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention).
993 /// The boundary advances when [`major_compact`](Self::major_compact)
994 /// prunes the history up to its `seqno_threshold` and when
995 /// [`clear`](Self::clear) drains it, so a caller holding a long-lived
996 /// snapshot (a lagging consumer, a point-in-time query) can validate it
997 /// here before reading and report "history collected" instead of an
998 /// unexpected error mid-scan. A fresh tree reports `0`.
999 ///
1000 /// The boundary survives a reopen. The install that discards what older
1001 /// snapshots saw records it in the same version edit: after a GC
1002 /// compaction with watermark `w` the reopened boundary is `w - 1`, capped
1003 /// at the compaction's own install seqno (the retained pre-compaction
1004 /// version that served reads between the live front and `w` does not
1005 /// survive a restart), after a `clear`, a table drop or a compaction
1006 /// whose filter transformed rows it is that install's seqno. A manifest rebuilt by
1007 /// [`Config::repair`](crate::Config::repair) cannot know what the lost
1008 /// manifest recorded and seeds the boundary from
1009 /// [`Config::repair_retention_floor`](crate::Config::repair_retention_floor)
1010 /// (default `0`: every snapshot served).
1011 ///
1012 /// # Examples
1013 ///
1014 /// ```
1015 /// # let folder = tempfile::tempdir()?;
1016 /// use lsm_tree::{AbstractTree, Config, Error, SequenceNumberCounter};
1017 ///
1018 /// let seqno = SequenceNumberCounter::default();
1019 /// let tree = Config::new(folder, seqno.clone(), Default::default()).open()?;
1020 /// assert_eq!(tree.oldest_retained_seqno(), 0);
1021 ///
1022 /// tree.insert("a", "v1", seqno.next());
1023 /// tree.flush_active_memtable(0)?;
1024 /// let stale = seqno.get();
1025 /// tree.insert("a", "v2", seqno.next());
1026 /// tree.flush_active_memtable(0)?;
1027 ///
1028 /// // A watermark above every live snapshot lets compaction prune the
1029 /// // history; the boundary moves past the stale snapshot.
1030 /// tree.major_compact(u64::MAX, seqno.get())?;
1031 /// let oldest = tree.oldest_retained_seqno();
1032 /// assert!(stale <= oldest);
1033 /// assert!(matches!(
1034 /// tree.get("a", stale),
1035 /// Err(Error::SnapshotBelowRetention { .. })
1036 /// ));
1037 /// assert_eq!(tree.get("a", oldest + 1)?.as_deref(), Some(b"v2".as_slice()));
1038 /// #
1039 /// # Ok::<(), lsm_tree::Error>(())
1040 /// ```
1041 fn oldest_retained_seqno(&self) -> SeqNo;
1042
1043 /// Returns the PERSISTED retention boundary: the highest snapshot seqno
1044 /// the tree will refuse after a reopen.
1045 ///
1046 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno) is the LIVE
1047 /// boundary, which the in-memory version history may hold below this
1048 /// one: a table drop with no GC watermark keeps its pre-drop version
1049 /// retained (and serving) until the restart, while the manifest already
1050 /// records the drop's install seqno here. The two agree after a reopen.
1051 ///
1052 /// This is the value a deployment records for a later manifest repair
1053 /// ([`Config::repair_retention_floor`](crate::Config::repair_retention_floor)):
1054 /// it already folds in every operation that advanced the boundary (GC
1055 /// compactions, `clear`, table drops, filtering compactions), so no
1056 /// caller-side bookkeeping of watermarks is needed. `0` until the first
1057 /// such install.
1058 fn retention_floor(&self) -> SeqNo;
1059
1060 /// Scans the entire tree, returning the number of items.
1061 ///
1062 /// ###### Caution
1063 ///
1064 /// This operation scans the entire tree: O(n) complexity!
1065 ///
1066 /// Never, under any circumstances, use .`len()` == 0 to check
1067 /// if the tree is empty, use [`Tree::is_empty`] instead.
1068 ///
1069 /// # Examples
1070 ///
1071 /// ```
1072 /// # use lsm_tree::Error as TreeError;
1073 /// use lsm_tree::{AbstractTree, Config, Tree};
1074 ///
1075 /// let folder = tempfile::tempdir()?;
1076 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1077 ///
1078 /// assert_eq!(tree.len(0, None)?, 0);
1079 /// tree.insert("1", "abc", 0);
1080 /// tree.insert("3", "abc", 1);
1081 /// tree.insert("5", "abc", 2);
1082 /// assert_eq!(tree.len(3, None)?, 3);
1083 /// #
1084 /// # Ok::<(), TreeError>(())
1085 /// ```
1086 ///
1087 /// # Errors
1088 ///
1089 /// Will return `Err` if an IO error occurs.
1090 fn len(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<usize> {
1091 let mut count = 0;
1092
1093 for item in self.iter(seqno, index) {
1094 let _ = item.key()?;
1095 count += 1;
1096 }
1097
1098 Ok(count)
1099 }
1100
1101 /// Returns `true` if the tree is empty.
1102 ///
1103 /// This operation has O(log N) complexity.
1104 ///
1105 /// # Examples
1106 ///
1107 /// ```
1108 /// # let folder = tempfile::tempdir()?;
1109 /// use lsm_tree::{AbstractTree, Config, Tree};
1110 ///
1111 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1112 /// assert!(tree.is_empty(0, None)?);
1113 ///
1114 /// tree.insert("a", "abc", 0);
1115 /// assert!(!tree.is_empty(1, None)?);
1116 /// #
1117 /// # Ok::<(), lsm_tree::Error>(())
1118 /// ```
1119 ///
1120 /// # Errors
1121 ///
1122 /// Will return `Err` if an IO error occurs.
1123 fn is_empty(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<bool> {
1124 Ok(self
1125 .first_key_value(seqno, index)
1126 .map(crate::Guard::key)
1127 .transpose()?
1128 .is_none())
1129 }
1130
1131 /// Returns the first key-value pair in the tree.
1132 /// The key in this pair is the minimum key in the tree.
1133 ///
1134 /// # Examples
1135 ///
1136 /// ```
1137 /// # use lsm_tree::Error as TreeError;
1138 /// # use lsm_tree::{AbstractTree, Config, Tree, Guard};
1139 /// #
1140 /// # let folder = tempfile::tempdir()?;
1141 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1142 ///
1143 /// tree.insert("1", "abc", 0);
1144 /// tree.insert("3", "abc", 1);
1145 /// tree.insert("5", "abc", 2);
1146 ///
1147 /// let key = tree.first_key_value(3, None).expect("item should exist").key()?;
1148 /// assert_eq!(&*key, "1".as_bytes());
1149 /// #
1150 /// # Ok::<(), TreeError>(())
1151 /// ```
1152 ///
1153 /// # Errors
1154 ///
1155 /// Will return `Err` if an IO error occurs.
1156 fn first_key_value(
1157 &self,
1158 seqno: SeqNo,
1159 index: Option<(Arc<Memtable>, SeqNo)>,
1160 ) -> Option<IterGuardImpl> {
1161 self.iter(seqno, index).next()
1162 }
1163
1164 /// Returns the last key-value pair in the tree.
1165 /// The key in this pair is the maximum key in the tree.
1166 ///
1167 /// # Examples
1168 ///
1169 /// ```
1170 /// # use lsm_tree::Error as TreeError;
1171 /// # use lsm_tree::{AbstractTree, Config, Tree, Guard};
1172 /// #
1173 /// # let folder = tempfile::tempdir()?;
1174 /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1175 /// #
1176 /// tree.insert("1", "abc", 0);
1177 /// tree.insert("3", "abc", 1);
1178 /// tree.insert("5", "abc", 2);
1179 ///
1180 /// let key = tree.last_key_value(3, None).expect("item should exist").key()?;
1181 /// assert_eq!(&*key, "5".as_bytes());
1182 /// #
1183 /// # Ok::<(), TreeError>(())
1184 /// ```
1185 ///
1186 /// # Errors
1187 ///
1188 /// Will return `Err` if an IO error occurs.
1189 fn last_key_value(
1190 &self,
1191 seqno: SeqNo,
1192 index: Option<(Arc<Memtable>, SeqNo)>,
1193 ) -> Option<IterGuardImpl> {
1194 self.iter(seqno, index).next_back()
1195 }
1196
1197 /// Returns the size of a value if it exists.
1198 ///
1199 /// # Examples
1200 ///
1201 /// ```
1202 /// # let folder = tempfile::tempdir()?;
1203 /// use lsm_tree::{AbstractTree, Config, Tree};
1204 ///
1205 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1206 /// tree.insert("a", "my_value", 0);
1207 ///
1208 /// let size = tree.size_of("a", 1)?.unwrap_or_default();
1209 /// assert_eq!("my_value".len() as u32, size);
1210 ///
1211 /// let size = tree.size_of("b", 1)?.unwrap_or_default();
1212 /// assert_eq!(0, size);
1213 /// #
1214 /// # Ok::<(), lsm_tree::Error>(())
1215 /// ```
1216 ///
1217 /// # Errors
1218 ///
1219 /// Will return `Err` if an IO error occurs.
1220 fn size_of<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<u32>>;
1221
1222 /// Retrieves an item from the tree.
1223 ///
1224 /// # Examples
1225 ///
1226 /// ```
1227 /// # let folder = tempfile::tempdir()?;
1228 /// use lsm_tree::{AbstractTree, Config, Tree};
1229 ///
1230 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1231 /// tree.insert("a", "my_value", 0);
1232 ///
1233 /// let item = tree.get("a", 1)?;
1234 /// assert_eq!(Some("my_value".as_bytes().into()), item);
1235 /// #
1236 /// # Ok::<(), lsm_tree::Error>(())
1237 /// ```
1238 ///
1239 /// # Errors
1240 ///
1241 /// Will return `Err` if an IO error occurs, or
1242 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
1243 /// when the history no longer retains a version for `seqno` (see
1244 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)). The same applies
1245 /// to every read that resolves a snapshot: [`get_pinned`](Self::get_pinned),
1246 /// [`contains_key`](Self::contains_key), [`size_of`](Self::size_of),
1247 /// [`multi_get`](Self::multi_get), [`len`](Self::len),
1248 /// [`is_empty`](Self::is_empty), [`first_key_value`](Self::first_key_value),
1249 /// [`last_key_value`](Self::last_key_value),
1250 /// [`approximate_range_stats`](Self::approximate_range_stats) and
1251 /// [`approximate_range_cardinality`](Self::approximate_range_cardinality).
1252 fn get<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<UserValue>>;
1253
1254 /// Retrieves an item from the tree as a [`PinnableSlice`](crate::PinnableSlice).
1255 ///
1256 /// When the value is backed by an on-disk data block, implementations
1257 /// may return [`PinnableSlice::Pinned`](crate::PinnableSlice::Pinned) holding a reference to that block's
1258 /// decompressed buffer (avoiding a data copy). Memtable and blob-resolved
1259 /// values use [`PinnableSlice::Owned`](crate::PinnableSlice::Owned). The default implementation always
1260 /// returns `Owned`; only [`Tree`] overrides with the pinned path.
1261 ///
1262 /// The existing [`AbstractTree::get`] method is unaffected.
1263 ///
1264 /// # Examples
1265 ///
1266 /// ```
1267 /// # let folder = tempfile::tempdir()?;
1268 /// use lsm_tree::{AbstractTree, Config, Tree};
1269 ///
1270 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1271 /// tree.insert("a", "my_value", 0);
1272 ///
1273 /// let item = tree.get_pinned("a", 1)?;
1274 /// assert_eq!(item.as_ref().map(|v| v.as_ref()), Some("my_value".as_bytes()));
1275 /// #
1276 /// # Ok::<(), lsm_tree::Error>(())
1277 /// ```
1278 ///
1279 /// # Errors
1280 ///
1281 /// Will return `Err` if an IO error occurs.
1282 fn get_pinned<K: AsRef<[u8]>>(
1283 &self,
1284 key: K,
1285 seqno: SeqNo,
1286 ) -> crate::Result<Option<crate::PinnableSlice>> {
1287 // Default: delegate to get() and wrap as Owned
1288 self.get(key, seqno)
1289 .map(|opt| opt.map(crate::PinnableSlice::owned))
1290 }
1291
1292 /// Returns `true` if the tree contains the specified key.
1293 ///
1294 /// # Examples
1295 ///
1296 /// ```
1297 /// # let folder = tempfile::tempdir()?;
1298 /// # use lsm_tree::{AbstractTree, Config, Tree};
1299 /// #
1300 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1301 /// assert!(!tree.contains_key("a", 0)?);
1302 ///
1303 /// tree.insert("a", "abc", 0);
1304 /// assert!(tree.contains_key("a", 1)?);
1305 /// #
1306 /// # Ok::<(), lsm_tree::Error>(())
1307 /// ```
1308 ///
1309 /// # Errors
1310 ///
1311 /// Will return `Err` if an IO error occurs.
1312 fn contains_key<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<bool> {
1313 self.get(key, seqno).map(|x| x.is_some())
1314 }
1315
1316 /// Returns `true` if the tree contains any key with the given prefix.
1317 ///
1318 /// This is a convenience method that checks whether the corresponding
1319 /// prefix iterator yields at least one item, while surfacing any IO
1320 /// errors via the `Result` return type. Implementations may override
1321 /// this method to provide a more efficient prefix-existence check.
1322 ///
1323 /// # Examples
1324 ///
1325 /// ```
1326 /// # let folder = tempfile::tempdir()?;
1327 /// use lsm_tree::{AbstractTree, Config, Tree};
1328 ///
1329 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1330 /// assert!(!tree.contains_prefix("abc", 0, None)?);
1331 ///
1332 /// tree.insert("abc:1", "value", 0);
1333 /// assert!(tree.contains_prefix("abc", 1, None)?);
1334 /// assert!(!tree.contains_prefix("xyz", 1, None)?);
1335 /// #
1336 /// # Ok::<(), lsm_tree::Error>(())
1337 /// ```
1338 ///
1339 /// # Errors
1340 ///
1341 /// Will return `Err` if an IO error occurs.
1342 fn contains_prefix<K: AsRef<[u8]>>(
1343 &self,
1344 prefix: K,
1345 seqno: SeqNo,
1346 index: Option<(Arc<Memtable>, SeqNo)>,
1347 ) -> crate::Result<bool> {
1348 match self.prefix(prefix, seqno, index).next() {
1349 Some(guard) => guard.key().map(|_| true),
1350 None => Ok(false),
1351 }
1352 }
1353
1354 /// Reads multiple keys from the tree.
1355 ///
1356 /// Implementations may choose to perform all lookups against a single
1357 /// version snapshot and acquire the version lock only once, which can be
1358 /// more efficient than calling [`AbstractTree::get`] in a loop. The
1359 /// default trait implementation, however, is a convenience wrapper that
1360 /// simply calls [`AbstractTree::get`] for each key and therefore does not
1361 /// guarantee a single-snapshot or single-lock acquisition. Optimized
1362 /// implementations (such as [`Tree`] and [`BlobTree`]) provide the
1363 /// single-snapshot/one-lock behavior.
1364 ///
1365 /// # Examples
1366 ///
1367 /// ```
1368 /// # let folder = tempfile::tempdir()?;
1369 /// use lsm_tree::{AbstractTree, Config, Tree};
1370 ///
1371 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1372 /// tree.insert("a", "value_a", 0);
1373 /// tree.insert("b", "value_b", 1);
1374 ///
1375 /// let results = tree.multi_get(["a", "b", "c"], 2)?;
1376 /// assert_eq!(results[0], Some("value_a".as_bytes().into()));
1377 /// assert_eq!(results[1], Some("value_b".as_bytes().into()));
1378 /// assert_eq!(results[2], None);
1379 /// #
1380 /// # Ok::<(), lsm_tree::Error>(())
1381 /// ```
1382 ///
1383 /// # Errors
1384 ///
1385 /// Will return `Err` if an IO error occurs.
1386 fn multi_get<K: AsRef<[u8]>>(
1387 &self,
1388 keys: impl IntoIterator<Item = K>,
1389 seqno: SeqNo,
1390 ) -> crate::Result<Vec<Option<UserValue>>> {
1391 keys.into_iter().map(|key| self.get(key, seqno)).collect()
1392 }
1393
1394 /// Applies a [`WriteBatch`](crate::WriteBatch) with the given sequence number.
1395 ///
1396 /// All entries share a single seqno. This is more efficient than individual
1397 /// writes because the version-history lock and memtable size accounting
1398 /// are performed only once for the entire batch.
1399 ///
1400 /// **Visibility:** entries become individually visible to concurrent readers
1401 /// as they are inserted. For atomic batch visibility, the caller must
1402 /// publish `seqno` (via `visible_seqno.fetch_max(seqno + 1)`) only
1403 /// **after** this method returns.
1404 ///
1405 /// Returns the total bytes added and new size of the memtable.
1406 ///
1407 /// # Examples
1408 ///
1409 /// ```
1410 /// # let folder = tempfile::tempdir()?;
1411 /// use lsm_tree::{AbstractTree, Config, Tree, WriteBatch};
1412 ///
1413 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1414 ///
1415 /// let mut batch = WriteBatch::new();
1416 /// batch.insert("key1", "value1");
1417 /// batch.insert("key2", "value2");
1418 /// batch.remove("key3");
1419 ///
1420 /// let (bytes_added, memtable_size) = tree.apply_batch(batch, 0)?;
1421 /// assert!(bytes_added > 0);
1422 /// #
1423 /// # Ok::<(), lsm_tree::Error>(())
1424 /// ```
1425 ///
1426 /// # Errors
1427 ///
1428 /// Returns [`Error::MixedOperationBatch`](crate::Error::MixedOperationBatch)
1429 /// if the batch contains mixed operation types for the same user key.
1430 fn apply_batch(&self, batch: crate::WriteBatch, seqno: SeqNo) -> crate::Result<(u64, u64)>;
1431
1432 /// Inserts a key-value pair into the tree.
1433 ///
1434 /// If the key already exists, the item will be overwritten.
1435 ///
1436 /// Returns the added item's size and new size of the memtable.
1437 ///
1438 /// # Examples
1439 ///
1440 /// ```
1441 /// # let folder = tempfile::tempdir()?;
1442 /// use lsm_tree::{AbstractTree, Config, Tree};
1443 ///
1444 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1445 /// tree.insert("a", "abc", 0);
1446 /// #
1447 /// # Ok::<(), lsm_tree::Error>(())
1448 /// ```
1449 ///
1450 /// # Errors
1451 ///
1452 /// Will return `Err` if an IO error occurs.
1453 fn insert<K: Into<UserKey>, V: Into<UserValue>>(
1454 &self,
1455 key: K,
1456 value: V,
1457 seqno: SeqNo,
1458 ) -> (u64, u64);
1459
1460 /// Admission-gated [`insert`](Self::insert): consults
1461 /// [`write_admission`](Self::write_admission) first and declines with
1462 /// [`Error::StorageFull`](crate::Error::StorageFull) when the tree is over
1463 /// budget, otherwise inserts and returns the same `(added_bytes,
1464 /// memtable_size)` tuple. This is the write entry a space-aware caller uses
1465 /// so over-budget writes are refused up front rather than failing a flush
1466 /// later; bare [`insert`](Self::insert) stays infallible for callers that
1467 /// do not opt into admission control.
1468 ///
1469 /// # Errors
1470 ///
1471 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1472 /// is closed.
1473 fn try_insert<K: Into<UserKey>, V: Into<UserValue>>(
1474 &self,
1475 key: K,
1476 value: V,
1477 seqno: SeqNo,
1478 ) -> crate::Result<(u64, u64)> {
1479 self.write_admission()?;
1480 Ok(self.insert(key, value, seqno))
1481 }
1482
1483 /// Admission-gated [`merge`](Self::merge). See
1484 /// [`try_insert`](Self::try_insert).
1485 ///
1486 /// # Errors
1487 ///
1488 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1489 /// is closed.
1490 fn try_merge<K: Into<UserKey>, V: Into<UserValue>>(
1491 &self,
1492 key: K,
1493 operand: V,
1494 seqno: SeqNo,
1495 ) -> crate::Result<(u64, u64)> {
1496 self.write_admission()?;
1497 Ok(self.merge(key, operand, seqno))
1498 }
1499
1500 /// Admission-gated [`remove`](Self::remove). See
1501 /// [`try_insert`](Self::try_insert).
1502 ///
1503 /// Note a tombstone is itself a write that consumes space; an over-budget
1504 /// tree refuses it too. Reclaim space via compaction (never gated) rather
1505 /// than relying on deletes when already read-only.
1506 ///
1507 /// # Errors
1508 ///
1509 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1510 /// is closed.
1511 fn try_remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> crate::Result<(u64, u64)> {
1512 self.write_admission()?;
1513 Ok(self.remove(key, seqno))
1514 }
1515
1516 /// Admission-gated [`remove_weak`](Self::remove_weak). See
1517 /// [`try_insert`](Self::try_insert).
1518 ///
1519 /// # Errors
1520 ///
1521 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1522 /// is closed.
1523 fn try_remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> crate::Result<(u64, u64)> {
1524 self.write_admission()?;
1525 Ok(self.remove_weak(key, seqno))
1526 }
1527
1528 /// Admission-gated [`remove_range`](Self::remove_range). See
1529 /// [`try_insert`](Self::try_insert).
1530 ///
1531 /// # Errors
1532 ///
1533 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1534 /// is closed.
1535 fn try_remove_range<K: Into<UserKey>>(
1536 &self,
1537 start: K,
1538 end: K,
1539 seqno: SeqNo,
1540 ) -> crate::Result<u64> {
1541 self.write_admission()?;
1542 Ok(self.remove_range(start, end, seqno))
1543 }
1544
1545 /// Removes an item from the tree.
1546 ///
1547 /// Returns the added item's size and new size of the memtable.
1548 ///
1549 /// # Examples
1550 ///
1551 /// ```
1552 /// # let folder = tempfile::tempdir()?;
1553 /// # use lsm_tree::{AbstractTree, Config, Tree};
1554 /// #
1555 /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1556 /// tree.insert("a", "abc", 0);
1557 ///
1558 /// let item = tree.get("a", 1)?.expect("should have item");
1559 /// assert_eq!("abc".as_bytes(), &*item);
1560 ///
1561 /// tree.remove("a", 1);
1562 ///
1563 /// let item = tree.get("a", 2)?;
1564 /// assert_eq!(None, item);
1565 /// #
1566 /// # Ok::<(), lsm_tree::Error>(())
1567 /// ```
1568 ///
1569 /// # Errors
1570 ///
1571 /// Will return `Err` if an IO error occurs.
1572 fn remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64);
1573
1574 /// Writes a merge operand for a key.
1575 ///
1576 /// The operand is stored as a partial update that will be combined with
1577 /// other operands and/or a base value via the configured [`crate::MergeOperator`]
1578 /// during reads and compaction.
1579 ///
1580 /// Returns the added item's size and new size of the memtable.
1581 ///
1582 /// # Examples
1583 ///
1584 /// ```
1585 /// # let folder = tempfile::tempdir()?;
1586 /// # use lsm_tree::{AbstractTree, Config, MergeOperator, UserValue};
1587 /// # use std::sync::Arc;
1588 /// # struct SumMerge;
1589 /// # impl MergeOperator for SumMerge {
1590 /// # fn merge(&self, _key: &[u8], base: Option<&[u8]>, operands: &[&[u8]]) -> lsm_tree::Result<UserValue> {
1591 /// # let mut sum: i64 = base.map_or(0, |b| i64::from_le_bytes(b.try_into().unwrap()));
1592 /// # for op in operands { sum += i64::from_le_bytes((*op).try_into().unwrap()); }
1593 /// # Ok(sum.to_le_bytes().to_vec().into())
1594 /// # }
1595 /// # }
1596 /// # let tree = Config::new(folder, Default::default(), Default::default())
1597 /// # .with_merge_operator(Some(Arc::new(SumMerge)))
1598 /// # .open()?;
1599 /// tree.merge("counter", 1_i64.to_le_bytes(), 0);
1600 /// # Ok::<(), lsm_tree::Error>(())
1601 /// ```
1602 fn merge<K: Into<UserKey>, V: Into<UserValue>>(
1603 &self,
1604 key: K,
1605 operand: V,
1606 seqno: SeqNo,
1607 ) -> (u64, u64);
1608
1609 /// Removes an item from the tree.
1610 ///
1611 /// The tombstone marker of this delete operation will vanish when it
1612 /// collides with its corresponding insertion.
1613 /// This may cause older versions of the value to be resurrected, so it should
1614 /// only be used and preferred in scenarios where a key is only ever written once.
1615 ///
1616 /// Returns the added item's size and new size of the memtable.
1617 ///
1618 /// # Examples
1619 ///
1620 /// ```
1621 /// # let folder = tempfile::tempdir()?;
1622 /// # use lsm_tree::{AbstractTree, Config, Tree};
1623 /// #
1624 /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1625 /// tree.insert("a", "abc", 0);
1626 ///
1627 /// let item = tree.get("a", 1)?.expect("should have item");
1628 /// assert_eq!("abc".as_bytes(), &*item);
1629 ///
1630 /// tree.remove_weak("a", 1);
1631 ///
1632 /// let item = tree.get("a", 2)?;
1633 /// assert_eq!(None, item);
1634 /// #
1635 /// # Ok::<(), lsm_tree::Error>(())
1636 /// ```
1637 ///
1638 /// # Errors
1639 ///
1640 /// Will return `Err` if an IO error occurs.
1641 #[doc(hidden)]
1642 fn remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64);
1643
1644 /// Deletes all keys in the range `[start, end)` by inserting a range tombstone.
1645 ///
1646 /// This is much more efficient than deleting keys individually when
1647 /// removing a contiguous range of keys.
1648 ///
1649 /// Returns the approximate size added to the memtable.
1650 /// Returns 0 if `start >= end` (invalid interval is silently ignored).
1651 ///
1652 /// This is a required method on the crate's sealed tree types.
1653 fn remove_range<K: Into<UserKey>>(&self, start: K, end: K, seqno: SeqNo) -> u64;
1654
1655 /// Deletes all keys with the given prefix by inserting a range tombstone.
1656 ///
1657 /// This is sugar over [`AbstractTree::remove_range`] using prefix bounds.
1658 ///
1659 /// Returns the approximate size added to the memtable.
1660 /// Returns 0 for empty prefixes or all-`0xFF` prefixes (cannot form valid half-open range).
1661 fn remove_prefix<K: AsRef<[u8]>>(&self, prefix: K, seqno: SeqNo) -> u64 {
1662 use crate::range::prefix_to_range;
1663 use core::ops::Bound;
1664
1665 let (lo, hi) = prefix_to_range(prefix.as_ref());
1666
1667 let Bound::Included(start) = lo else { return 0 };
1668
1669 // Bound::Unbounded means the prefix is all 0xFF — no representable
1670 // exclusive upper bound exists, so we cannot form a valid range tombstone.
1671 let Bound::Excluded(end) = hi else { return 0 };
1672
1673 self.remove_range(start, end, seqno)
1674 }
1675}