Skip to main content

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}