Micro Moka
A predictable, safe-Rust, single-threaded cache. Micro Moka is forked from Mini Moka and uses the SIEVE eviction policy without locks, atomics, unsafe code, background work, or an async runtime.
use Cache;
let mut cache = new;
cache.insert;
assert_eq!;
The Design Point
Cache design is a multi-objective problem: hit ratio, byte hit ratio, average latency, tail latency, throughput, metadata density, expiration, weighting, and concurrency all compete. Micro Moka deliberately targets one Pareto point: single-owner caches that need a global SIEVE policy, arbitrary-size entries, Rust 1.76 support, and explicitly bounded admission work.
- Budgeted admission.
try_insertexamines at most 16 SIEVE candidates by default. If all were recently visited, it returns the candidate instead of extending the sweep. The next attempt resumes at the saved hand. This bounds eviction-policy work, spreads a hot sweep across requests, and resists short one-hit scans. - Exact insertion remains available.
insertalways admits a nonzero- capacity candidate and runs the original SIEVE sweep to completion. Choose exact admission when every new value must become resident. - Compact safe-Rust metadata. The visited flag is packed into the nonzero
predecessor link. On the supported targets, an
Option-wrapped slab slot foru64keys and values is 32 bytes, down from 40 bytes in v1.1.0. This excludes hash-table storage and allocations owned by the key or value. This is not an ecosystem density record: Senba offers a specialized 16-byte fixed slot for the same primitive pair. - A small read path. A hit performs one hash-table lookup and one visited-bit update. It never relinks an entry. A miss does not touch SIEVE metadata.
- Operationally small. The only production dependency is
hashbrown.RandomStateremains the HashDoS-resistant default; trusted-key workloads can opt into a faster hasher.
The benchmark suite measures hits, negative lookups, inserts, updates, delete-and-reinsert churn, mixed workloads, request and byte hit ratios, scan filtering, admission-attempt outcomes, and matched successful-admission retry accounting. See benchmark report for methodology, results, and limitations.
Installation
[]
= "1"
Usage
use Cache;
let mut cache: = builder
.max_capacity
.initial_capacity
.admission_scan_limit
.build;
cache.insert;
assert_eq!;
assert!;
// Inspect without affecting SIEVE state.
assert_eq!;
// Compute a missing value once using exact, always-admit insertion.
assert_eq!;
// Bound eviction scanning and retain ownership if admission is deferred.
if let Err = cache.try_insert
cache.invalidate;
let removed = cache.remove;
assert_eq!;
cache.insert;
cache.insert;
cache.invalidate_entries_if;
for in cache.iter
cache.invalidate_all;
assert_eq!;
Choosing an Insertion Path
| Requirement | Method | Full, all-hot cache |
|---|---|---|
| Every candidate must become resident | insert |
Sweeps until it finds a victim |
| Bound policy scan work | try_insert |
Returns Err((K, V)) after the configured budget |
| Load once and borrow the result | get_or_insert_with |
Uses exact insertion |
try_insert always updates an existing key and always admits below capacity.
A scan limit of zero freezes admission of new keys while full. A larger limit
makes the policy approach exact SIEVE; a smaller limit protects hot residents
more aggressively and rejects more candidates. Rejection is observable through
the returned Result, so applications can count, retry, or bypass the cache
without enabling internal statistics.
Custom Hashers
The default hasher is RandomState, the same HashDoS-resistant default used by
std::collections::HashMap. A faster hasher can materially improve throughput
when all keys are trusted:
use Cache;
let mut cache: = builder
.max_capacity
.build_with_hasher;
How SIEVE and Budgeted Admission Work
Entries remain in insertion order. Each entry has one visited bit, and the cache maintains a hand into the list:
- A successful
get, update, orget_or_insert_withhit marks the entry as visited without moving it. - Exact eviction starts at the oldest resident, clears visited bits while moving toward newer residents, and evicts the first unvisited entry.
- Budgeted admission performs the same sweep for at most the configured number of entries.
- If the budget expires,
try_insertsaves the hand and returns the candidate. Otherwise it evicts the unvisited victim and admits the candidate.
The exact sweep is amortized constant work, but a single insertion can inspect
the whole cache when every resident was visited. try_insert exists for callers
that need a hard bound on one operation's policy scan work.
peek, contains_key, and iteration do not set the visited bit.
Ecosystem Fit
Micro Moka does not try to replace every Rust cache:
| Need | Better fit |
|---|---|
| Safe-Rust, Rust 1.76, arbitrary-size entries, global SIEVE, and explicit bounded admission | Micro Moka try_insert |
| Single-owner cache where every candidate must be retained immediately | Micro Moka insert or lru |
| Fixed 16/32/64-byte slots, unsafe/SIMD specialization, and bounded sharded SIEVE work | senba |
| A published core SIEVE API plus sync and sharded wrappers | sieve-cache |
| Weighted capacity, S3-FIFO, pinning, or sync and unsync variants | quick_cache |
| Unsync weighting, TTL, TTI, and Window TinyLFU | mini-moka |
| Per-entry expiry, listeners, async loading, or concurrent TinyLFU | moka |
| Strict LRU with a broad map-like API | lru or hashlink |
Micro Moka intentionally omits weighted capacity and expiration. Those features
need more per-entry state, clock reads, cleanup machinery, or policy coupling and
would weaken this crate's predictability-density position. Values may still be
Option<T> for negative caching, and callers can invalidate entries explicitly.
Minimum Supported Rust Version
Micro Moka supports Rust 1.76.0 and uses the Rust 2021 edition. MSRV increases are not considered semver-breaking changes.
Development
# Benchmarks require Rust 1.85 and have a separate, locked dependency graph.
Releases
Merges to main are released automatically. See RELEASING.md.
Credits
SIEVE was introduced by Zhang et al. at NSDI 2024. Micro Moka was forked from Mini Moka by Tatsuya Kawano and the Moka contributors.
License
MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.