Skip to main content

StorageEngine

Struct StorageEngine 

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

The unified storage engine for MenteDB.

Memories are stored as variable length records packed into 512 byte blocks of one file (see crate::record), so the file is about the size of the data. Every record is addressed by a stable locator (PageId) resolved through an in memory table that is persisted at checkpoints and replayed forward from the WAL on open.

Concurrency model:

  • One process per database. Open takes an exclusive lock on a LOCK file in the data directory (fcntl OFD lock, enforced across hosts on NFSv4) and holds it for the engine’s lifetime, so in memory state never goes stale and needs no reload from disk on each write.
  • Writes are serialized by the state lock: WAL append and fsync, then the record write, then the table update.
  • Reads run concurrently with each other: a cache hit takes only the cache lock; a miss reads its extent with a positional read under the shared state lock.

Implementations§

Source§

impl StorageEngine

Source

pub fn open(path: &Path) -> MenteResult<Self>

Open (or create) a storage engine rooted at path with default settings. See Self::open_with_config.

use mentedb_storage::StorageEngine;

let engine = StorageEngine::open("/tmp/mentedb".as_ref())?;
// engine is ready: WAL recovery already ran if needed
Source

pub fn open_with_config( path: &Path, config: &StorageConfig, ) -> MenteResult<Self>

Open (or create) a storage engine rooted at path.

path must be a directory; it is created if missing. A database in the old page file layout is migrated first (see crate::legacy); then any WAL entries are replayed. config.cache_bytes bounds the record cache; size it to the database to serve every scan from memory.

Source

pub fn close(&self) -> MenteResult<()>

Gracefully shut down: sync the record file and the WAL, release the directory lock.

Source

pub fn buffer_stats(&self) -> BufferStats

Record cache activity, for metrics (hit ratio, evictions, residency).

Source

pub fn set_cache_capacity(&self, bytes: u64)

Change the record cache capacity in bytes. A host with spare memory can raise it to the database size so every scan is served from memory.

Source

pub fn cache_capacity(&self) -> u64

Record cache capacity in bytes.

Source

pub fn page_count(&self) -> u64

Blocks in the record file (BLOCK_SIZE bytes each).

Source

pub fn data_bytes(&self) -> u64

Bytes of the record file.

Source

pub fn free_bytes(&self) -> u64

Bytes of the record file not holding any record (reusable holes).

Source

pub fn memory_loads(&self) -> u64

Memory loads served since open, whether from the cache or from disk.

Source

pub fn page_reads(&self) -> u64

Reads issued to the record file since open (cache misses, plus one per extent header when open had to scan).

Source

pub fn bytes_read(&self) -> u64

Bytes read from the record file since open.

Source

pub fn store_memory(&self, node: &MemoryNode) -> MenteResult<PageId>

Store a MemoryNode as a new record and return its locator.

use mentedb_storage::StorageEngine;
use mentedb_core::{MemoryNode, memory::MemoryType, types::AgentId};

let engine = StorageEngine::open("/tmp/mentedb".as_ref())?;
let node = MemoryNode::new(
    AgentId::new(),
    MemoryType::Semantic,
    "User likes dark mode".to_string(),
    vec![0.1, 0.2],
);
let page_id = engine.store_memory(&node)?;
Source

pub fn store_memory_batch( &self, nodes: &[MemoryNode], ) -> MenteResult<Vec<PageId>>

Store several MemoryNodes with a single WAL fsync.

Source

pub fn update_memory( &self, page_id: PageId, node: &MemoryNode, ) -> MenteResult<()>

Rewrite the record at page_id with node. The locator never changes: a record that still fits its extent is rewritten in place, one that outgrew it moves to a larger extent and the old one is freed, both in one logged operation.

Source

pub fn delete_memory(&self, page_id: PageId) -> MenteResult<()>

Delete the record at page_id, freeing its extent for reuse. Logged before it is applied, so the memory does not resurrect after a crash.

Source

pub fn load_memory(&self, page_id: PageId) -> MenteResult<MemoryNode>

Load the MemoryNode stored at page_id.

let node = engine.load_memory(PageId(1))?;
println!("memory: {}", node.content);
Source

pub fn checkpoint(&self) -> MenteResult<()>

Checkpoint: sync the record file, persist the record map, and truncate the WAL. A no-op when nothing changed since the last checkpoint.

Source

pub fn memory_map(&self) -> MenteResult<Vec<MappedMemory>>

Every memory in storage with its locator and write stamp, in locator order. Served from the in memory table; reads nothing.

Source

pub fn memory_stamp(&self, page: PageId) -> Option<(MemoryId, u64)>

The memory and write stamp at page, or None when it holds no memory.

Source

pub fn scan_all_memories(&self) -> Vec<(MemoryId, PageId)>

Read and decode every record in the file, independent of the locator table, and return (memory id, locator) pairs in locator order. The ground truth of what is on disk; used by tests and diagnostics.

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

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

Source§

type Error = !

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

fn try_from(value: U) -> Result<T, !>

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<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more