Skip to main content

luci/storage/
mod.rs

1// Obsidian [[wikilinks]] in doc comments are intentional — they link to
2// design and reference docs in docs/. Rustdoc doesn't understand them.
3#![allow(rustdoc::broken_intra_doc_links)]
4
5//! `luci-storage` — block-based storage engine for Luci.
6//!
7//! This crate implements the [[single-file-format]] that backs every Luci
8//! index. It manages disk space as fixed-size blocks, tracks free space with
9//! extent-based free lists, and provides atomic commit via a two-root-pointer
10//! technique.
11//!
12//! See [[architecture-storage-format]] for the full design.
13
14mod allocator;
15mod block;
16mod directory;
17mod header;
18pub mod lock;
19mod single_file;
20mod wal;
21
22pub use allocator::BlockAllocator;
23pub use block::{BLOCK_SIZE, BlockId, Extent, HEADER_SIZE};
24pub use directory::{MetadataSnapshot, SegmentEntry, VectorIndexEntry};
25pub use header::{ActiveRoot, FORMAT_VERSION, FileHeader, MAGIC, RootPointer, xxh3_checksum};
26#[cfg(unix)]
27pub use single_file::SingleFileDirectory;
28pub use wal::{DurabilityMode, Wal, WalRecord, replay_wal};
29
30use crate::core::{FieldId, Result, SegmentId};
31
32/// Trait abstracting over storage backends.
33///
34/// [`SingleFileDirectory`] is the sole implementation today. The trait
35/// exists so that consumers (`IndexReader`, the index writer, etc.)
36/// depend on a stable storage interface rather than a concrete type.
37pub trait Storage: Send {
38    fn write_segment(&mut self, segment_id: SegmentId, data: &[u8]) -> Result<()>;
39    fn read_segment(&self, segment_id: SegmentId) -> Result<Vec<u8>>;
40    fn commit(&mut self) -> Result<()>;
41    fn segments(&self) -> &[SegmentEntry];
42    fn generation(&self) -> u64;
43    fn set_user_metadata(&mut self, metadata: Vec<u8>);
44    fn user_metadata(&self) -> &[u8];
45    /// Mark segments for removal on the next commit. Their storage space
46    /// is reclaimed when the commit completes.
47    fn remove_segments(&mut self, segment_ids: &[SegmentId]);
48
49    /// Write a per-field vector index (e.g., serialized HNSW graph) as
50    /// an index-wide artifact, separate from segments. Replaces any
51    /// previously-committed bytes for the same `field_id` on next
52    /// commit. See [[global-vector-indices]].
53    fn write_vector_index(&mut self, field_id: FieldId, data: &[u8]) -> Result<()>;
54
55    /// Read the committed bytes for a per-field vector index. Returns
56    /// `None` if no index exists for that field.
57    fn read_vector_index(&self, field_id: FieldId) -> Result<Option<Vec<u8>>>;
58
59    /// List the fields that have a committed vector index.
60    fn vector_index_fields(&self) -> Vec<FieldId>;
61
62    /// Mark the vector index for `field_id` for removal on the next
63    /// commit. No-op if the field has no committed index.
64    fn remove_vector_index(&mut self, field_id: FieldId);
65
66    /// Set the timeout for acquiring the cross-process write lock.
67    ///
68    /// Default: 5 seconds.
69    fn set_write_timeout(&mut self, _timeout: std::time::Duration) {}
70}