Skip to main content

SingleFileDirectory

Struct SingleFileDirectory 

Source
pub struct SingleFileDirectory { /* private fields */ }
Expand description

Production storage backend: all data in a single .luci file using the block allocator and two-root-pointer atomic commit.

See [[architecture-storage-format]] for the full design and [[architecture-storage-format#Atomic Commit Protocol]] for the commit sequence.

§Crash Safety

At any point, both root pointers reference intact (non-overwritten) metadata blocks. A crash mid-commit is recovered by falling back to the still-valid root. Uncommitted segment writes are orphaned blocks that the allocator does not reference — they are harmlessly reclaimed on the next file open.

Implementations§

Source§

impl SingleFileDirectory

Source

pub fn create(path: impl AsRef<Path>) -> Result<Self>

Create a new .luci file at the given path.

Fails if a file already exists at path.

Source

pub fn open(path: impl AsRef<Path>) -> Result<Self>

Open an existing .luci file, performing crash recovery.

Validates root pointers, loads metadata from the best available root, and reconstructs the block allocator. If the active root’s checksum is invalid (torn write), falls back to the inactive root and repairs the header.

See [[architecture-storage-format#Crash Recovery]].

Source

pub fn file_handle(&self) -> Arc<File>

Return a shared reference to the underlying file handle.

Used by the reader path to avoid opening a separate fd, which would break fcntl lock semantics (closing any fd releases all locks for the process on that inode).

See [[architecture-cross-process-locking#Critical Constraint]].

Source

pub fn set_write_timeout(&mut self, timeout: Duration)

Set the timeout for acquiring the cross-process write lock.

Default: 5 seconds. If another process holds the write lock, retries with exponential backoff until the timeout expires, then returns WriterLocked.

Source

pub fn open_from_handle(file: Arc<File>) -> Result<Self>

Open a read-only view using an existing file handle.

Does not open a new fd or acquire any locks. The caller’s process already holds the appropriate lock via the writer’s fd. Used by refresh_reader() to reload segment data without breaking fcntl lock semantics.

Source

pub fn write_segment( &mut self, segment_id: SegmentId, data: &[u8], ) -> Result<()>

Write segment data to allocated blocks.

The segment is not visible to readers until commit is called. Data is written to disk immediately so that commit only needs to write metadata and flip the header.

Automatically acquires RESERVED lock on first write (blocks other writers). The lock is held until commit() completes.

§Errors

Returns an error if data is empty or the write fails. Returns WriterLocked if another process holds RESERVED.

Source

pub fn read_segment(&self, segment_id: SegmentId) -> Result<Vec<u8>>

Read committed segment data by segment ID.

Only committed segments are visible. Returns LuciError::IndexNotFound if the segment does not exist in the committed state.

Source

pub fn commit(&mut self) -> Result<()>

Atomically commit all pending segment writes.

Implements the six-step atomic commit protocol from [[architecture-storage-format]]:

  1. Segment data already written to blocks (by write_segment)
  2. Serialize new metadata to a freshly allocated block (copy-on-write)
  3. Compute checksum, update the inactive root pointer
  4. fsync the data file
  5. Write the 4 KB header with the flipped active root flag
  6. fsync the header

The old inactive root’s metadata block is freed and included in the new free list — but never overwritten until after this commit succeeds, so both roots remain valid at all times for crash recovery.

Source

pub fn segments(&self) -> &[SegmentEntry]

The currently committed segment entries.

Source

pub fn generation(&self) -> u64

The current commit generation.

Source

pub fn set_user_metadata(&mut self, metadata: Vec<u8>)

Set opaque user metadata to be persisted on the next commit.

Source

pub fn user_metadata(&self) -> &[u8]

Get the persisted user metadata (empty if none).

Source

pub fn free_block_count(&self) -> u64

Total number of free (reusable) blocks tracked by the allocator.

Exposed for integration testing (free-list reclamation validation).

Source

pub fn total_blocks(&self) -> u64

Total number of data blocks the file spans.

Exposed for integration testing.

Source

pub fn remove_segments(&mut self, segment_ids: &[SegmentId])

Mark segments for removal on the next commit.

Source

pub fn write_vector_index( &mut self, field_id: FieldId, data: &[u8], ) -> Result<()>

Write a per-field vector index. The bytes are written to a fresh extent immediately; the entry becomes visible to readers on the next commit(). If the field already had a committed entry, its old extent is freed during commit.

Source

pub fn read_vector_index(&self, field_id: FieldId) -> Result<Option<Vec<u8>>>

Read committed vector-index bytes for field_id. Returns None if no committed index exists for that field.

Source

pub fn vector_index_fields(&self) -> Vec<FieldId>

List the fields that have a committed vector index.

Source

pub fn remove_vector_index(&mut self, field_id: FieldId)

Mark the vector index for field_id for removal on next commit.

Trait Implementations§

Source§

impl Storage for SingleFileDirectory

Available on Unix only.
Source§

fn write_segment(&mut self, segment_id: SegmentId, data: &[u8]) -> Result<()>

Source§

fn read_segment(&self, segment_id: SegmentId) -> Result<Vec<u8>>

Source§

fn commit(&mut self) -> Result<()>

Source§

fn segments(&self) -> &[SegmentEntry]

Source§

fn generation(&self) -> u64

Source§

fn set_user_metadata(&mut self, metadata: Vec<u8>)

Source§

fn user_metadata(&self) -> &[u8]

Source§

fn remove_segments(&mut self, segment_ids: &[SegmentId])

Mark segments for removal on the next commit. Their storage space is reclaimed when the commit completes.
Source§

fn write_vector_index(&mut self, field_id: FieldId, data: &[u8]) -> Result<()>

Write a per-field vector index (e.g., serialized HNSW graph) as an index-wide artifact, separate from segments. Replaces any previously-committed bytes for the same field_id on next commit. See [[global-vector-indices]].
Source§

fn read_vector_index(&self, field_id: FieldId) -> Result<Option<Vec<u8>>>

Read the committed bytes for a per-field vector index. Returns None if no index exists for that field.
Source§

fn vector_index_fields(&self) -> Vec<FieldId>

List the fields that have a committed vector index.
Source§

fn remove_vector_index(&mut self, field_id: FieldId)

Mark the vector index for field_id for removal on the next commit. No-op if the field has no committed index.
Source§

fn set_write_timeout(&mut self, timeout: Duration)

Set the timeout for acquiring the cross-process write lock. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<G1, G2> Within<G2> for G1
where G2: Contains<G1>,

Source§

fn is_within(&self, b: &G2) -> bool