Cache tuning¶
cache.Cache is the storage core under every KV backend, and a capable
standalone in-process cache in its own right. All knobs live on cache.Config
(reachable as DirectConfig.Cache / EmbeddedConfig.Cache, or passed straight
to cache.New).
Configuration¶
| Field | Default | Meaning |
|---|---|---|
NumShards |
256 | independent shards; must be a power of two. More shards = less lock contention. |
PageSize |
16 MiB | slab page size (1 MiB – 1 GiB) |
MaxMemoryPerShard |
256 MiB | per-shard cap (≥ PageSize); total budget ≈ NumShards × MaxMemoryPerShard |
InitialPagesPerShard |
0 (lazy) | pre-allocate pages up front instead of on demand |
AtCapPolicy |
PolicyRingbufEvict |
at capacity: overwrite oldest entries (PolicyRingbufEvict) or reject writes with cache.ErrFull (PolicyRejectWrites) |
TTLSweepIntervalMs |
1000 | background expiry sweep; 0 disables (lazy expiry on read still applies) |
DataDir |
"" (heap) | directory for per-shard mmap files; enables persistence + warm restart. Linux only. |
Durable |
false | msync on commit boundaries (bounded by MsyncIntervalMs, default 100 ms); false = opportunistic OS flushing |
Mlock |
false | pin the mmap into RAM (needs ulimit -l headroom; failure logs and continues) |
Read semantics¶
Get(key)— policy-dependent. UnderPolicyRejectWritesit is zero-copy: the returned slice aliases the page arena, valid until the next write, so copy it if you retain it. Under the defaultPolicyRingbufEvictit is a freshly allocated copy you own and may retain — one allocation per hit, and the reason is safety rather than oversight: ring-buffer eviction overwrites live page bytes, so an alias could be pulled out from under you.GetInto(dst, key)— copies into your reusable buffer and returns it. 0 allocations on a hit when capacity suffices, and the copy is safe to retain even under the evicting ring-buffer policy. This is the hot-loop read.
buf := make([]byte, 0, 256)
for _, k := range keys {
buf, err = c.GetInto(buf[:0], k)
// use buf before the next iteration reuses it
}
Persistence & warm restart¶
With DataDir set, each shard's pages live in a memory-mapped file. On
restart the index is rebuilt from the pages — a warm start that skips
re-population. Under Embedded, the cache also tracks the Raft
AppliedIndex, so recovery replays only Raft entries newer than the persisted
state.
Durability levels:
- Heap mode (
DataDir: "") — fastest, nothing survives restart. - mmap, opportunistic (
DataDirset,Durable: false) — survives process restart; OS decides flush timing, so a power loss can lose recent writes. - mmap, durable (
Durable: true) —msyncat commit boundaries, at mostMsyncIntervalMsbehind.
On Embedded, the Raft log is the source of truth regardless — mmap
persistence is an optimization for restart speed, not the correctness
mechanism.
Sizing guidance¶
- Size
MaxMemoryPerShard × NumShardsto your working set plus headroom; underPolicyRingbufEvictthe store behaves as a bounded cache, evicting oldest-first per shard once full. - Prefer more shards over bigger shards for write-heavy concurrent loads.
- Values are stored in slab pages; very large values fragment less with a
larger
PageSize.