FluxMap
A high-performance, thread-safe, transactional, and durable in-memory key-value store for modern async Rust.
FluxMap provides a pure Rust, in-memory database solution that combines the speed of a skiplist data structure with the safety of Multi-Version Concurrency Control (MVCC). It offers ACID-compliant transactions with Serializable Snapshot Isolation (SSI), the highest level of isolation, preventing subtle concurrency bugs like write skew.
It is designed for ease of use, high performance, and seamless integration into tokio-based applications.
Features
- ACID Transactions: Guarantees atomicity, consistency, isolation, and durability.
- Serializable Snapshot Isolation (SSI): The strongest MVCC isolation level, protecting against phantom reads, write skew, and other subtle anomalies.
- Concurrent & Thread-Safe: Built for modern
asyncRust. TheDatabasecan be safely shared across threads usingArc. - Configurable Durability:
- In-Memory: For maximum performance when data persistence is not required.
- Durable (WAL): Use a Write-Ahead Log for durability. Choose between:
Relaxed: (Group Commit) Commits are buffered and flushed periodically for high throughput.Full: (fsync-per-transaction) Commits are flushed to disk before acknowledging, ensuring maximum safety.
- Configurable Maintenance:
- Automatic Vacuuming: Optional background thread to automatically clean up old data versions and reclaim memory.
- Memory Limiting: Optional memory limit with configurable eviction policies (LRU, LFU, Random, ARC) to keep memory usage in check.
- High Performance: A lock-free skiplist implementation provides excellent performance for reads and low-contention writes.
- Ergonomic API: A clean and simple API for
get,insert, andremoveoperations. Supports both simple autocommit operations and explicit, multi-statement transactions. - Range & Prefix Scans: Efficiently query ranges of keys or keys with a specific prefix, with both
VecandStream-based APIs.
Quick Start
First, add FluxMap to your Cargo.toml:
[]
= "0.3.7"
= { = "1", = ["full"] }
Example 1: In-Memory Autocommit
For simple use cases, each operation runs in its own small transaction.
use Database;
use Arc;
async
Example 2: Explicit Transactions
For atomic, multi-statement operations, use the transaction helper. It automatically handles beginning, committing, and rolling back the transaction.
use Database;
use FluxError;
use Arc;
// It's good practice to define a custom error type for your application.
// Implement From<FluxError> to allow the `?` operator to work inside the transaction.
async
Example 3: Durable Database with WAL
To persist data to disk, configure a durability level using the builder.
use ;
use Arc;
use tempdir; // For a temporary directory in this example
async
Example 4: Prefix Scans
Efficiently find all keys that start with a given prefix.
use Database;
use Arc;
async
Example 5: Memory Limiting and Eviction
Set a memory limit to automatically evict the least recently used (LRU) items when the database exceeds its capacity.
use Database;
use MemSize;
use Arc;
// Your key and value types must implement `MemSize` for memory limiting to work.
// For this example, we'll use simple types that already have it implemented.
// For custom structs, you would implement it like this:
//
// #[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
// struct MyValue {
// data: String,
// num: u64,
// }
//
// impl MemSize for MyValue {
// fn mem_size(&self) -> usize {
// std::mem::size_of::<Self>() + self.data.capacity()
// }
// }
async
Core Concepts
Configuration
The Database is configured using the builder pattern, which provides a flexible way to set durability and maintenance options.
use ;
use Duration;
# use tempdir;
#
# async
Database and Handle
Database<K, V>: The central object that owns all data. It's thread-safe and should be wrapped in anArcto be shared across tasks.Handle<'db, K, V>: A lightweight session handle for interacting with the database. You create handles from theDatabaseinstance. Handles are notSendorSyncand should be created per-task.
Transactions
FluxMap supports two modes of operation:
- Autocommit (Default): When you call
get,insert, orremovedirectly on aHandle, the operation is wrapped in its own transaction. This is simple and safe but can be less efficient for multiple dependent operations. - Explicit Transactions: For grouping multiple operations into a single atomic unit, you have two options:
handle.transaction(|h| ...): This is the recommended, high-level approach. It provides a closure with a mutable handle and automatically manages the transaction's lifecycle. If the closure returnsOk, it commits. If it returnsErr, it rolls back.handle.begin(),handle.commit(),handle.rollback(): These low-level methods give you manual control over the transaction boundaries.
Durability Levels
You can control the trade-off between performance and safety by configuring durability via the DatabaseBuilder.
InMemory: The default. No data is written to disk.Relaxed: (Group Commit) Commits are written to the OS buffer and a background thread flushes them to disk when the first of several conditions is met (e.g., time elapsed, number of commits, or bytes written). This offers high performance and durability against process crashes, but recent commits may be lost in case of an OS crash or power failure.Full: (fsync-per-transaction) Each transaction is fully synced to the disk before thecommitcall returns. This provides the strongest durability guarantee but has a higher performance overhead.
Isolation Levels
FluxMap supports configurable transaction isolation levels via the DatabaseBuilder.
-
Serializable(default): Serializable Snapshot Isolation (SSI). This is the strongest level, providing true serializability and protecting against all concurrency anomalies, including write skew. It achieves this by tracking read/write dependencies and aborting transactions that could violate serializability. -
Snapshot: Snapshot Isolation (SI). This is a slightly weaker, more optimistic level. It still provides many of the benefits of MVCC, such as non-blocking reads, but it is vulnerable to write skew anomalies. It can offer higher performance for some workloads because it avoids the dependency tracking overhead of SSI. Read-only transactions are guaranteed not to be aborted under this level.
use ;
#
# async
Durability Mechanism: WAL and Snapshots
When a durability level other than InMemory is chosen, FluxMap uses a Write-Ahead Log (WAL) combined with periodic snapshotting to provide crash safety and manage disk space. This is a standard and robust technique used by many production databases.
-
WAL Segmentation: Instead of a single, ever-growing log file, the WAL is split into a pool of smaller, fixed-size files called segments (e.g.,
wal.0,wal.1, etc.). The database writes to only one segment at a time. -
Log Rotation: When the active WAL segment becomes full, the engine performs a rotation: it marks the full segment as ready for processing and begins writing to the next available segment in the pool.
-
Background Snapshotting: A dedicated background thread works to consolidate full WAL segments. It reads a segment, applies all the changes within it to an in-memory representation of the database, and then writes the entire, up-to-date dataset to a single
snapshot.dbfile. -
Space Reclamation: Once a WAL segment has been successfully included in a new snapshot, it is no longer needed for recovery. The engine truncates the segment file, making it available for reuse. This crucial process prevents the WAL from growing indefinitely.
-
Fast Recovery: When a durable database is started, it doesn't need to replay the entire history of all transactions. It simply loads the most recent snapshot into memory and then replays only the few WAL segments that were written after that snapshot was created. This makes the recovery process fast and efficient, even for databases that have been running for a long time.
Automatic Vacuuming
When configured via auto_vacuum on the builder, the database will spawn a background thread to periodically run the vacuum process. This reclaims memory from old, dead data versions. If not enabled, you can still call db.vacuum() manually.
Memory Limiting and Eviction
You can configure a memory limit to prevent the database from growing indefinitely. When the estimated memory usage exceeds this limit, FluxMap will automatically evict keys to stay under the limit based on the configured EvictionPolicy.
Lru(default): Evicts the Least Recently Used item. A good general-purpose policy.Lfu: Evicts the Least Frequently Used item. Useful for workloads where access frequency is more important than recency.Random: Evicts a random item. Can be effective for certain access patterns and avoids the overhead of tracking usage.Arc: Adaptive Replacement Cache. It intelligently balances between LRU and LFU, providing excellent performance across a wide variety of workloads.
To use this feature, your key and value types must implement the fluxmap::mem::MemSize trait, which helps the database estimate how much memory each entry consumes.
use Database;
use MemSize; // Don't forget to import the trait
// For custom types, you need to implement MemSize.
;
#
# async
Under the Hood: MVCC and SSI
FluxMap is built on a Multi-Version Concurrency Control (MVCC) model. Instead of locking data, every modification creates a new version of the value, tagged with the transaction ID. When you read a key, the database finds the correct version that is visible to your transaction's "snapshot" of the data.
This approach allows for non-blocking reads—readers never have to wait for writers.
To provide true serializability, FluxMap implements Serializable Snapshot Isolation (SSI). It tracks read/write dependencies between concurrent transactions. If it detects a "write skew" or other anomaly that would violate serializability, it will automatically abort one of the conflicting transactions, forcing it to be retried. This ensures that your transactions behave as if they were run one after another, eliminating a whole class of subtle concurrency bugs.
Contributing
Contributions are welcome! Please feel free to open an issue or submit a pull request.
License
This project is licensed under the MIT License.