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 Config, Guard, InternalValue, KvPair, Memtable, SeqNo, TableId, UserKey, UserValue,
8 iter_guard::{IterGuardImpl, SeekableGuardIter},
9 table::Table,
10 version::Version,
11 vlog::BlobFile,
12};
13use alloc::sync::Arc;
14#[cfg(not(feature = "std"))]
15use alloc::{boxed::Box, vec::Vec};
16use core::ops::RangeBounds;
17
18pub(crate) type RangeItem = crate::Result<KvPair>;
19
20pub(crate) type FlushToTablesResult = (
21 Vec<Table>,
22 Option<Vec<BlobFile>>,
23 crate::runtime_config::WritePin,
24);
25
26/// Summary of a checkpoint produced by
27/// [`AbstractTree::create_checkpoint`].
28///
29/// All byte counts are *logical* file sizes — hard links share the
30/// underlying inode storage, so a checkpoint's marginal disk usage is
31/// typically zero until the original files are compacted away.
32#[derive(Debug, Clone, Copy)]
33pub struct CheckpointInfo {
34 /// Number of SST files captured.
35 pub sst_files: usize,
36 /// Number of blob (value-log) files captured. Always `0` for a
37 /// standard [`Tree`](crate::Tree).
38 pub blob_files: usize,
39 /// Sum of the logical file sizes of every file the checkpoint captured:
40 /// the SSTs, the blob files, and the `.restrict-bound` sidecar that belongs
41 /// to a tight-space-restricted SST (per-table state, written beside its
42 /// table, that a restore needs to recover the exact bound). Tree-level
43 /// metadata (the manifest snapshot and its edit log, the config, and the
44 /// compression dictionaries under `dicts/`) is NOT counted: this figure
45 /// describes the captured data, not the whole directory. A checkpoint of a
46 /// dictionary-compressed tree therefore holds a little more on disk than
47 /// this reports, by the size of the dictionaries it carries (order 100 KiB
48 /// each, and shared by every table written against them rather than
49 /// per-table like the data this counts).
50 ///
51 /// It measures the same SET of files as
52 /// [`StorageStats::used_bytes`](crate::StorageStats::used_bytes), which
53 /// reports them PHYSICALLY, so the two differ by exactly the bytes a
54 /// tight-space reclaim punched out and agree everywhere else.
55 pub total_bytes: u64,
56 /// The version ID of the manifest snapshot the checkpoint installs.
57 pub version_id: u64,
58 /// Lower-bound visible-seqno watermark for the snapshot.
59 ///
60 /// Captured from the tree's `visible_seqno` generator BEFORE
61 /// [`AbstractTree::current_version`]. Following the standard
62 /// "lowest-excluded" watermark convention, `info.seqno = N` means
63 /// every record with `seqno < N` was committed at sample time and
64 /// is therefore guaranteed to be present in the snapshot. Records
65 /// with `seqno == N` may or may not be included (writers can hold
66 /// a record in the memtable for an instant before publishing the
67 /// next watermark); records with `seqno > N` may also be present
68 /// (writers can advance the counter between sample and version
69 /// snapshot, and those keys still land in the captured memtable).
70 ///
71 /// PITR consumers MUST use `seqno < info.seqno` as the inclusion
72 /// gate. Using `<=` (treating this as a max-included ceiling)
73 /// could move a recovery cutoff past data still needed from WAL
74 /// or replication; the field is a strict lower-exclusive watermark,
75 /// not a max-included ceiling.
76 pub seqno: SeqNo,
77}
78
79// Sealed on purpose: this trait is still public as a consumer-side bound
80// (`&impl AbstractTree`), but external implementations are no longer part of
81// the supported extension surface. Internal flush/version hooks keep evolving
82// with crate-owned tree types and must not create downstream semver traps.
83//
84// `sealed` is crate-visible so sibling modules in this crate can write
85// `crate::abstract_tree::sealed::Sealed` in their impls; downstream crates
86// cannot name or implement this trait.
87pub(crate) mod sealed {
88 pub trait Sealed {}
89}
90
91/// Outcome of [`AbstractTree::refresh_table_checksum`].
92#[cfg(feature = "std")]
93#[doc(hidden)]
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ChecksumRefreshOutcome {
96 /// The digest was installed into the manifest.
97 Refreshed,
98 /// Legitimate no-op: the table was compacted away, or its restriction no
99 /// longer matches the one the digest was computed for; the current view
100 /// carries its own (compaction-installed) digest for the next patrol to
101 /// reconcile.
102 Stale,
103 /// The install lock was held by a concurrent compaction (blocking on it
104 /// would invert the heal-lock / compaction-state order and deadlock). The
105 /// manifest digest stays stale against durable healed bytes: the caller
106 /// must surface this as a finding, keep the attestation, and let a later
107 /// patrol retry.
108 Contended,
109}
110
111/// Generic Tree API
112pub trait AbstractTree: sealed::Sealed {
113 /// Debug method for tracing the MVCC history of a key.
114 #[doc(hidden)]
115 fn print_trace(&self, key: &[u8]) -> crate::Result<()>;
116
117 /// Returns the number of cached table file descriptors.
118 fn table_file_cache_size(&self) -> usize;
119
120 #[doc(hidden)]
121 fn next_table_id(&self) -> TableId;
122
123 #[doc(hidden)]
124 fn id(&self) -> crate::TreeId;
125
126 /// Like [`AbstractTree::get`], but returns the actual internal entry, not just the user value.
127 ///
128 /// Used in tests.
129 #[doc(hidden)]
130 fn get_internal_entry(&self, key: &[u8], seqno: SeqNo) -> crate::Result<Option<InternalValue>>;
131
132 #[doc(hidden)]
133 fn current_version(&self) -> Version;
134
135 /// Records that the table with `table_id` now has the digest `checksum`,
136 /// persisting it to the manifest through a version upgrade.
137 ///
138 /// Called after an in-place heal rewrites a table's bytes: the healed
139 /// file's digest no longer matches the one captured at recovery, and
140 /// without the refresh a later [`verify`](crate::verify) pass (or a
141 /// repair) would flag the healed file as corrupted against the stale
142 /// manifest digest. A no-op when the table is no longer part of the
143 /// current version (compacted away while the heal ran — the old file is
144 /// on its way out).
145 ///
146 /// `expected_restriction` is the tight-space restriction bound the CALLER
147 /// computed `checksum` for (its captured view's [`restrict_lower_bound`]).
148 /// This method holds the install lock, so it re-checks it against the CURRENT
149 /// view under that lock and refuses the install (a no-op) if a compaction
150 /// swapped the view to a different restriction meanwhile: a whole-file digest
151 /// installed into a suffix-only restricted manifest (or vice versa) could
152 /// never match the punched file.
153 ///
154 /// Returns [`ChecksumRefreshOutcome::Refreshed`] when the digest was
155 /// installed, [`ChecksumRefreshOutcome::Stale`] on a legitimate no-op (the
156 /// table was compacted away, or its restriction no longer matches
157 /// `expected_restriction` — the current view carries its own digest), and
158 /// [`ChecksumRefreshOutcome::Contended`] when the install lock was held by
159 /// a concurrent compaction. The caller uses this to decide whether to clear
160 /// the heal attestation (only `Refreshed` may) and whether the pass stayed
161 /// clean: a contended skip leaves the manifest digest stale against durable
162 /// healed bytes, which must surface as a finding rather than a clean
163 /// report.
164 ///
165 /// [`restrict_lower_bound`]: crate::table::Table::restrict_lower_bound
166 #[cfg(feature = "std")]
167 #[doc(hidden)]
168 fn refresh_table_checksum(
169 &self,
170 table_id: TableId,
171 checksum: crate::checksum::Checksum,
172 expected_restriction: Option<&crate::UserKey>,
173 ) -> crate::Result<ChecksumRefreshOutcome>;
174
175 /// The tree's configured durability mode
176 /// ([`Config::sync_mode`](crate::config::Config::sync_mode)). Maintenance
177 /// paths that write outside the flush pipeline (the in-place heal) read
178 /// it here so their syncs honor the same durability the tree's own
179 /// writes use.
180 #[doc(hidden)]
181 fn sync_mode(&self) -> crate::fs::SyncMode;
182
183 /// Waits for the table file pools to finish the refills running off the
184 /// writing threads: a test that counts the syncs an operation causes
185 /// waits for them before it counts.
186 #[doc(hidden)]
187 fn wait_for_table_file_refills(&self);
188
189 /// The tree's configured prefix extractor, or `None` when it indexes no
190 /// prefixes. The patrol scrub's filter cross-check reads it here so it
191 /// can verify a rebuilt full filter carries the source's prefix hashes,
192 /// not just its complete-key hashes.
193 #[doc(hidden)]
194 fn prefix_extractor(&self) -> Option<alloc::sync::Arc<dyn crate::prefix::PrefixExtractor>>;
195
196 /// Returns a read-only snapshot of the tree's on-disk storage footprint:
197 /// total used bytes, entry count, the average shape of a stored entry
198 /// (average key / value bytes), and an estimate of how many more
199 /// average-shaped entries fit in a byte budget (see
200 /// [`StorageStats::estimated_remaining_entries`](crate::StorageStats::estimated_remaining_entries)).
201 ///
202 /// Computed from the live version's table + blob metadata plus one
203 /// size-stat per live file; it never reads a data block. The default
204 /// implementation reports [`StorageStatus::Healthy`](crate::StorageStatus::Healthy);
205 /// the standard tree overrides it to report
206 /// [`StorageStatus::CompactionInProgress`](crate::StorageStatus::CompactionInProgress) while a
207 /// compaction runs.
208 ///
209 /// # Examples
210 ///
211 /// ```
212 /// # use lsm_tree::Error as TreeError;
213 /// use lsm_tree::{AbstractTree, Config};
214 ///
215 /// let folder = tempfile::tempdir()?;
216 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
217 ///
218 /// for i in 0..100u64 {
219 /// tree.insert(format!("key{i:04}"), "value", i);
220 /// }
221 /// tree.flush_active_memtable(0)?;
222 ///
223 /// let stats = tree.storage_stats()?;
224 /// assert_eq!(stats.item_count, 100);
225 /// // Roughly how many more average-shaped entries fit in another 1 MiB.
226 /// let _headroom = stats.estimated_remaining_entries(1024 * 1024);
227 /// #
228 /// # Ok::<(), TreeError>(())
229 /// ```
230 ///
231 /// # Errors
232 ///
233 /// Returns an error if a live file's size cannot be stat-ed, or a table's
234 /// blob-link section cannot be read or parsed.
235 fn storage_stats(&self) -> crate::Result<crate::StorageStats> {
236 crate::storage_stats::compute_storage_stats(&self.current_version(), false, true)
237 }
238
239 /// Per-LSM-level and per-segment size + entry-count stats, for tiering and
240 /// erasure-coding placement decisions (which level / segment is large enough
241 /// to demote, EC-encode, or migrate).
242 ///
243 /// Cheap: derived from the live version's metadata plus one file-size stat
244 /// per segment (no data-block scan); in a tree that separates values, each
245 /// segment's blob-link section is also read once, then kept. The per-level
246 /// totals reconcile with
247 /// [`storage_stats`](Self::storage_stats): summed across levels they equal
248 /// the SST portion of [`StorageStats::used_bytes`](crate::StorageStats::used_bytes)
249 /// and [`StorageStats::item_count`](crate::StorageStats::item_count).
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// # use lsm_tree::Error as TreeError;
255 /// use lsm_tree::{AbstractTree, Config};
256 ///
257 /// let folder = tempfile::tempdir()?;
258 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
259 /// for i in 0..100u32 {
260 /// tree.insert(format!("k{i:04}"), "v", 0);
261 /// }
262 /// tree.flush_active_memtable(0)?;
263 ///
264 /// let levels = tree.level_segment_stats()?;
265 /// let total: u64 = levels.iter().map(|l| l.item_count).sum();
266 /// assert_eq!(total, tree.storage_stats()?.item_count);
267 /// #
268 /// # Ok::<(), TreeError>(())
269 /// ```
270 ///
271 /// # Errors
272 ///
273 /// Returns an error if a segment's file size cannot be stat-ed, or its
274 /// blob-link section cannot be read or parsed.
275 fn level_segment_stats(&self) -> crate::Result<Vec<crate::LevelStats>> {
276 crate::storage_stats::compute_level_segment_stats(&self.current_version())
277 }
278
279 /// Estimated bytes pending compaction under `strategy`: on-disk data above
280 /// its level's target that must eventually be rewritten downward (a `RocksDB`
281 /// `estimate-pending-compaction-bytes` analog), a compaction-debt signal for a
282 /// scheduler / tiering consumer.
283 ///
284 /// The strategy is supplied by the caller because the engine does not own a
285 /// configured compaction strategy (it is injected per compaction run); a
286 /// `&dyn` keeps this object-safe. Returns `0` for strategies without a
287 /// size-target notion of debt (FIFO), or when the tree is at or
288 /// below its target shape. See
289 /// [`CompactionStrategy::pending_compaction_bytes`](crate::compaction::CompactionStrategy::pending_compaction_bytes).
290 fn compaction_debt(&self, strategy: &dyn crate::compaction::CompactionStrategy) -> u64 {
291 strategy.pending_compaction_bytes(&self.current_version())
292 }
293
294 /// Computed write-backpressure verdict from the live L0 run count, the
295 /// strategy's pending-compaction bytes and the unflushed memtable bytes,
296 /// against the configured
297 /// [`RuntimeConfig::backpressure`](crate::runtime_config::RuntimeConfig)
298 /// thresholds.
299 ///
300 /// Advisory, mirroring [`write_admission`](Self::write_admission): the caller
301 /// consults it and throttles in its own write loop (sleep `suggested_delay`
302 /// at [`Slowdown`](crate::Backpressure::Slowdown), pause at
303 /// [`Stop`](crate::Backpressure::Stop)). The engine never blocks on it,
304 /// because it does not own the compaction that drains the debt — an internal
305 /// stall could deadlock the very thread that would compact.
306 ///
307 /// The strategy is supplied by the caller for the same reason as
308 /// [`compaction_debt`](Self::compaction_debt). Returns
309 /// [`Backpressure::None`](crate::Backpressure::None) by default (no
310 /// thresholds configured).
311 fn write_backpressure(
312 &self,
313 _strategy: &dyn crate::compaction::CompactionStrategy,
314 ) -> crate::Backpressure {
315 crate::Backpressure::None
316 }
317
318 /// The verdict for one write of `bytes`: [`write_backpressure`](Self::write_backpressure),
319 /// except that a slowdown on a tree with a write rate limit (see
320 /// [`Config::write_rate_limit`](crate::Config::write_rate_limit)) debits
321 /// `bytes` from the tree's write limiter and suggests waiting until they,
322 /// and every byte debited before them, are paid off at its rate. Writes
323 /// honouring it are then bounded by the rate in bytes per second, large
324 /// and small alike, however many writers share the limiter. Outside the
325 /// slowdown tier nothing is debited.
326 ///
327 /// # Examples
328 ///
329 /// ```
330 /// use lsm_tree::{AbstractTree, Backpressure, Config, compaction::Leveled};
331 ///
332 /// let folder = tempfile::tempdir()?;
333 /// let tree = Config::new(&folder, Default::default(), Default::default())
334 /// .write_rate_limit(1_024 * 1_024)
335 /// .open()?;
336 /// // No threshold set: full speed, nothing debited.
337 /// assert_eq!(tree.write_backpressure_for(&Leveled::default(), 4_096), Backpressure::None);
338 /// # Ok::<(), lsm_tree::Error>(())
339 /// ```
340 fn write_backpressure_for(
341 &self,
342 strategy: &dyn crate::compaction::CompactionStrategy,
343 bytes: u64,
344 ) -> crate::Backpressure {
345 let verdict = self.write_backpressure(strategy);
346 if !matches!(verdict, crate::Backpressure::Slowdown { .. }) {
347 return verdict;
348 }
349 let limiter = self.write_rate_limiter();
350 if limiter.rate() == 0 {
351 return verdict;
352 }
353 #[cfg(feature = "std")]
354 {
355 crate::Backpressure::Slowdown {
356 suggested_delay: limiter.reserve(bytes),
357 }
358 }
359 // no-std: acquire_wait with a caller-provided monotonic clock; without
360 // one there is no rate to pace against and the slowdown keeps its ramp.
361 #[cfg(not(feature = "std"))]
362 {
363 let _ = bytes;
364 verdict
365 }
366 }
367
368 /// The limiter writes are paced with under backpressure slowdown: shared
369 /// with other trees if one was supplied through
370 /// [`Config::write_rate_limiter`](crate::Config::write_rate_limiter), else
371 /// the tree's own. Retune it live with
372 /// [`RateLimiter::set_rate`](crate::rate_limiter::RateLimiter::set_rate);
373 /// `0` turns byte pacing off.
374 fn write_rate_limiter(&self) -> alloc::sync::Arc<crate::rate_limiter::RateLimiter>;
375
376 /// Storage admission gate: `Ok(())` if a write may proceed, or
377 /// [`Error::StorageFull`](crate::Error::StorageFull) if the tree is
378 /// over budget and should be treated as read-only.
379 ///
380 /// Opt-in: returns `Ok(())` unless
381 /// [`storage_admission_check`](crate::runtime_config::RuntimeConfig::storage_admission_check)
382 /// is enabled. The predicate is computed (not latched), so once space is
383 /// freed — the budget raised, a compaction reclaiming space, or disk freed —
384 /// the next call admits again with no restart.
385 ///
386 /// Intended as a cheap pre-check the caller consults before applying a
387 /// write batch. The footprint is cached per version, so the check is a
388 /// constant-time read on the common path and only re-measures when a new
389 /// version is installed (flush / compaction). Internal flush / compaction
390 /// are never gated, so the engine can always reclaim.
391 ///
392 /// # Errors
393 ///
394 /// [`Error::StorageFull`](crate::Error::StorageFull) when the live footprint
395 /// plus reserved headroom exceeds the effective budget.
396 fn write_admission(&self) -> crate::Result<()> {
397 Ok(())
398 }
399
400 /// `true` when the admission gate is currently closed (see
401 /// [`write_admission`](Self::write_admission)). Convenience for callers that
402 /// want a boolean rather than a `Result`. Always `false` unless admission
403 /// control is enabled and the tree is over budget.
404 ///
405 /// Only [`Error::StorageFull`](crate::Error::StorageFull) counts as
406 /// read-only: an unrelated admission error (e.g. an I/O failure while
407 /// measuring the footprint) is NOT an out-of-space condition and must not be
408 /// reported as one.
409 fn is_read_only(&self) -> bool {
410 matches!(
411 self.write_admission(),
412 Err(crate::Error::StorageFull { .. })
413 )
414 }
415
416 /// Proactively verifies every block's XXH3 checksum across every SST in
417 /// the tree's current version — a scrubber for catching bit rot before it
418 /// surfaces as a user-visible read failure (cron / scrub jobs).
419 ///
420 /// Reports at block granularity and never aborts early. The returned
421 /// [`BlockVerifyReport`](crate::verify::BlockVerifyReport) records
422 /// block-corruption findings with `(file, offset)`, while file-level errors
423 /// (e.g. [`BlockVerifyError::SstFileUnreadable`](crate::verify::BlockVerifyError::SstFileUnreadable))
424 /// carry the file only (no offset). It does not surface per-entry indices or
425 /// ECC-correction counts (when ECC-at-rest is enabled a within-budget corrupt
426 /// block may still be healed on read as a side effect of the scan, but the
427 /// report does not tally corrections).
428 ///
429 /// Filesystems with native per-block integrity (ZFS, Btrfs, `ReFS`, S3 —
430 /// see [`Fs::capabilities`](crate::fs::Fs::capabilities)) already detect
431 /// corruption on read; this scrub is the portable check for the rest.
432 ///
433 /// Use [`Self::verify_checksum_with`] for parallelism / throttle control.
434 #[cfg(feature = "std")]
435 fn verify_checksum(&self) -> crate::verify::BlockVerifyReport
436 where
437 Self: Sized,
438 {
439 crate::verify::verify_block_checksums(self)
440 }
441
442 /// Like [`Self::verify_checksum`] but with configurable parallelism and
443 /// I/O throttle (see [`VerifyOptions`](crate::verify::VerifyOptions)).
444 #[cfg(feature = "std")]
445 fn verify_checksum_with(
446 &self,
447 options: &crate::verify::VerifyOptions,
448 ) -> crate::verify::BlockVerifyReport
449 where
450 Self: Sized,
451 {
452 crate::verify::verify_block_checksums_with(self, options)
453 }
454
455 #[doc(hidden)]
456 fn get_version_history_lock(&self) -> VersionsWriteGuard<'_>;
457
458 /// Creates a hard-linked checkpoint of the tree's on-disk state in
459 /// `target_path` for point-in-time recovery (PITR) backup.
460 ///
461 /// The checkpoint is a fully functional tree that can be opened
462 /// independently via [`Config::open`](crate::Config::open). For the
463 /// common single-filesystem case all SST files (and blob files, for
464 /// [`BlobTree`](crate::BlobTree)) are hard-linked rather than copied, so the operation
465 /// is O(1) per file and consumes zero additional disk space until the
466 /// original files are compacted away — at which point the inode is
467 /// kept alive by the checkpoint link.
468 ///
469 /// # Cross-filesystem / cross-backend fall-back
470 ///
471 /// When a source file lives on a different filesystem than the
472 /// checkpoint target — e.g. an SST routed to a hot tier via
473 /// [`level_routes`](crate::Config::level_routes) on a separate volume,
474 /// or a backup directory on a foreign mount — the hard link cannot
475 /// be created (Unix `EXDEV`). In that case the checkpoint silently
476 /// falls back to a streamed byte copy, which:
477 ///
478 /// - takes time linear in the file size instead of O(1), and
479 /// - consumes disk space equal to the copied bytes on the target
480 /// volume (no inode sharing across filesystems).
481 ///
482 /// Each fall-back call emits one [`log::debug`] line (deliberately not
483 /// `warn`: a misconfigured tier could trigger this path once per SST
484 /// and per blob — thousands of times per snapshot — and per-file
485 /// warnings would drown real signal). Operators wanting hard-visibility
486 /// of unexpected full copies should enable debug logging on the `fs`
487 /// module or watch the `CheckpointInfo.total_bytes` figure (≫ inode
488 /// link cost means the fallback fired). The same `debug` policy applies
489 /// when source and target use entirely different [`Fs`](crate::fs::Fs)
490 /// backends (e.g. [`MemFs`](crate::fs::MemFs) → [`StdFs`](crate::fs::StdFs)
491 /// in tests).
492 ///
493 /// # Durability
494 ///
495 /// `Ok` means the checkpoint's files and directories are persisted under
496 /// [`Config::sync_mode`](crate::Config::sync_mode): durable under
497 /// [`SyncMode::Full`](crate::fs::SyncMode::Full); under
498 /// [`SyncMode::Barrier`](crate::fs::SyncMode::Barrier) synced in order, one
499 /// barrier per file, and durable once a later
500 /// [`Fs::sync_device`](crate::fs::Fs::sync_device) of `target_path` returns
501 /// `Ok`.
502 ///
503 /// # Concurrency
504 ///
505 /// While the checkpoint is being built, compaction continues normally
506 /// but the physical removal of obsolete files is deferred until the
507 /// checkpoint hard-links are in place. This is implemented by an
508 /// internal reference-counted deletion gate; callers do not have to
509 /// pause compaction themselves.
510 ///
511 /// # Errors
512 ///
513 /// Returns an error if:
514 /// - the active memtable could not be flushed,
515 /// - `target_path` already exists (to prevent accidental overwrites),
516 /// - a hard link / copy fall-back could not be created, or
517 /// - the manifest files could not be replicated.
518 ///
519 /// On error any partial checkpoint files are removed automatically
520 /// (best-effort) so callers can safely retry against the same path.
521 // std-only: checkpoint creation hard-links / copies files via std::fs.
522 #[cfg(feature = "std")]
523 fn create_checkpoint(&self, target_path: &crate::path::Path) -> crate::Result<CheckpointInfo>;
524
525 /// Pins the tree's current state for a point-in-time copy that costs no
526 /// file I/O: seals the active memtable and returns a
527 /// [`PinnedVersion`](crate::PinnedVersion) holding the sealed memtables,
528 /// tables, blob files and dictionaries as they are now.
529 ///
530 /// Nothing is copied, linked, synced or written. The pinned files stay on
531 /// disk with unchanged bytes while flushes and compactions supersede them,
532 /// until the last pin listing them drops; a consumer streams them byte for
533 /// byte and rebuilds the tree elsewhere with
534 /// [`Config::restore_pinned`](crate::Config::restore_pinned). A restart
535 /// drops every pin.
536 ///
537 /// Waits for an in-place heal or a tight-space hole punch already under
538 /// way to finish; new ones on a pinned file wait for the pin.
539 ///
540 /// # Examples
541 ///
542 /// ```
543 /// use lsm_tree::{AbstractTree, Config, SequenceNumberCounter};
544 ///
545 /// let folder = tempfile::tempdir()?;
546 /// let seqno = SequenceNumberCounter::default();
547 /// let visible = SequenceNumberCounter::default();
548 /// let tree = Config::new(&folder, seqno.clone(), visible.clone()).open()?;
549 /// tree.insert("a", "1", seqno.next());
550 /// tree.flush_active_memtable(0)?;
551 /// tree.insert("b", "2", seqno.next());
552 /// // The writes are published: the pin holds every write below this.
553 /// visible.set(seqno.get());
554 ///
555 /// let pin = tree.pin();
556 /// assert_eq!(pin.tables().len(), 1);
557 /// assert_eq!(pin.memtable_entries().count(), 1);
558 /// # Ok::<(), lsm_tree::Error>(())
559 /// ```
560 #[cfg(feature = "std")]
561 fn pin(&self) -> crate::PinnedVersion;
562
563 /// Stops the tree owing the repair report with this
564 /// [`RepairReport::id`](crate::RepairReport::id), installing a version
565 /// that no longer carries it: no later
566 /// [`Config::open_or_repair`](crate::Config::open_or_repair) hands it out.
567 ///
568 /// A repair's report stays in the tree until this call, so a process that
569 /// ends before the caller acted on it gets it again. Call it once the
570 /// report's replay obligation is met. Returns `false`, changing nothing,
571 /// when the tree owes no report or owes one with another id.
572 ///
573 /// # Errors
574 ///
575 /// Propagates the install's failure; the report is then still owed, or,
576 /// on [`Error::InstallOutcomeUnknown`](crate::Error::InstallOutcomeUnknown),
577 /// may be after a power loss.
578 ///
579 /// # Examples
580 ///
581 /// ```no_run
582 /// use lsm_tree::{AbstractTree, Config, RepairPolicy, SequenceNumberCounter};
583 ///
584 /// let config = Config::new(
585 /// "/var/lib/mydb",
586 /// SequenceNumberCounter::default(),
587 /// SequenceNumberCounter::default(),
588 /// );
589 /// let (tree, repaired) = config.open_or_repair(RepairPolicy::default())?;
590 /// if let Some(report) = repaired {
591 /// // Replay the external log as `report.wal_replay_scope()` requires,
592 /// // then let the tree forget the report.
593 /// tree.acknowledge_repair_report(report.id)?;
594 /// }
595 /// # Ok::<_, lsm_tree::Error>(())
596 /// ```
597 #[cfg(feature = "std")]
598 fn acknowledge_repair_report(&self, id: u128) -> crate::Result<bool>;
599
600 /// Seals the active memtable and flushes it, with every memtable sealed
601 /// before it, to tables: the durability barrier for a caller that keeps
602 /// its own journal of the writes.
603 ///
604 /// `Ok` means the tables, their directories and the manifest edits that
605 /// name them are persisted under [`Config::sync_mode`](crate::Config::sync_mode)
606 /// for the memtable active at the call and for every memtable sealed before
607 /// it, in sealing order, including one another thread began flushing: the
608 /// call waits for that flush and, should it fail, flushes the memtable
609 /// itself. A caller may release the journal entries those memtables hold
610 /// once the call returns `Ok`; under
611 /// [`SyncMode::Barrier`](crate::fs::SyncMode::Barrier) the writes are only
612 /// ordered then, and the entries may go once
613 /// [`sync_devices`](Self::sync_devices) has returned `Ok` too.
614 ///
615 /// `gc_watermark` has the meaning documented at
616 /// [`major_compact`](Self::major_compact); pass `0` to collect nothing.
617 ///
618 /// # Errors
619 ///
620 /// Returns the error of [`flush`](Self::flush); the memtables it could not
621 /// persist stay sealed, and the next call flushes them.
622 ///
623 /// # Examples
624 ///
625 /// ```
626 /// use lsm_tree::{AbstractTree, Config, SequenceNumberCounter};
627 ///
628 /// let folder = tempfile::tempdir()?;
629 /// let tree = Config::new(
630 /// &folder,
631 /// SequenceNumberCounter::default(),
632 /// SequenceNumberCounter::default(),
633 /// )
634 /// .open()?;
635 /// tree.insert("key", "value", 0);
636 /// // The write is on disk now (the default sync mode is not `Barrier`): a
637 /// // journal that holds it can drop it.
638 /// tree.flush_active_memtable(0)?;
639 /// assert_eq!(tree.table_count(), 1);
640 /// # Ok::<(), lsm_tree::Error>(())
641 /// ```
642 fn flush_active_memtable(&self, gc_watermark: SeqNo) -> crate::Result<()> {
643 let lock = self.get_flush_lock();
644 self.rotate_memtable();
645 self.flush(&lock, gc_watermark)?;
646 Ok(())
647 }
648
649 /// Makes every flush installed before this call durable, under
650 /// [`FlushDurability::Deferred`](crate::config::FlushDurability::Deferred):
651 /// the tables and blob files those flushes wrote are synced, then their
652 /// folders, then the manifest is written. Once it returns `Ok` a caller
653 /// may drop its own log up to the writes those flushes held; under
654 /// [`SyncMode::Barrier`](crate::fs::SyncMode::Barrier) after
655 /// [`sync_devices`](Self::sync_devices) as well. A no-op when nothing is
656 /// pending, and under [`FlushDurability::Synced`](crate::config::FlushDurability::Synced)
657 /// every flush is durable when it returns.
658 ///
659 /// Every other install (a compaction, an ingestion, a drop) persists the
660 /// pending flushes with it, and so do a checkpoint and a dropped tree.
661 ///
662 /// # Examples
663 ///
664 /// ```
665 /// # let folder = tempfile::tempdir()?;
666 /// use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, config::FlushDurability};
667 ///
668 /// let tree = Config::new(
669 /// folder.path(),
670 /// SequenceNumberCounter::default(),
671 /// SequenceNumberCounter::default(),
672 /// )
673 /// .flush_durability(FlushDurability::Deferred)
674 /// .open()?;
675 /// for i in 0..3_u64 {
676 /// tree.insert(i.to_be_bytes(), "v", i);
677 /// tree.flush_active_memtable(0)?;
678 /// }
679 /// // One barrier for the three flushes.
680 /// tree.persist_flushes()?;
681 /// # Ok::<(), lsm_tree::Error>(())
682 /// ```
683 ///
684 /// # Errors
685 ///
686 /// Returns a sync or manifest write error; the flushes stay installed and
687 /// readable, and the next persist retries them.
688 fn persist_flushes(&self) -> crate::Result<()>;
689
690 /// Makes durable everything this tree synced on each device it lies on:
691 /// the tree folder's, that of every level's tables, that of the
692 /// dictionaries folder and, with KV separation, that of the blobs folder.
693 ///
694 /// Under [`SyncMode::Barrier`](crate::fs::SyncMode::Barrier) a
695 /// [`flush_active_memtable`](Self::flush_active_memtable) that returns `Ok`
696 /// has its writes ordered, and they are durable once this returns `Ok`; a
697 /// caller releases its journal after both. Under any other mode the flush
698 /// alone is durable and this only flushes the device caches.
699 ///
700 /// # Errors
701 ///
702 /// Returns the error of a device flush that failed; the devices not yet
703 /// flushed are not.
704 fn sync_devices(&self) -> crate::Result<()> {
705 let config = self.tree_config();
706 let mut flushed: Vec<(Arc<dyn crate::fs::Fs>, crate::path::PathBuf)> = Vec::new();
707 let levels = (0..config.level_count).map(|level| {
708 let (folder, fs) = config.tables_folder_for_level(level);
709 (fs, folder)
710 });
711 // The blobs folder may be a mount of its own under the tree folder.
712 let blobs = config.kv_separation_opts.is_some().then(|| {
713 (
714 Arc::clone(&config.fs),
715 config.path.join(crate::file::BLOBS_FOLDER),
716 )
717 });
718 // So may the dictionaries folder, which the tables written against a
719 // dictionary need to be read.
720 #[cfg(zstd_any)]
721 let dicts = Some((
722 Arc::clone(&config.fs),
723 config.path.join(crate::file::DICTS_FOLDER),
724 ));
725 #[cfg(not(zstd_any))]
726 let dicts: Option<(Arc<dyn crate::fs::Fs>, crate::path::PathBuf)> = None;
727 for (fs, path) in core::iter::once((Arc::clone(&config.fs), config.path.clone()))
728 .chain(levels)
729 .chain(blobs)
730 .chain(dicts)
731 {
732 // One flush per device: a folder on a device already flushed
733 // gains nothing from another.
734 let volume = fs.volume_id(&path);
735 if flushed.iter().any(|(done, at)| {
736 (Arc::ptr_eq(done, &fs) && *at == path)
737 || volume.is_some_and(|volume| done.volume_id(at) == Some(volume))
738 }) {
739 continue;
740 }
741 match fs.sync_device(&path) {
742 Ok(()) => flushed.push((fs, path)),
743 // A folder never made holds nothing to flush.
744 Err(e) if e.kind() == crate::io::ErrorKind::NotFound => {}
745 Err(e) => return Err(e.into()),
746 }
747 }
748 Ok(())
749 }
750
751 /// Synchronously flushes pending sealed memtables to tables.
752 ///
753 /// Returns the sum of flushed memtable sizes that were flushed.
754 ///
755 /// The function may not return a result, if nothing was flushed.
756 ///
757 /// `gc_watermark` has the meaning documented at
758 /// [`major_compact`](Self::major_compact): the flush folds away versions
759 /// below it exactly as a compaction does, and when it does, the install
760 /// raises the retention floor ([`retention_floor`](Self::retention_floor))
761 /// to match. Pass `0` to collect nothing.
762 ///
763 /// # Errors
764 ///
765 /// Returns `Err` on an I/O error, or on a memtable residence-verification
766 /// failure under [`KvChecksumComputePoint::AtInsert`](crate::runtime_config::KvChecksumComputePoint::AtInsert):
767 /// a sealed memtable whose insert-time per-KV digests do not verify before
768 /// flush surfaces [`crate::Error::MemtableKvChecksumMismatch`],
769 /// [`crate::Error::MemtableKvChecksumCorruptAlgorithm`], or
770 /// [`crate::Error::InvalidTag`] (corrupt `value_type`).
771 fn flush(&self, _lock: &FlushGuard<'_>, gc_watermark: SeqNo) -> crate::Result<Option<u64>> {
772 use crate::{
773 compaction::stream::CompactionStream, merge::Merger, range_tombstone::RangeTombstone,
774 };
775
776 let version_history = self.get_version_history_lock();
777 let latest = version_history.latest_version();
778
779 if latest.sealed_memtables.len() == 0 {
780 return Ok(None);
781 }
782
783 let sealed_ids = latest
784 .sealed_memtables
785 .iter()
786 .map(|mt| mt.id)
787 .collect::<Vec<_>>();
788
789 let flushed_size = latest.sealed_memtables.iter().map(|mt| mt.size()).sum();
790 // The keys the flush's filters cover and the hashes they hold, which
791 // the filter plan keeps room for: counted only when a filter budget
792 // plans them, over the merge the flush writes, so a key in several
793 // memtables counts once.
794 let flushed = if self.tree_config().filter_advisor.is_some() {
795 // A partitioned filter holds no prefix hashes.
796 let prefixes = if self.tree_config().filter_block_partitioning_policy.get(0) {
797 None
798 } else {
799 self.tree_config().prefix_extractor.as_deref()
800 };
801 let merged = Merger::new(
802 latest
803 .sealed_memtables
804 .iter()
805 .map(|mt| mt.iter().map(Ok))
806 .collect::<Vec<_>>(),
807 self.tree_config().comparator.clone(),
808 );
809 // Memtable entries read without I/O: the merge yields no error.
810 crate::memtable::filter_count(
811 merged.filter_map(|item| item.ok().map(|item| item.key.user_key)),
812 prefixes,
813 )
814 } else {
815 crate::filter_budget::FilterCount::default()
816 };
817
818 // AtInsert residence check: verify each sealed memtable's insert-time
819 // per-KV digests against a recompute over the entries' current bytes
820 // before writing them out. A divergence means an entry was corrupted
821 // (a RAM bit-flip) while it sat in the memtable. Memtables with no
822 // insert digests (the default) return immediately without walking.
823 //
824 // This is a SEPARATE pass over the as-inserted memtable entries, not
825 // fused into the writer's per-KV footer encode, and deliberately so:
826 // the footer digest is computed over POST-merge / post-seqno-filter
827 // bytes (the CompactionStream below applies the merge operator), so a
828 // merge operator's combined value differs from any single inserted
829 // value. Comparing the carried insert digest against the footer digest
830 // would false-positive on every legitimate merge. Residence corruption
831 // is a property of what was inserted (pre-merge); it must be checked
832 // here, against the raw memtable entries.
833 for mt in latest.sealed_memtables.iter() {
834 mt.verify_kv_residence()?;
835 }
836
837 // Collect range tombstones from sealed memtables
838 let mut range_tombstones: Vec<RangeTombstone> = Vec::new();
839 for mt in latest.sealed_memtables.iter() {
840 range_tombstones.extend(mt.range_tombstones_sorted());
841 }
842 range_tombstones
843 .sort_by(|a, b| a.cmp_with_comparator(b, self.tree_config().comparator.as_ref()));
844 range_tombstones.dedup();
845
846 let merger = Merger::new(
847 latest
848 .sealed_memtables
849 .iter()
850 .map(|mt| mt.iter().map(Ok))
851 .collect::<Vec<_>>(),
852 self.tree_config().comparator.clone(),
853 );
854 // Observed under the stream, which drops the versions that show a key
855 // was overwritten; only a blob tree that groups values by lifetime
856 // asks.
857 let overwritten = crate::blob_tree::overwritten::OverwrittenKeys::default();
858 let marks = self
859 .tree_config()
860 .kv_separation_opts
861 .as_ref()
862 .is_some_and(|kv| kv.lifetime_classifier().is_grouping())
863 .then_some(&overwritten);
864 let merger = crate::blob_tree::overwritten::MarkOverwritten::new(merger, marks);
865 // RT suppression is not needed here: flush writes both entries and RTs
866 // to the output tables. Suppression happens at read time, not write time.
867 let stream = CompactionStream::new(merger, gc_watermark)
868 .with_merge_operator(self.tree_config().merge_operator.clone());
869 // Versions that go in and do not come out, so the install can tell a
870 // flush that collected history from one that collected none and only
871 // raise the retention floor for the former. Read once the stream is
872 // drained, below.
873 let gc_balance = stream.gc_balance();
874
875 drop(version_history);
876
877 let has_range_tombstones = !range_tombstones.is_empty();
878 if let Some((tables, blob_files, write_pin)) = self.flush_to_tables_with_rt(
879 stream,
880 range_tombstones,
881 flushed.keys,
882 flushed.hashes,
883 marks,
884 )? {
885 // A writer given range tombstones and no key writes a table around
886 // them, so a flush that acknowledges a deletion has put it on disk.
887 debug_assert!(
888 !tables.is_empty() || !has_range_tombstones,
889 "a flush of range tombstones wrote no table"
890 );
891
892 #[cfg(all(test, feature = "std"))]
893 self.tree_config().fire_before_output_install();
894
895 self.register_tables(
896 &tables,
897 blob_files.as_deref(),
898 None,
899 &sealed_ids,
900 gc_watermark,
901 gc_balance.load(core::sync::atomic::Ordering::Relaxed) > 0,
902 )?;
903 // The installed files name their dictionaries from here on.
904 drop(write_pin);
905 }
906
907 Ok(Some(flushed_size))
908 }
909
910 /// Returns an iterator that scans through the entire tree.
911 ///
912 /// Avoid using this function, or limit it as otherwise it may scan a lot of items.
913 ///
914 /// A snapshot at or below the retention floor (see
915 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
916 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
917 /// as the iterator's first and only item.
918 fn iter(
919 &self,
920 seqno: SeqNo,
921 index: Option<(Arc<Memtable>, SeqNo)>,
922 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
923 self.range::<&[u8], _>(.., seqno, index)
924 }
925
926 /// Returns an iterator over a prefixed set of items.
927 ///
928 /// Avoid using an empty prefix as it may scan a lot of items (unless limited).
929 ///
930 /// A snapshot at or below the retention floor (see
931 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
932 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
933 /// as the iterator's first and only item.
934 fn prefix<K: AsRef<[u8]>>(
935 &self,
936 prefix: K,
937 seqno: SeqNo,
938 index: Option<(Arc<Memtable>, SeqNo)>,
939 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static>;
940
941 /// Returns an iterator over a range of items.
942 ///
943 /// Avoid using full or unbounded ranges as they may scan a lot of items (unless limited).
944 ///
945 /// A snapshot at or below the retention floor (see
946 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
947 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
948 /// as the iterator's first and only item.
949 fn range<K: AsRef<[u8]>, R: RangeBounds<K>>(
950 &self,
951 range: R,
952 seqno: SeqNo,
953 index: Option<(Arc<Memtable>, SeqNo)>,
954 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static>;
955
956 /// Returns a range iterator that can reposition (seek) in place without
957 /// reopening its per-SST readers.
958 ///
959 /// Unlike [`range`](Self::range)'s boxed `DoubleEndedIterator`, the returned
960 /// [`SeekableGuardIter`] additionally exposes `seek_to` / `seek_to_for_prev`,
961 /// so a consumer can jump a live iterator to any key (`RocksDB` `Seek` /
962 /// `SeekForPrev`) — enabling data-dependent scans (joins, skip-scan) without
963 /// reopening per-SST readers per jump.
964 ///
965 /// A snapshot at or below the retention floor (see
966 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
967 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
968 /// as the iterator's first and only item (also through `peek_key`); seeks
969 /// on such an iterator are no-ops.
970 fn range_seekable<K: AsRef<[u8]>, R: RangeBounds<K>>(
971 &self,
972 range: R,
973 seqno: SeqNo,
974 index: Option<(Arc<Memtable>, SeqNo)>,
975 ) -> Box<dyn SeekableGuardIter + 'static>;
976
977 /// Scans a sequence of disjoint, ascending key sub-intervals, reusing one set
978 /// of per-SST readers across all of them.
979 ///
980 /// The per-SST setup is paid once (when the underlying seekable iterator is
981 /// opened); each interval is served by repositioning that iterator. This
982 /// amortizes the setup across `N` intervals instead of paying it per
983 /// interval, so multi-interval scan throughput scales with the total rows
984 /// returned rather than the interval count.
985 ///
986 /// The interval source is pulled lazily, so intervals may be produced on
987 /// demand (e.g. computed from rows already returned).
988 ///
989 /// A snapshot at or below the retention floor (see
990 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
991 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
992 /// as the iterator's first and only item.
993 fn batch_range_scan<K: AsRef<[u8]>, R: RangeBounds<K> + 'static, I: IntoIterator<Item = R>>(
994 &self,
995 intervals: I,
996 seqno: SeqNo,
997 index: Option<(Arc<Memtable>, SeqNo)>,
998 ) -> Box<dyn Iterator<Item = IterGuardImpl> + Send + 'static>
999 where
1000 I::IntoIter: Send + 'static;
1001
1002 /// Returns the approximate number of tombstones in the tree.
1003 fn tombstone_count(&self) -> u64;
1004
1005 /// Returns the approximate number of weak tombstones (single deletes) in the tree.
1006 fn weak_tombstone_count(&self) -> u64;
1007
1008 /// Returns the approximate number of values reclaimable once weak tombstones can be GC'd.
1009 fn weak_tombstone_reclaimable_count(&self) -> u64;
1010
1011 /// Drops all tables and clears all memtables atomically.
1012 ///
1013 /// # Errors
1014 ///
1015 /// Will return `Err` only if an IO error occurs.
1016 fn clear(&self) -> crate::Result<()>;
1017
1018 /// Performs major compaction, blocking the caller until it's done.
1019 ///
1020 /// Returns a [`crate::compaction::CompactionResult`] describing what action was taken.
1021 ///
1022 /// # GC watermark (`gc_watermark`)
1023 ///
1024 /// `gc_watermark` is the one number that decides what history the engine's
1025 /// own GC may collect: every snapshot at or above it that is still servable
1026 /// (above the current retention floor) stays readable, and a version only
1027 /// snapshots below it could see may be dropped. It never lowers the floor:
1028 /// a snapshot an earlier run already refused stays refused. Concretely,
1029 /// only entries whose seqno is `< gc_watermark` are eligible for:
1030 ///
1031 /// - dropping shadowed versions / GC-ing tombstones, and
1032 /// - **folding merge operands** via the [`crate::MergeOperator`]: a key written
1033 /// only through [`Self::merge`] (no base value) accumulates one operand per
1034 /// call, and reads re-apply the whole chain (`O(operands)` per read) until
1035 /// compaction folds it. Folding a chain into a single value is only
1036 /// MVCC-safe when no live snapshot reads *between* the operands, which is
1037 /// exactly what `gc_watermark` certifies.
1038 ///
1039 /// The engine does **not** track snapshots (unlike a `RocksDB`-style
1040 /// snapshot list); the caller owns snapshot lifecycle and must supply this
1041 /// watermark:
1042 ///
1043 /// - To fold/GC everything (no active snapshots), pass a value **above every
1044 /// live seqno** (e.g. the next value from the [`crate::SequenceNumberCounter`]).
1045 /// - With outstanding snapshots, pass the **oldest** snapshot's seqno so their
1046 /// reads stay correct.
1047 /// - `gc_watermark == 0` certifies nothing as collapsible, so **no folding
1048 /// or GC happens**: `major_compact(target, 0)` only restructures tables
1049 /// and leaves a merge-only key's full operand chain intact.
1050 ///
1051 /// A run that collects anything raises the retention floor to
1052 /// `gc_watermark - 1` (capped at the install's own seqno) unless it is
1053 /// already higher. When the opt-in tight-space mode takes over a run
1054 /// (`RuntimeConfig::tight_space_compaction`), each slice raises it that
1055 /// far whether or not it collected. The floor is the
1056 /// only read boundary: from then on a new read at or below it (see
1057 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) fails with
1058 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention),
1059 /// which is why the watermark must not exceed the oldest snapshot still in
1060 /// use. A reader opened before the install keeps its own version, and the
1061 /// tables it references, until it is dropped.
1062 ///
1063 /// A [compaction filter](crate::compaction::filter) is the exception: it
1064 /// acts on whatever version it is shown, regardless of `gc_watermark`
1065 /// (`0` included). A run whose filter removed or rewrote anything raises
1066 /// the floor to the install's own seqno, so every snapshot up to it is
1067 /// refused from then on, however low the watermark was.
1068 ///
1069 /// # Errors
1070 ///
1071 /// Will return `Err` if an IO error occurs.
1072 fn major_compact(
1073 &self,
1074 target_size: u64,
1075 gc_watermark: SeqNo,
1076 ) -> crate::Result<crate::compaction::CompactionResult>;
1077
1078 /// Returns the disk space used by stale blobs.
1079 fn stale_blob_bytes(&self) -> u64 {
1080 0
1081 }
1082
1083 /// Gets the space usage of all filters in the tree.
1084 ///
1085 /// May not correspond to the actual memory size because filter blocks may be paged out.
1086 fn filter_size(&self) -> u64;
1087
1088 /// Gets the memory usage of all pinned filters in the tree.
1089 fn pinned_filter_size(&self) -> usize;
1090
1091 /// The serialised and the resident bytes of the tree's filters, and how
1092 /// they stand against a [`FilterAdvisor`](crate::config::FilterAdvisor)
1093 /// budget.
1094 ///
1095 /// Looks up each live table's filter blocks in the block cache without
1096 /// counting a hit.
1097 ///
1098 /// # Examples
1099 ///
1100 /// ```
1101 /// use lsm_tree::{AbstractTree, Config, config::FilterAdvisor};
1102 ///
1103 /// # let folder = tempfile::tempdir()?;
1104 /// let tree = Config::new(&folder, Default::default(), Default::default())
1105 /// .filter_advisor(Some(FilterAdvisor::new(64 * 1_024)))
1106 /// .open()?;
1107 /// tree.insert("a", "value", 0);
1108 /// tree.flush_active_memtable(0)?;
1109 ///
1110 /// let memory = tree.filter_memory();
1111 /// assert_eq!(memory.serialised_bytes, tree.filter_size());
1112 /// assert_eq!(memory.budget_bytes, Some(64 * 1_024));
1113 /// assert!(!memory.over_budget);
1114 /// # Ok::<(), lsm_tree::Error>(())
1115 /// ```
1116 fn filter_memory(&self) -> crate::FilterMemory;
1117
1118 /// Gets the memory usage of all pinned index blocks in the tree.
1119 fn pinned_block_index_size(&self) -> usize;
1120
1121 /// Returns the metrics structure.
1122 #[cfg(feature = "metrics")]
1123 fn metrics(&self) -> &Arc<crate::Metrics>;
1124
1125 /// A point-in-time [`CacheStats`](crate::CacheStats) snapshot of block-cache
1126 /// effectiveness (cumulative hit / miss counts and rate) and occupancy
1127 /// (current size against capacity).
1128 ///
1129 /// The stable, owned observability view over the block cache, so a consumer
1130 /// can read cache health without holding the mutable
1131 /// [`metrics`](Self::metrics) handle. Counts are cumulative since process
1132 /// start; derive a rate over an interval from the delta between two polls.
1133 #[cfg(feature = "metrics")]
1134 fn cache_stats(&self) -> crate::CacheStats;
1135
1136 /// Acquires the flush lock which is required to call [`Tree::flush`](crate::Tree::flush).
1137 fn get_flush_lock(&self) -> FlushGuard<'_>;
1138
1139 /// Synchronously flushes a memtable to a table.
1140 ///
1141 /// This method will not make the table immediately available,
1142 /// use [`AbstractTree::register_tables`] for that.
1143 ///
1144 /// Alongside the files comes a [`WritePin`](crate::runtime_config::WritePin)
1145 /// on the compression dictionaries they were written against. Keep it until
1146 /// `register_tables` returns: until then no installed file names those
1147 /// dictionaries, and a collection in between would take them.
1148 ///
1149 /// # Errors
1150 ///
1151 /// Will return `Err` if an IO error occurs.
1152 fn flush_to_tables(
1153 &self,
1154 stream: impl Iterator<Item = crate::Result<InternalValue>>,
1155 ) -> crate::Result<Option<FlushToTablesResult>> {
1156 // The stream's upper bound, when it tells one, is the most entries it
1157 // holds; the hashes over them are learnt from the first filter.
1158 let keys = stream
1159 .size_hint()
1160 .1
1161 .map_or(0, |upper| u64::try_from(upper).unwrap_or(u64::MAX));
1162 self.flush_to_tables_with_rt(stream, Vec::new(), keys, keys, None)
1163 }
1164
1165 /// Like [`AbstractTree::flush_to_tables`], but also writes range tombstones.
1166 /// `keys` bounds the distinct keys of `stream` from above and `hashes` the
1167 /// filter hashes over them, zero when unknown: a filter advisor keeps room
1168 /// for every table the flush writes by them. `overwritten`, when given,
1169 /// names the keys of `stream` its source held more than one version of.
1170 ///
1171 /// This is an internal extension hook on the crate's sealed tree types and
1172 /// is hidden from generated documentation.
1173 ///
1174 /// # Errors
1175 ///
1176 /// Will return `Err` if an IO error occurs.
1177 #[doc(hidden)]
1178 fn flush_to_tables_with_rt(
1179 &self,
1180 stream: impl Iterator<Item = crate::Result<InternalValue>>,
1181 range_tombstones: Vec<crate::range_tombstone::RangeTombstone>,
1182 keys: u64,
1183 hashes: u64,
1184 overwritten: Option<&crate::blob_tree::overwritten::OverwrittenKeys>,
1185 ) -> crate::Result<Option<FlushToTablesResult>>;
1186
1187 /// Atomically registers flushed tables into the tree, removing their associated sealed memtables.
1188 ///
1189 /// `gc_watermark` must be the same threshold the stream that produced
1190 /// `tables` ran with, and `collected_below_watermark` whether that stream
1191 /// actually dropped a version because of it. Together they are the
1192 /// install's retention effect: understating either admits reads whose
1193 /// answer the flush already collected, overstating `collected_below_watermark`
1194 /// refuses reads whose data is still on disk.
1195 ///
1196 /// # Errors
1197 ///
1198 /// Will return `Err` if an IO error occurs.
1199 fn register_tables(
1200 &self,
1201 tables: &[Table],
1202 blob_files: Option<&[BlobFile]>,
1203 frag_map: Option<crate::blob_tree::FragmentationMap>,
1204 sealed_memtables_to_delete: &[crate::tree::inner::MemtableId],
1205 gc_watermark: SeqNo,
1206 collected_below_watermark: bool,
1207 ) -> crate::Result<()>;
1208
1209 /// Clears the active memtable atomically.
1210 fn clear_active_memtable(&self);
1211
1212 /// Returns the number of sealed memtables.
1213 fn sealed_memtable_count(&self) -> usize;
1214
1215 /// Performs compaction on the tree's levels, blocking the caller until it's done.
1216 ///
1217 /// Returns a [`crate::compaction::CompactionResult`] describing what action was taken.
1218 ///
1219 /// `gc_watermark` has the meaning documented at
1220 /// [`major_compact`](Self::major_compact): the engine's own GC collects
1221 /// only versions below it, and a run that collects one raises the
1222 /// retention floor. Before the strategy chooses, the run unlinks every
1223 /// table a flushed range tombstone below the watermark spans whole and
1224 /// is newer than (see [`remove_range`](Self::remove_range)).
1225 ///
1226 /// Two things act regardless of the watermark: a compaction filter, and a
1227 /// strategy that drops whole tables (FIFO eviction, a custom strategy
1228 /// choosing to drop). Either removes data at any watermark, `0` included,
1229 /// and raises the floor to the install's own seqno, so a low watermark
1230 /// does not protect a snapshot from them.
1231 ///
1232 /// # Errors
1233 ///
1234 /// Will return `Err` if an IO error occurs.
1235 fn compact(
1236 &self,
1237 strategy: Arc<dyn crate::compaction::CompactionStrategy>,
1238 gc_watermark: SeqNo,
1239 ) -> crate::Result<crate::compaction::CompactionResult>;
1240
1241 /// Returns the next table's ID.
1242 fn get_next_table_id(&self) -> TableId;
1243
1244 /// Returns the tree config.
1245 fn tree_config(&self) -> &Config;
1246
1247 /// Returns the highest sequence number.
1248 fn get_highest_seqno(&self) -> Option<SeqNo> {
1249 let memtable_seqno = self.get_highest_memtable_seqno();
1250 let table_seqno = self.get_highest_persisted_seqno();
1251 memtable_seqno.max(table_seqno)
1252 }
1253
1254 /// Returns the active memtable.
1255 fn active_memtable(&self) -> Arc<Memtable>;
1256
1257 /// Whether the active memtable holds nothing, so that the next write makes
1258 /// one more flush owed. Sealed memtables do not count.
1259 ///
1260 /// Under `std` it takes no lock, unlike [`active_memtable`](Self::active_memtable),
1261 /// so it may be called from inside a version install, as a metered file
1262 /// system's admission is. A concurrent write can change the answer as it
1263 /// returns.
1264 ///
1265 /// # Examples
1266 ///
1267 /// ```
1268 /// use lsm_tree::{AbstractTree, Config, SequenceNumberCounter};
1269 /// # let folder = tempfile::tempdir()?;
1270 /// let tree = Config::new(
1271 /// &folder,
1272 /// SequenceNumberCounter::default(),
1273 /// SequenceNumberCounter::default(),
1274 /// )
1275 /// .open()?;
1276 /// assert!(tree.active_memtable_is_empty());
1277 ///
1278 /// tree.insert("key", "value", 0);
1279 /// assert!(!tree.active_memtable_is_empty());
1280 ///
1281 /// tree.rotate_memtable();
1282 /// assert!(tree.active_memtable_is_empty());
1283 /// # Ok::<(), lsm_tree::Error>(())
1284 /// ```
1285 fn active_memtable_is_empty(&self) -> bool;
1286
1287 /// Returns the tree type.
1288 fn tree_type(&self) -> crate::TreeType {
1289 if self.tree_config().kv_separation_opts.is_some() {
1290 crate::TreeType::Blob
1291 } else {
1292 crate::TreeType::Standard
1293 }
1294 }
1295
1296 /// Seals the active memtable.
1297 fn rotate_memtable(&self) -> Option<Arc<Memtable>>;
1298
1299 /// Returns the number of tables currently in the tree.
1300 fn table_count(&self) -> usize;
1301
1302 /// Returns the number of tables in `levels[idx]`.
1303 ///
1304 /// Returns `None` if the level does not exist (if idx >= 7).
1305 fn level_table_count(&self, idx: usize) -> Option<usize>;
1306
1307 /// Returns the number of disjoint runs in L0.
1308 ///
1309 /// Can be used to determine whether to write stall.
1310 fn l0_run_count(&self) -> usize;
1311
1312 /// Returns the number of blob files currently in the tree.
1313 fn blob_file_count(&self) -> usize;
1314
1315 /// Approximates the number of items in the tree.
1316 fn approximate_len(&self) -> usize;
1317
1318 /// Returns the disk space usage.
1319 fn disk_space(&self) -> u64;
1320
1321 /// Estimates the on-disk bytes and entry count contained in `range` at
1322 /// `seqno`, WITHOUT reading any data block.
1323 ///
1324 /// The estimate interpolates each overlapping SST's data-block offsets at
1325 /// the range boundaries (block granularity) and adds the active + sealed
1326 /// memtables' in-range share. For a KV-separated tree the per-SST blob
1327 /// bytes are apportioned by the same in-range fraction (blob files are not
1328 /// key-indexed, so a finer estimate is impossible without reading data).
1329 /// Intended for query planning (split-point selection, cost-based join
1330 /// ordering), not exact accounting; accuracy is typically within ~10-15%
1331 /// on roughly-uniform data.
1332 ///
1333 /// # Examples
1334 ///
1335 /// ```
1336 /// # use lsm_tree::Error as TreeError;
1337 /// use lsm_tree::{AbstractTree, Config};
1338 ///
1339 /// let folder = tempfile::tempdir()?;
1340 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1341 /// for i in 0..100u32 {
1342 /// tree.insert(format!("k{i:04}"), "value", 0);
1343 /// }
1344 /// tree.flush_active_memtable(0)?;
1345 ///
1346 /// // The full range covers every entry exactly once it is all flushed —
1347 /// // estimated without reading a single data block.
1348 /// let all = tree.approximate_range_stats::<&str, _>(.., 1)?;
1349 /// assert_eq!(all.key_count, tree.approximate_len() as u64);
1350 /// assert!(all.bytes > 0);
1351 ///
1352 /// // A range past every key is empty.
1353 /// let none = tree.approximate_range_stats("zzzz".."zzzzz", 1)?;
1354 /// assert_eq!(none.key_count, 0);
1355 /// assert_eq!(none.bytes, 0);
1356 /// #
1357 /// # Ok::<(), TreeError>(())
1358 /// ```
1359 ///
1360 /// # Errors
1361 ///
1362 /// Returns an error if a block index or table metadata read fails.
1363 fn approximate_range_stats<K: AsRef<[u8]>, R: RangeBounds<K>>(
1364 &self,
1365 range: R,
1366 seqno: SeqNo,
1367 ) -> crate::Result<crate::ApproximateRangeStats>;
1368
1369 /// Estimates the row cardinality and selectivity of `range` at `seqno`,
1370 /// WITHOUT reading any data block.
1371 ///
1372 /// Uses the per-data-block zone map (per-block row counts + key ranges) for a
1373 /// block-granularity row count: every data block whose key range overlaps the
1374 /// query contributes its recorded row count, plus the active + sealed
1375 /// memtables' in-range counts. When a table has no zone map the row count
1376 /// falls back to the byte-fraction estimate of [`approximate_range_stats`].
1377 /// Selectivity is `rows / total_rows`, monotonic in predicate tightness, for
1378 /// cost-based planning (join ordering, scan-vs-seek). Not exact accounting.
1379 ///
1380 /// [`approximate_range_stats`]: AbstractTree::approximate_range_stats
1381 ///
1382 /// # Examples
1383 ///
1384 /// ```
1385 /// # use lsm_tree::Error as TreeError;
1386 /// use lsm_tree::{AbstractTree, Config};
1387 ///
1388 /// let folder = tempfile::tempdir()?;
1389 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1390 /// for i in 0..100u32 {
1391 /// tree.insert(format!("k{i:04}"), "v", 0);
1392 /// }
1393 /// tree.flush_active_memtable(0)?;
1394 ///
1395 /// // The full range covers every row; selectivity is 1.0.
1396 /// let all = tree.approximate_range_cardinality::<&str, _>(.., 1)?;
1397 /// assert_eq!(all.rows, tree.approximate_len() as u64);
1398 /// assert!((all.selectivity - 1.0).abs() < 1e-9);
1399 ///
1400 /// // A tighter range selects fewer rows.
1401 /// let part = tree.approximate_range_cardinality("k0000".."k0050", 1)?;
1402 /// assert!(part.selectivity <= all.selectivity);
1403 /// #
1404 /// # Ok::<(), TreeError>(())
1405 /// ```
1406 ///
1407 /// # Errors
1408 ///
1409 /// Returns an error if a block index, zone map, or table metadata read fails.
1410 fn approximate_range_cardinality<K: AsRef<[u8]>, R: RangeBounds<K>>(
1411 &self,
1412 range: R,
1413 seqno: SeqNo,
1414 ) -> crate::Result<crate::RangeCardinality>;
1415
1416 /// Returns the highest sequence number of the active memtable.
1417 fn get_highest_memtable_seqno(&self) -> Option<SeqNo>;
1418
1419 /// Returns the highest sequence number that is flushed to disk.
1420 ///
1421 /// Under [`FlushDurability::Deferred`](crate::config::FlushDurability::Deferred)
1422 /// only the flushes a persist made durable count: a flush that is
1423 /// installed but not yet persisted would be lost by a power loss.
1424 fn get_highest_persisted_seqno(&self) -> Option<SeqNo>;
1425
1426 /// Returns the retention floor: the lower bound of the readable snapshot
1427 /// window.
1428 ///
1429 /// A snapshot is servable iff it is `0` (sees nothing) or strictly above
1430 /// this seqno; a new read at `0 < seqno <= oldest_retained_seqno()` fails
1431 /// with
1432 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention).
1433 /// A caller holding a long-lived snapshot (a lagging consumer, a
1434 /// point-in-time query) can validate it here before reading and report
1435 /// "history collected" instead of an unexpected error mid-scan. A reader
1436 /// opened before the floor rose is not affected: it keeps the version it
1437 /// resolved. A fresh tree reports `0`.
1438 ///
1439 /// The install that discards what older snapshots saw raises the floor in
1440 /// the same version edit, so the boundary is the same live and after a
1441 /// reopen: after a GC with watermark `w` it is `w - 1`, capped at the
1442 /// install's own seqno; after a `clear`, a table drop or a compaction
1443 /// whose filter transformed rows it is that install's seqno. A manifest
1444 /// rebuilt by [`Config::repair`](crate::Config::repair) cannot know what
1445 /// the lost manifest recorded and seeds the boundary from
1446 /// [`Config::repair_retention_floor`](crate::Config::repair_retention_floor)
1447 /// (default `0`: every snapshot served).
1448 ///
1449 /// # Examples
1450 ///
1451 /// ```
1452 /// # let folder = tempfile::tempdir()?;
1453 /// use lsm_tree::{AbstractTree, Config, Error, SequenceNumberCounter};
1454 ///
1455 /// let seqno = SequenceNumberCounter::default();
1456 /// let tree = Config::new(folder, seqno.clone(), Default::default()).open()?;
1457 /// assert_eq!(tree.oldest_retained_seqno(), 0);
1458 ///
1459 /// tree.insert("a", "v1", seqno.next());
1460 /// tree.flush_active_memtable(0)?;
1461 /// let stale = seqno.get();
1462 /// tree.insert("a", "v2", seqno.next());
1463 /// tree.flush_active_memtable(0)?;
1464 ///
1465 /// // A watermark above every live snapshot lets compaction collect the
1466 /// // old version; the boundary moves past the stale snapshot.
1467 /// tree.major_compact(u64::MAX, seqno.get())?;
1468 /// let oldest = tree.oldest_retained_seqno();
1469 /// assert!(stale <= oldest);
1470 /// assert!(matches!(
1471 /// tree.get("a", stale),
1472 /// Err(Error::SnapshotBelowRetention { .. })
1473 /// ));
1474 /// assert_eq!(tree.get("a", oldest + 1)?.as_deref(), Some(b"v2".as_slice()));
1475 /// #
1476 /// # Ok::<(), lsm_tree::Error>(())
1477 /// ```
1478 fn oldest_retained_seqno(&self) -> SeqNo;
1479
1480 /// Returns the persisted retention floor: the highest snapshot seqno the
1481 /// tree refuses, now and after a reopen. It always equals
1482 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno).
1483 ///
1484 /// This is the value a deployment records for a later manifest repair
1485 /// ([`Config::repair_retention_floor`](crate::Config::repair_retention_floor)):
1486 /// it already folds in every operation that moved the boundary (GC
1487 /// compactions, `clear`, table drops, filtering compactions, and
1488 /// [`lower_retention_floor`](Self::lower_retention_floor)), so no
1489 /// caller-side bookkeeping of watermarks is needed. `0` until the first
1490 /// such install.
1491 fn retention_floor(&self) -> SeqNo;
1492
1493 /// Lowers the persisted retention floor to `floor`, so snapshots above it
1494 /// are served again, now and after a reopen.
1495 ///
1496 /// For a rebuild from a source that kept history above its own floor:
1497 /// [`clear`](Self::clear) the tree, reinstall the source's history at its
1498 /// original seqnos, then lower the floor to the source's
1499 /// [`retention_floor`](Self::retention_floor). Until this call the clear's
1500 /// floor refuses every snapshot below it, so a reader never sees the
1501 /// history half reinstalled, and a crash before the call leaves that
1502 /// refusal in place.
1503 ///
1504 /// The tree cannot check the claim this call makes: that it holds every
1505 /// version above `floor` a snapshot above `floor` could see. That includes
1506 /// not collecting any of the reinstalled history with a GC watermark above
1507 /// `floor + 1` before the call.
1508 ///
1509 /// # Errors
1510 ///
1511 /// [`Error::RetentionFloorRaise`](crate::Error::RetentionFloorRaise) when
1512 /// `floor` is above the current floor, which only GC and the installs that
1513 /// drop data raise; nothing changes.
1514 /// [`Error::RetentionFloorAboveSeqno`](crate::Error::RetentionFloorAboveSeqno)
1515 /// when `floor` is above the seqno counter (one reset on reopen can stand
1516 /// below the current floor); nothing changes. An I/O error persisting the
1517 /// new floor.
1518 ///
1519 /// # Examples
1520 ///
1521 /// ```
1522 /// # let folder = tempfile::tempdir()?;
1523 /// use lsm_tree::{AbstractTree, Config, SequenceNumberCounter};
1524 ///
1525 /// let tree = Config::new(folder, SequenceNumberCounter::new(100), Default::default()).open()?;
1526 /// tree.clear()?;
1527 /// // Reinstall a source's history, which it kept above seqno 5.
1528 /// tree.insert("k", "v", 10);
1529 /// tree.lower_retention_floor(5)?;
1530 /// assert_eq!(tree.get("k", 11)?.as_deref(), Some(b"v".as_slice()));
1531 /// assert!(tree.get("k", 5).is_err());
1532 /// #
1533 /// # Ok::<(), lsm_tree::Error>(())
1534 /// ```
1535 fn lower_retention_floor(&self, floor: SeqNo) -> crate::Result<()>;
1536
1537 /// Returns the limiter that throttles this tree's compactions: the one
1538 /// supplied through
1539 /// [`Config::compaction_rate_limiter`](crate::Config::compaction_rate_limiter),
1540 /// which other trees may share, or the tree's own built from
1541 /// [`Config::compaction_rate_limit`](crate::Config::compaction_rate_limit).
1542 ///
1543 /// Change the rate while the tree runs, with no restart, through
1544 /// [`RateLimiter::set_rate`](crate::rate_limiter::RateLimiter::set_rate):
1545 /// every tree holding the limiter is bounded by the new rate from its next
1546 /// request.
1547 ///
1548 /// # Examples
1549 ///
1550 /// ```
1551 /// use lsm_tree::{AbstractTree, Config};
1552 ///
1553 /// let folder = tempfile::tempdir()?;
1554 /// let tree = Config::new(&folder, Default::default(), Default::default())
1555 /// .compaction_rate_limit(16 * 1_024 * 1_024)
1556 /// .open()?;
1557 /// tree.compaction_rate_limiter().set_rate(64 * 1_024 * 1_024);
1558 /// assert_eq!(tree.compaction_rate_limiter().rate(), 64 * 1_024 * 1_024);
1559 /// #
1560 /// # Ok::<(), lsm_tree::Error>(())
1561 /// ```
1562 fn compaction_rate_limiter(&self) -> alloc::sync::Arc<crate::rate_limiter::RateLimiter>;
1563
1564 /// Scans the entire tree, returning the number of items.
1565 ///
1566 /// ###### Caution
1567 ///
1568 /// This operation scans the entire tree: O(n) complexity!
1569 ///
1570 /// Never, under any circumstances, use .`len()` == 0 to check
1571 /// if the tree is empty, use [`Tree::is_empty`](crate::Tree::is_empty) instead.
1572 ///
1573 /// # Examples
1574 ///
1575 /// ```
1576 /// # use lsm_tree::Error as TreeError;
1577 /// use lsm_tree::{AbstractTree, Config, Tree};
1578 ///
1579 /// let folder = tempfile::tempdir()?;
1580 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1581 ///
1582 /// assert_eq!(tree.len(0, None)?, 0);
1583 /// tree.insert("1", "abc", 0);
1584 /// tree.insert("3", "abc", 1);
1585 /// tree.insert("5", "abc", 2);
1586 /// assert_eq!(tree.len(3, None)?, 3);
1587 /// #
1588 /// # Ok::<(), TreeError>(())
1589 /// ```
1590 ///
1591 /// # Errors
1592 ///
1593 /// Will return `Err` if an IO error occurs.
1594 fn len(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<usize> {
1595 let mut count = 0;
1596
1597 for item in self.iter(seqno, index) {
1598 let _ = item.key()?;
1599 count += 1;
1600 }
1601
1602 Ok(count)
1603 }
1604
1605 /// Returns `true` if the tree is empty.
1606 ///
1607 /// This operation has O(log N) complexity.
1608 ///
1609 /// # Examples
1610 ///
1611 /// ```
1612 /// # let folder = tempfile::tempdir()?;
1613 /// use lsm_tree::{AbstractTree, Config, Tree};
1614 ///
1615 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1616 /// assert!(tree.is_empty(0, None)?);
1617 ///
1618 /// tree.insert("a", "abc", 0);
1619 /// assert!(!tree.is_empty(1, None)?);
1620 /// #
1621 /// # Ok::<(), lsm_tree::Error>(())
1622 /// ```
1623 ///
1624 /// # Errors
1625 ///
1626 /// Will return `Err` if an IO error occurs.
1627 fn is_empty(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<bool> {
1628 Ok(self
1629 .first_key_value(seqno, index)
1630 .map(crate::Guard::key)
1631 .transpose()?
1632 .is_none())
1633 }
1634
1635 /// Returns the first key-value pair in the tree.
1636 /// The key in this pair is the minimum key in the tree.
1637 ///
1638 /// # Examples
1639 ///
1640 /// ```
1641 /// # use lsm_tree::Error as TreeError;
1642 /// # use lsm_tree::{AbstractTree, Config, Tree, Guard};
1643 /// #
1644 /// # let folder = tempfile::tempdir()?;
1645 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1646 ///
1647 /// tree.insert("1", "abc", 0);
1648 /// tree.insert("3", "abc", 1);
1649 /// tree.insert("5", "abc", 2);
1650 ///
1651 /// let key = tree.first_key_value(3, None).expect("item should exist").key()?;
1652 /// assert_eq!(&*key, "1".as_bytes());
1653 /// #
1654 /// # Ok::<(), TreeError>(())
1655 /// ```
1656 ///
1657 /// # Errors
1658 ///
1659 /// Will return `Err` if an IO error occurs.
1660 fn first_key_value(
1661 &self,
1662 seqno: SeqNo,
1663 index: Option<(Arc<Memtable>, SeqNo)>,
1664 ) -> Option<IterGuardImpl> {
1665 self.iter(seqno, index).next()
1666 }
1667
1668 /// Returns the last key-value pair in the tree.
1669 /// The key in this pair is the maximum key in the tree.
1670 ///
1671 /// # Examples
1672 ///
1673 /// ```
1674 /// # use lsm_tree::Error as TreeError;
1675 /// # use lsm_tree::{AbstractTree, Config, Tree, Guard};
1676 /// #
1677 /// # let folder = tempfile::tempdir()?;
1678 /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1679 /// #
1680 /// tree.insert("1", "abc", 0);
1681 /// tree.insert("3", "abc", 1);
1682 /// tree.insert("5", "abc", 2);
1683 ///
1684 /// let key = tree.last_key_value(3, None).expect("item should exist").key()?;
1685 /// assert_eq!(&*key, "5".as_bytes());
1686 /// #
1687 /// # Ok::<(), TreeError>(())
1688 /// ```
1689 ///
1690 /// # Errors
1691 ///
1692 /// Will return `Err` if an IO error occurs.
1693 fn last_key_value(
1694 &self,
1695 seqno: SeqNo,
1696 index: Option<(Arc<Memtable>, SeqNo)>,
1697 ) -> Option<IterGuardImpl> {
1698 self.iter(seqno, index).next_back()
1699 }
1700
1701 /// Returns the size of a value if it exists.
1702 ///
1703 /// # Examples
1704 ///
1705 /// ```
1706 /// # let folder = tempfile::tempdir()?;
1707 /// use lsm_tree::{AbstractTree, Config, Tree};
1708 ///
1709 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1710 /// tree.insert("a", "my_value", 0);
1711 ///
1712 /// let size = tree.size_of("a", 1)?.unwrap_or_default();
1713 /// assert_eq!("my_value".len() as u32, size);
1714 ///
1715 /// let size = tree.size_of("b", 1)?.unwrap_or_default();
1716 /// assert_eq!(0, size);
1717 /// #
1718 /// # Ok::<(), lsm_tree::Error>(())
1719 /// ```
1720 ///
1721 /// # Errors
1722 ///
1723 /// Will return `Err` if an IO error occurs.
1724 fn size_of<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<u32>>;
1725
1726 /// Retrieves an item from the tree.
1727 ///
1728 /// # Examples
1729 ///
1730 /// ```
1731 /// # let folder = tempfile::tempdir()?;
1732 /// use lsm_tree::{AbstractTree, Config, Tree};
1733 ///
1734 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1735 /// tree.insert("a", "my_value", 0);
1736 ///
1737 /// let item = tree.get("a", 1)?;
1738 /// assert_eq!(Some("my_value".as_bytes().into()), item);
1739 /// #
1740 /// # Ok::<(), lsm_tree::Error>(())
1741 /// ```
1742 ///
1743 /// # Errors
1744 ///
1745 /// Will return `Err` if an IO error occurs, or
1746 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
1747 /// when `seqno` is at or below the retention floor (see
1748 /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)). The same applies
1749 /// to every read that resolves a snapshot: [`get_pinned`](Self::get_pinned),
1750 /// [`contains_key`](Self::contains_key), [`size_of`](Self::size_of),
1751 /// [`multi_get`](Self::multi_get), [`len`](Self::len),
1752 /// [`is_empty`](Self::is_empty), [`first_key_value`](Self::first_key_value),
1753 /// [`last_key_value`](Self::last_key_value),
1754 /// [`approximate_range_stats`](Self::approximate_range_stats) and
1755 /// [`approximate_range_cardinality`](Self::approximate_range_cardinality).
1756 fn get<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<UserValue>>;
1757
1758 /// Retrieves an item from the tree as a [`PinnableSlice`](crate::PinnableSlice).
1759 ///
1760 /// When the value is backed by an on-disk data block, implementations
1761 /// may return [`PinnableSlice::Pinned`](crate::PinnableSlice::Pinned) holding a reference to that block's
1762 /// decompressed buffer (avoiding a data copy). Memtable and blob-resolved
1763 /// values use [`PinnableSlice::Owned`](crate::PinnableSlice::Owned). The default implementation always
1764 /// returns `Owned`; only [`Tree`](crate::Tree) overrides with the pinned path.
1765 ///
1766 /// The existing [`AbstractTree::get`] method is unaffected.
1767 ///
1768 /// # Examples
1769 ///
1770 /// ```
1771 /// # let folder = tempfile::tempdir()?;
1772 /// use lsm_tree::{AbstractTree, Config, Tree};
1773 ///
1774 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1775 /// tree.insert("a", "my_value", 0);
1776 ///
1777 /// let item = tree.get_pinned("a", 1)?;
1778 /// assert_eq!(item.as_ref().map(|v| v.as_ref()), Some("my_value".as_bytes()));
1779 /// #
1780 /// # Ok::<(), lsm_tree::Error>(())
1781 /// ```
1782 ///
1783 /// # Errors
1784 ///
1785 /// Will return `Err` if an IO error occurs.
1786 fn get_pinned<K: AsRef<[u8]>>(
1787 &self,
1788 key: K,
1789 seqno: SeqNo,
1790 ) -> crate::Result<Option<crate::PinnableSlice>> {
1791 // Default: delegate to get() and wrap as Owned
1792 self.get(key, seqno)
1793 .map(|opt| opt.map(crate::PinnableSlice::owned))
1794 }
1795
1796 /// Returns `true` if the tree contains the specified key.
1797 ///
1798 /// # Examples
1799 ///
1800 /// ```
1801 /// # let folder = tempfile::tempdir()?;
1802 /// # use lsm_tree::{AbstractTree, Config, Tree};
1803 /// #
1804 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1805 /// assert!(!tree.contains_key("a", 0)?);
1806 ///
1807 /// tree.insert("a", "abc", 0);
1808 /// assert!(tree.contains_key("a", 1)?);
1809 /// #
1810 /// # Ok::<(), lsm_tree::Error>(())
1811 /// ```
1812 ///
1813 /// # Errors
1814 ///
1815 /// Will return `Err` if an IO error occurs.
1816 fn contains_key<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<bool> {
1817 self.get(key, seqno).map(|x| x.is_some())
1818 }
1819
1820 /// Returns `true` if the tree contains any key with the given prefix.
1821 ///
1822 /// This is a convenience method that checks whether the corresponding
1823 /// prefix iterator yields at least one item, while surfacing any IO
1824 /// errors via the `Result` return type. Implementations may override
1825 /// this method to provide a more efficient prefix-existence check.
1826 ///
1827 /// # Examples
1828 ///
1829 /// ```
1830 /// # let folder = tempfile::tempdir()?;
1831 /// use lsm_tree::{AbstractTree, Config, Tree};
1832 ///
1833 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1834 /// assert!(!tree.contains_prefix("abc", 0, None)?);
1835 ///
1836 /// tree.insert("abc:1", "value", 0);
1837 /// assert!(tree.contains_prefix("abc", 1, None)?);
1838 /// assert!(!tree.contains_prefix("xyz", 1, None)?);
1839 /// #
1840 /// # Ok::<(), lsm_tree::Error>(())
1841 /// ```
1842 ///
1843 /// # Errors
1844 ///
1845 /// Will return `Err` if an IO error occurs.
1846 fn contains_prefix<K: AsRef<[u8]>>(
1847 &self,
1848 prefix: K,
1849 seqno: SeqNo,
1850 index: Option<(Arc<Memtable>, SeqNo)>,
1851 ) -> crate::Result<bool> {
1852 match self.prefix(prefix, seqno, index).next() {
1853 Some(guard) => guard.key().map(|_| true),
1854 None => Ok(false),
1855 }
1856 }
1857
1858 /// Reads multiple keys from the tree.
1859 ///
1860 /// Implementations may choose to perform all lookups against a single
1861 /// version snapshot and acquire the version lock only once, which can be
1862 /// more efficient than calling [`AbstractTree::get`] in a loop. The
1863 /// default trait implementation, however, is a convenience wrapper that
1864 /// simply calls [`AbstractTree::get`] for each key and therefore does not
1865 /// guarantee a single-snapshot or single-lock acquisition. Optimized
1866 /// implementations (such as [`Tree`](crate::Tree) and
1867 /// [`BlobTree`](crate::BlobTree)) provide the
1868 /// single-snapshot/one-lock behavior.
1869 ///
1870 /// # Examples
1871 ///
1872 /// ```
1873 /// # let folder = tempfile::tempdir()?;
1874 /// use lsm_tree::{AbstractTree, Config, Tree};
1875 ///
1876 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1877 /// tree.insert("a", "value_a", 0);
1878 /// tree.insert("b", "value_b", 1);
1879 ///
1880 /// let results = tree.multi_get(["a", "b", "c"], 2)?;
1881 /// assert_eq!(results[0], Some("value_a".as_bytes().into()));
1882 /// assert_eq!(results[1], Some("value_b".as_bytes().into()));
1883 /// assert_eq!(results[2], None);
1884 /// #
1885 /// # Ok::<(), lsm_tree::Error>(())
1886 /// ```
1887 ///
1888 /// # Errors
1889 ///
1890 /// Will return `Err` if an IO error occurs.
1891 fn multi_get<K: AsRef<[u8]>>(
1892 &self,
1893 keys: impl IntoIterator<Item = K>,
1894 seqno: SeqNo,
1895 ) -> crate::Result<Vec<Option<UserValue>>> {
1896 keys.into_iter().map(|key| self.get(key, seqno)).collect()
1897 }
1898
1899 /// Applies a [`WriteBatch`](crate::WriteBatch) with the given sequence number.
1900 ///
1901 /// All entries share a single seqno. This is more efficient than individual
1902 /// writes because the version-history lock and memtable size accounting
1903 /// are performed only once for the entire batch.
1904 ///
1905 /// **Visibility:** entries become individually visible to concurrent readers
1906 /// as they are inserted. For atomic batch visibility, the caller must
1907 /// publish `seqno` (via `visible_seqno.fetch_max(seqno + 1)`) only
1908 /// **after** this method returns.
1909 ///
1910 /// Returns the total bytes added and new size of the memtable.
1911 ///
1912 /// # Examples
1913 ///
1914 /// ```
1915 /// # let folder = tempfile::tempdir()?;
1916 /// use lsm_tree::{AbstractTree, Config, Tree, WriteBatch};
1917 ///
1918 /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1919 ///
1920 /// let mut batch = WriteBatch::new();
1921 /// batch.insert("key1", "value1");
1922 /// batch.insert("key2", "value2");
1923 /// batch.remove("key3");
1924 ///
1925 /// let (bytes_added, memtable_size) = tree.apply_batch(batch, 0)?;
1926 /// assert!(bytes_added > 0);
1927 /// #
1928 /// # Ok::<(), lsm_tree::Error>(())
1929 /// ```
1930 ///
1931 /// # Errors
1932 ///
1933 /// Returns [`Error::MixedOperationBatch`](crate::Error::MixedOperationBatch)
1934 /// if the batch contains mixed operation types for the same user key.
1935 fn apply_batch(&self, batch: crate::WriteBatch, seqno: SeqNo) -> crate::Result<(u64, u64)>;
1936
1937 /// Inserts a key-value pair into the tree.
1938 ///
1939 /// If the key already exists, the item will be overwritten.
1940 ///
1941 /// Returns the added item's size and new size of the memtable.
1942 ///
1943 /// # Examples
1944 ///
1945 /// ```
1946 /// # let folder = tempfile::tempdir()?;
1947 /// use lsm_tree::{AbstractTree, Config, Tree};
1948 ///
1949 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1950 /// tree.insert("a", "abc", 0);
1951 /// #
1952 /// # Ok::<(), lsm_tree::Error>(())
1953 /// ```
1954 ///
1955 /// # Errors
1956 ///
1957 /// Will return `Err` if an IO error occurs.
1958 fn insert<K: Into<UserKey>, V: Into<UserValue>>(
1959 &self,
1960 key: K,
1961 value: V,
1962 seqno: SeqNo,
1963 ) -> (u64, u64);
1964
1965 /// Admission-gated [`insert`](Self::insert): consults
1966 /// [`write_admission`](Self::write_admission) first and declines with
1967 /// [`Error::StorageFull`](crate::Error::StorageFull) when the tree is over
1968 /// budget, otherwise inserts and returns the same `(added_bytes,
1969 /// memtable_size)` tuple. This is the write entry a space-aware caller uses
1970 /// so over-budget writes are refused up front rather than failing a flush
1971 /// later; bare [`insert`](Self::insert) stays infallible for callers that
1972 /// do not opt into admission control.
1973 ///
1974 /// # Errors
1975 ///
1976 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1977 /// is closed.
1978 fn try_insert<K: Into<UserKey>, V: Into<UserValue>>(
1979 &self,
1980 key: K,
1981 value: V,
1982 seqno: SeqNo,
1983 ) -> crate::Result<(u64, u64)> {
1984 self.write_admission()?;
1985 Ok(self.insert(key, value, seqno))
1986 }
1987
1988 /// Admission-gated [`merge`](Self::merge). See
1989 /// [`try_insert`](Self::try_insert).
1990 ///
1991 /// # Errors
1992 ///
1993 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1994 /// is closed.
1995 fn try_merge<K: Into<UserKey>, V: Into<UserValue>>(
1996 &self,
1997 key: K,
1998 operand: V,
1999 seqno: SeqNo,
2000 ) -> crate::Result<(u64, u64)> {
2001 self.write_admission()?;
2002 Ok(self.merge(key, operand, seqno))
2003 }
2004
2005 /// Admission-gated [`remove`](Self::remove). See
2006 /// [`try_insert`](Self::try_insert).
2007 ///
2008 /// Note a tombstone is itself a write that consumes space; an over-budget
2009 /// tree refuses it too. Reclaim space via compaction (never gated) rather
2010 /// than relying on deletes when already read-only.
2011 ///
2012 /// # Errors
2013 ///
2014 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
2015 /// is closed.
2016 fn try_remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> crate::Result<(u64, u64)> {
2017 self.write_admission()?;
2018 Ok(self.remove(key, seqno))
2019 }
2020
2021 /// Admission-gated [`remove_weak`](Self::remove_weak). See
2022 /// [`try_insert`](Self::try_insert).
2023 ///
2024 /// # Errors
2025 ///
2026 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
2027 /// is closed.
2028 fn try_remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> crate::Result<(u64, u64)> {
2029 self.write_admission()?;
2030 Ok(self.remove_weak(key, seqno))
2031 }
2032
2033 /// Admission-gated [`remove_range`](Self::remove_range). See
2034 /// [`try_insert`](Self::try_insert).
2035 ///
2036 /// # Errors
2037 ///
2038 /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
2039 /// is closed.
2040 fn try_remove_range<K: Into<UserKey>>(
2041 &self,
2042 start: K,
2043 end: K,
2044 seqno: SeqNo,
2045 ) -> crate::Result<u64> {
2046 self.write_admission()?;
2047 Ok(self.remove_range(start, end, seqno))
2048 }
2049
2050 /// Removes an item from the tree.
2051 ///
2052 /// Returns the added item's size and new size of the memtable.
2053 ///
2054 /// # Examples
2055 ///
2056 /// ```
2057 /// # let folder = tempfile::tempdir()?;
2058 /// # use lsm_tree::{AbstractTree, Config, Tree};
2059 /// #
2060 /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
2061 /// tree.insert("a", "abc", 0);
2062 ///
2063 /// let item = tree.get("a", 1)?.expect("should have item");
2064 /// assert_eq!("abc".as_bytes(), &*item);
2065 ///
2066 /// tree.remove("a", 1);
2067 ///
2068 /// let item = tree.get("a", 2)?;
2069 /// assert_eq!(None, item);
2070 /// #
2071 /// # Ok::<(), lsm_tree::Error>(())
2072 /// ```
2073 ///
2074 /// # Errors
2075 ///
2076 /// Will return `Err` if an IO error occurs.
2077 fn remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64);
2078
2079 /// Writes a merge operand for a key.
2080 ///
2081 /// The operand is stored as a partial update that will be combined with
2082 /// other operands and/or a base value via the configured [`crate::MergeOperator`]
2083 /// during reads and compaction.
2084 ///
2085 /// Returns the added item's size and new size of the memtable.
2086 ///
2087 /// # Examples
2088 ///
2089 /// ```
2090 /// # let folder = tempfile::tempdir()?;
2091 /// # use lsm_tree::{AbstractTree, Config, MergeOperator, UserValue};
2092 /// # use std::sync::Arc;
2093 /// # struct SumMerge;
2094 /// # impl MergeOperator for SumMerge {
2095 /// # fn merge(&self, _key: &[u8], base: Option<&[u8]>, operands: &[&[u8]]) -> lsm_tree::Result<UserValue> {
2096 /// # let mut sum: i64 = base.map_or(0, |b| i64::from_le_bytes(b.try_into().unwrap()));
2097 /// # for op in operands { sum += i64::from_le_bytes((*op).try_into().unwrap()); }
2098 /// # Ok(sum.to_le_bytes().to_vec().into())
2099 /// # }
2100 /// # }
2101 /// # let tree = Config::new(folder, Default::default(), Default::default())
2102 /// # .with_merge_operator(Some(Arc::new(SumMerge)))
2103 /// # .open()?;
2104 /// tree.merge("counter", 1_i64.to_le_bytes(), 0);
2105 /// # Ok::<(), lsm_tree::Error>(())
2106 /// ```
2107 fn merge<K: Into<UserKey>, V: Into<UserValue>>(
2108 &self,
2109 key: K,
2110 operand: V,
2111 seqno: SeqNo,
2112 ) -> (u64, u64);
2113
2114 /// Removes an item from the tree.
2115 ///
2116 /// The tombstone marker of this delete operation will vanish when it
2117 /// collides with its corresponding insertion.
2118 /// This may cause older versions of the value to be resurrected, so it should
2119 /// only be used and preferred in scenarios where a key is only ever written once.
2120 ///
2121 /// Returns the added item's size and new size of the memtable.
2122 ///
2123 /// # Examples
2124 ///
2125 /// ```
2126 /// # let folder = tempfile::tempdir()?;
2127 /// # use lsm_tree::{AbstractTree, Config, Tree};
2128 /// #
2129 /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
2130 /// tree.insert("a", "abc", 0);
2131 ///
2132 /// let item = tree.get("a", 1)?.expect("should have item");
2133 /// assert_eq!("abc".as_bytes(), &*item);
2134 ///
2135 /// tree.remove_weak("a", 1);
2136 ///
2137 /// let item = tree.get("a", 2)?;
2138 /// assert_eq!(None, item);
2139 /// #
2140 /// # Ok::<(), lsm_tree::Error>(())
2141 /// ```
2142 ///
2143 /// # Errors
2144 ///
2145 /// Will return `Err` if an IO error occurs.
2146 #[doc(hidden)]
2147 fn remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64);
2148
2149 /// Deletes all keys in the range `[start, end)` by inserting a range tombstone.
2150 ///
2151 /// This is much more efficient than deleting keys individually when
2152 /// removing a contiguous range of keys. The deletion is exact at once,
2153 /// whatever tables hold the keys; a snapshot below `seqno` still reads them.
2154 ///
2155 /// Space comes back without rewriting: once the tombstone is flushed, every
2156 /// compaction whose `gc_watermark` is above `seqno` first unlinks each
2157 /// table the tombstone spans whole and is newer than, before its strategy
2158 /// chooses. Tables that reach past `[start, end)` keep their bytes until a
2159 /// merge rewrites them. Dropping tables raises the retention floor, at most
2160 /// to `seqno`: snapshots at or below the floor report
2161 /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention).
2162 ///
2163 /// Nothing is rewritten only where the range ends at table boundaries,
2164 /// which holds when keys arrive in the order of the range (a time series
2165 /// expiring its oldest days); a range cutting through tables leaves their
2166 /// survivors to the merge that rewrites them. The deletion covers this
2167 /// tree only: a caller indexing these keys elsewhere removes them there.
2168 ///
2169 /// To empty the whole tree, [`clear`](Self::clear) does it without a
2170 /// tombstone.
2171 ///
2172 /// Returns the approximate size added to the memtable.
2173 /// Returns 0 if `start >= end` (invalid interval is silently ignored).
2174 ///
2175 /// This is a required method on the crate's sealed tree types.
2176 fn remove_range<K: Into<UserKey>>(&self, start: K, end: K, seqno: SeqNo) -> u64;
2177
2178 /// Deletes all keys with the given prefix by inserting a range tombstone.
2179 ///
2180 /// This is sugar over [`AbstractTree::remove_range`] using prefix bounds.
2181 ///
2182 /// Returns the approximate size added to the memtable.
2183 /// Returns 0 for empty prefixes or all-`0xFF` prefixes (cannot form valid half-open range).
2184 fn remove_prefix<K: AsRef<[u8]>>(&self, prefix: K, seqno: SeqNo) -> u64 {
2185 use crate::range::prefix_to_range;
2186 use core::ops::Bound;
2187
2188 let (lo, hi) = prefix_to_range(prefix.as_ref());
2189
2190 let Bound::Included(start) = lo else { return 0 };
2191
2192 // Bound::Unbounded means the prefix is all 0xFF — no representable
2193 // exclusive upper bound exists, so we cannot form a valid range tombstone.
2194 let Bound::Excluded(end) = hi else { return 0 };
2195
2196 self.remove_range(start, end, seqno)
2197 }
2198}