Skip to main content

nodedb_lite/storage/
columnar_segment_ext.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! `ColumnarSegmentExt` — pagedb segment operations for columnar segment data.
4//!
5//! The `StorageEngine` trait handles sparse, sorted, key-value state.  For
6//! columnar segment bytes (potentially large per-segment blobs, sequentially
7//! read on cold-open restore) pagedb segments are the appropriate backing.
8//! Moving large compressed-column data off the B+ tree reduces write
9//! amplification and allows pagedb's AEAD encryption to be applied at the
10//! segment granularity rather than as opaque blobs.
11//!
12//! The split:
13//! - **pagedb segment**: `{collection}:seg:{id}` bytes produced by
14//!   `nodedb_columnar::SegmentWriter`.  These are potentially large and
15//!   immutable once written.
16//! - **B+ tree** (`Namespace::Columnar`): segment metadata list
17//!   (`{collection}:meta`), delete bitmaps (`{collection}:del:{id}`), and
18//!   schema entries.  All small and point-lookup hot.
19//!
20//! Segment metadata already carries the full list of segment IDs, so no
21//! auxiliary sentinel index is needed (unlike the FTS path which lacks an
22//! equivalent enumeration index).
23//!
24//! Only compiled on non-WASM targets.  WASM stays on the KV blob path.
25
26use crate::error::LiteError;
27
28/// Extension trait: write, open, and delete columnar segment data backed by
29/// pagedb encrypted segment files.
30///
31/// One segment per `(collection, segment_id)` pair.  The segment contains the
32/// serialized columnar bytes exactly as produced by
33/// `nodedb_columnar::SegmentWriter` — no additional framing beyond the 8-byte
34/// length prefix envelope used to survive pagedb's page-boundary padding.
35///
36/// Segment metadata (the ordered list of segment IDs with row counts),
37/// delete bitmaps, and collection schemas remain on the B+ tree
38/// (`Namespace::Columnar` and `Namespace::Meta`) — they are small and
39/// point-lookup hot.
40///
41/// This trait is object-safe so `StorageEngine` implementations can return
42/// `Option<&dyn ColumnarSegmentExt>` via `as_columnar_segment_ext()`.
43#[async_trait::async_trait]
44pub trait ColumnarSegmentExt: Send + Sync {
45    /// Write the segment bytes for `(collection, segment_id)`.
46    ///
47    /// Chunks the length-prefixed payload into 4 KiB pagedb pages, creates a
48    /// new encrypted segment, and links it under
49    /// `col/seg/{collection}/{segment_id}`.  If a segment already exists under
50    /// that name it is atomically replaced (old segment is tombstoned and
51    /// reaped by the next `Db::gc_now` call).
52    async fn write_columnar_segment(
53        &self,
54        collection: &str,
55        segment_id: u32,
56        bytes: &[u8],
57    ) -> Result<(), LiteError>;
58
59    /// Open a previously written columnar segment for `(collection, segment_id)`.
60    ///
61    /// Returns the raw segment bytes in a `Box<[u8]>`, identical to the bytes
62    /// passed to `write_columnar_segment`.
63    ///
64    /// Returns `None` if no segment exists under `col/seg/{collection}/{segment_id}`.
65    async fn open_columnar_segment(
66        &self,
67        collection: &str,
68        segment_id: u32,
69    ) -> Result<Option<Box<[u8]>>, LiteError>;
70
71    /// Delete the columnar segment for `(collection, segment_id)`.
72    ///
73    /// No-op if no segment exists.  The segment file is tombstoned and reaped
74    /// by the next `Db::gc_now` cycle.
75    async fn delete_columnar_segment(
76        &self,
77        collection: &str,
78        segment_id: u32,
79    ) -> Result<(), LiteError>;
80}