High-performance cache policies and supporting data structures.
Status: explicit boundaries for cachekit’s design. Companion to
design.md, which states what the crate optimizes for.
Good design needs negative space. This document records what cachekit is not trying to be, so future features can be judged against the same boundaries.
cachekit is an in-process cache library. It does not provide:
Use Redis, Memcached, or a database/cache service when those are the problem. cachekit can still be useful inside a node in front of those systems.
cachekit does not manage:
The library provides cache primitives and policies. Application frameworks can compose them into higher-level behaviours.
AsyncCacheFuture exists as a placeholder, but the shipped policies are
synchronous. Async-native traits are not currently implemented.
The reason is not that async is unimportant. It is that async cache APIs need
owned values, cancellation semantics, loader lifetime rules, and executor
integration. Adding async fn get_or_insert_with to the core trait would break
object safety and pull async choices into every policy.
Future async support should be a separate layer, not a mutation of
Cache<K, V>.
no_stdcachekit uses:
std::collections;std::sync::Arc;std::time in planned TTL work;parking_lot for concurrent wrappers;std.no_std would require a different allocator story, different synchronization
surface, and feature-gated alternatives for large parts of the crate. It is not
a current target.
The concurrency design is explicit and lock-based:
Concurrent* wrappers use parking_lot::RwLock;Lock-free structures would need a separate memory reclamation strategy, different value ownership rules, and a much larger unsafe surface. The current crate favours predictable, reviewable lock boundaries.
Some public surfaces use DoS-resistant hashing by default (HashMapStore,
ClockRing, ShardSelector::randomized). Other hot internal surfaces use
FxHashMap for speed.
cachekit documents those choices, but it is not a general-purpose security
boundary. Callers with adversarial keys must choose safe constructors, bound
admission, and avoid exposing interned handles or total_weight across trust
boundaries.
The serde feature supports metrics snapshots and StoreMetrics, not live
cache state. Serializing a policy means deciding what to do with recency lists,
ghost history, clock hands, hash seeds, Arc<V> identity, and TTL deadlines.
Until a policy has an explicit restore contract, do not derive serde for it.
The metrics layer provides counters, gauges, snapshots, reset, and a Prometheus text exporter. It does not provide:
Use your monitoring stack for those. cachekit exposes enough counters to make policy tuning possible without making the cache own observability.
MetricsCell (the crate-private interior-mutability wrapper used by
&self recorder paths) is not a substitute for AtomicU64. It is
sound only when increments happen under exclusive external
synchronization — single-threaded, &mut self, or behind a write
lock / Mutex. A shared RwLock::read guard does not serialize
readers and so is not sufficient protection. Counters reachable from a
read-locked &self path must use AtomicU64 (or escalate to a write
lock before recording). The contract is restated on the unsafe impl
Sync block in src/metrics/cell.rs and
in the Metrics design.
New policies are welcome, but they must fit the crate’s constraints:
A clever algorithm that needs tree walks, heap allocation on every access, or opaque trait-object dispatch in the hot loop belongs in a research branch until benchmarks justify it.
cachekit ships many policies, but it cannot choose your workload for you.
CachePolicy::Lru or CachePolicy::S3Fifo are defaults, not guarantees. Users
still need to measure reuse distance, scan rate, write ratio, object sizes, and
tail latency under representative traffic.
The benchmark suite provides workload generators to help, but it cannot infer production behaviour automatically.
Public traits and documented constructors follow SemVer. Internal layout does not:
DynCache’s private CacheInner enum.If downstream code depends on private layout, it is outside the compatibility contract.
When proposing a feature, ask:
If the answer is unclear, write a design doc before implementation.