indexmap_store
Mutable, persistent key-value store backed by an in-memory
IndexMap and an append-only write-ahead log on
disk.
- O(1) hashed lookup, insertion-order iteration.
- Single log file, length-prefixed records, buffered writes.
- Crash-safe recovery: torn tail is truncated, never bricks the store.
- Automatic compaction when the log accumulates dead records.
- Generic over any
Serialize + DeserializeOwnedkey and value (viabincode).
The store owns its file. Single-writer; no concurrent writers.
Install
[]
= "0.2"
Quick start
use IndexMapStore;
let mut store: = open?;
store.insert?;
store.insert?;
store.modify?;
store.remove?;
store.flush?;
assert_eq!;
assert_eq!;
// Reopen — state is recovered from the log.
drop;
let store: = open?;
assert_eq!;
# Ok::
Configuration
use ;
let cfg = StoreConfig ;
let mut store: =
open_with?;
# Ok::
Defaults: compact_ratio = 2.0, min_compact_bytes = 1 MiB,
sync_on_write = false, buf_capacity = 1 MiB.
API at a glance
| Method | Effect |
|---|---|
open / open_with |
Open or create a store, replay log |
insert(k, v) |
Append Insert record, return previous value |
remove(&k) |
Append Remove record (shift-remove) |
modify(&k, f) |
Mutate value in place, append new Insert |
get, contains_key, get_index |
Read-only lookups |
iter, keys, values |
Insertion-ordered iteration |
flush |
Flush buffer + sync_data |
compact |
Rewrite log to one record per live entry |
All mutating methods return io::Result. Errors come from the underlying
file or from bincode serialization (wrapped as InvalidData).
Durability
With sync_on_write = false (default), records land in the kernel page cache
on each call but are not fsynced until you flush() or the OS flushes its
own writeback. Suitable for bulk loads where you call flush() at safe
checkpoints. Set sync_on_write = true for fsync-per-mutation.
A crash mid-record leaves a partial trailer on disk. The next open reads
records front-to-back, stops at the first short or undecodable trailer, and
set_len-truncates the file to the last valid record boundary. No special
recovery tool needed.
Compaction
Every mutation increments total_records. Inserts/removes also update
live_records. When the log exceeds min_compact_bytes and
total_records / live_records >= compact_ratio, the store writes a fresh
log (one Insert per live entry) to path.compact.tmp, fsyncs, then
rename(2)s it over the original. Call compact() manually at any time.
Limitations
- Single-writer; no concurrent access from multiple processes or threads.
- Whole-record serialization — large values are rewritten on every
modify. - No range queries; lookup is by exact key.