StorageEngine API
StorageEngine is the top-level coordinator that ties together the WAL,
MemTable, Segments, and SnapshotManager.
Opening an Engine
#![allow(unused)] fn main() { use parallax_store::{StorageEngine, StoreConfig}; // Open an existing engine or create a new one let engine = StorageEngine::open(StoreConfig::new("/var/lib/parallax"))?; }
StoreConfig accepts:
#![allow(unused)] fn main() { pub struct StoreConfig { /// Root directory for all engine data. pub data_dir: PathBuf, /// Flush MemTable to segment when size exceeds this (default: 64MB). pub memtable_flush_size: usize, /// Maximum WAL segment size before rotation (default: 64MB). pub wal_segment_max_size: u64, } impl StoreConfig { /// Create a config with default settings. pub fn new(data_dir: impl Into<PathBuf>) -> Self { ... } } }
Writing Data
All writes go through WriteBatch:
#![allow(unused)] fn main() { use parallax_store::WriteBatch; let mut batch = WriteBatch::new(); // Upsert an entity (insert or update) batch.upsert_entity(entity); // Upsert a relationship batch.upsert_relationship(relationship); // Soft-delete an entity batch.delete_entity(entity_id); // Soft-delete a relationship batch.delete_relationship(rel_id); // Commit atomically (WAL + MemTable + snapshot publish) engine.write(batch)?; }
WriteBatch is opaque — you cannot inspect it after creation. The write is
atomic: either all operations succeed or none are applied.
WriteOp Internals
Under the hood, WriteBatch is a Vec<WriteOp>:
#![allow(unused)] fn main() { pub enum WriteOp { UpsertEntity(Entity), UpsertRelationship(Relationship), DeleteEntity(EntityId), DeleteRelationship(RelationshipId), } pub struct WriteBatch { pub(crate) ops: Vec<WriteOp>, } impl WriteBatch { pub fn is_empty(&self) -> bool { self.ops.is_empty() } pub fn len(&self) -> usize { self.ops.len() } } }
Reading Data
All reads go through a Snapshot:
#![allow(unused)] fn main() { // Acquire a snapshot (O(1), no lock) let snap = engine.snapshot(); // Point lookups let entity = snap.get_entity(entity_id); let rel = snap.get_relationship(rel_id); // Type/class scans (uses MemTable indices) let hosts = snap.entities_by_type(&EntityType::new_unchecked("host")); let services = snap.entities_by_class(&EntityClass::new_unchecked("Service")); // Source-scoped scans (for sync diff) let from_aws = snap.entities_by_source("connector-aws"); // Counts let total = snap.entity_count(); }
Metrics
#![allow(unused)] fn main() { // Get a snapshot of current engine metrics let metrics = engine.metrics().snapshot(); println!("Total entities: {}", metrics.entity_count); println!("Total relationships: {}", metrics.relationship_count); println!("Writes: {}", metrics.writes_total); println!("Reads: {}", metrics.reads_total); }
Metrics are maintained as atomic counters and can be read from any thread without acquiring the engine lock.
Engine in a Shared Context
In server mode, the engine is shared via Arc<Mutex<StorageEngine>>:
#![allow(unused)] fn main() { use std::sync::{Arc, Mutex}; use parallax_store::StorageEngine; let engine = StorageEngine::open(config)?; let shared = Arc::new(Mutex::new(engine)); // In a handler: let snap = { let engine = shared.lock().unwrap(); engine.snapshot() // Lock released here — snapshot is Arc-owned, not borrowed from engine }; // Use snap freely without holding the lock }
Error Types
#![allow(unused)] fn main() { pub enum StoreError { /// I/O error from the OS (file not found, permission denied, etc.) Io(io::Error), /// Data corruption detected (bad magic, CRC mismatch, etc.) Corruption(String), /// Serialization error (postcard format error) Serialization(String), } }
StoreError implements std::error::Error and Display. Use ? to
propagate it up the call stack.
Thread Safety
StorageEngine is Send but not Sync. Wrap it in Arc<Mutex<StorageEngine>>
for shared access. The lock should be held as briefly as possible:
#![allow(unused)] fn main() { // Good: lock, snapshot, release, use let snap = engine.lock().unwrap().snapshot(); let entity = snap.get_entity(id); // Bad: hold lock for the duration of graph computation let engine = engine.lock().unwrap(); let entity = engine.snapshot().get_entity(id); // lock held too long }