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}