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
impl StorageEngine
Sourcepub fn open(path: &Path) -> MenteResult<Self>
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 neededSourcepub fn open_with_config(
path: &Path,
config: &StorageConfig,
) -> MenteResult<Self>
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.
Sourcepub fn close(&self) -> MenteResult<()>
pub fn close(&self) -> MenteResult<()>
Gracefully shut down: sync the record file and the WAL, release the directory lock.
Sourcepub fn buffer_stats(&self) -> BufferStats
pub fn buffer_stats(&self) -> BufferStats
Record cache activity, for metrics (hit ratio, evictions, residency).
Sourcepub fn set_cache_capacity(&self, bytes: u64)
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.
Sourcepub fn cache_capacity(&self) -> u64
pub fn cache_capacity(&self) -> u64
Record cache capacity in bytes.
Sourcepub fn page_count(&self) -> u64
pub fn page_count(&self) -> u64
Blocks in the record file (BLOCK_SIZE bytes each).
Sourcepub fn data_bytes(&self) -> u64
pub fn data_bytes(&self) -> u64
Bytes of the record file.
Sourcepub fn free_bytes(&self) -> u64
pub fn free_bytes(&self) -> u64
Bytes of the record file not holding any record (reusable holes).
Sourcepub fn memory_loads(&self) -> u64
pub fn memory_loads(&self) -> u64
Memory loads served since open, whether from the cache or from disk.
Sourcepub fn page_reads(&self) -> u64
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).
Sourcepub fn bytes_read(&self) -> u64
pub fn bytes_read(&self) -> u64
Bytes read from the record file since open.
Sourcepub fn store_memory(&self, node: &MemoryNode) -> MenteResult<PageId>
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)?;Sourcepub fn store_memory_batch(
&self,
nodes: &[MemoryNode],
) -> MenteResult<Vec<PageId>>
pub fn store_memory_batch( &self, nodes: &[MemoryNode], ) -> MenteResult<Vec<PageId>>
Store several MemoryNodes with a single WAL fsync.
Sourcepub fn update_memory(
&self,
page_id: PageId,
node: &MemoryNode,
) -> MenteResult<()>
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.
Sourcepub fn delete_memory(&self, page_id: PageId) -> MenteResult<()>
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.
Sourcepub fn load_memory(&self, page_id: PageId) -> MenteResult<MemoryNode>
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);Sourcepub fn checkpoint(&self) -> MenteResult<()>
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.
Sourcepub fn memory_map(&self) -> MenteResult<Vec<MappedMemory>>
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.
Sourcepub fn memory_stamp(&self, page: PageId) -> Option<(MemoryId, u64)>
pub fn memory_stamp(&self, page: PageId) -> Option<(MemoryId, u64)>
The memory and write stamp at page, or None when it holds no memory.
Sourcepub fn scan_all_memories(&self) -> Vec<(MemoryId, PageId)>
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.