Keep an old answer from replacing a new one
A tiny key-value server, a delayed cache fill, and the version marker that stops it.
A versioned cache that rejects stale writes.
Python / RESP
Notes & checks
A Redis-compatible KV server using RESP. Measured check: 2,048 race probes, 0 stale writes.
The problem
A cache keeps a copy of data so readers can fetch it quickly. But a slow reader might fetch an old value, pause, then refill the cache after someone has already updated it. minikv adds version-aware writes to a small in-memory server that speaks RESP2, the wire format used by Redis clients.
How it works
- New value, v2Write accepted
- Invalidate v2Keep marker v2
- Late fill, v1Write rejected
Attach a version to each fill
SETIFNEWER takes a key, value, and caller-supplied version. It only installs a value if that version is strictly newer than the one stored. Equal versions are rejected too. The server handles each command atomically in its event loop.
Keep a marker after invalidation
INVALIDATE removes a value through a given version but keeps that version as a marker. DEL also keeps the last version. An old refill cannot bring a deleted value back just because the key looks empty.
Separate single-node memory from replication
The standalone server loses its data at restart. The optional three-node Raft mode elects a leader and waits for a majority to commit commands. Raft is a way for machines to agree on an ordered log; this implementation sends reads through the log too.
Decisions & tradeoffs
Markers use memory
Retaining versions prevents stale refills, but markers remain in memory until restart in standalone mode. There is no backing database or eviction policy in that mode, and callers must assign meaningful versions.
Agreement has a cost
Raft mode needs a quorum, a majority of the three nodes, for reads as well as writes. A timed-out command may still commit later, and retries are not deduplicated. The fixed-membership implementation has no authentication, snapshots, or log compaction.
Try it yourself
With Python 3.12+, uv, and redis-cli installed, start a fresh standalone checkout on loopback port 6380. Use an unused port if needed.
uv sync
uv run --no-env-file minikv --port 6380
# In another terminal:
redis-cli -p 6380 SETIFNEWER lesson new 2
redis-cli -p 6380 INVALIDATE lesson 2
redis-cli -p 6380 SETIFNEWER lesson old 1
redis-cli -p 6380 GETVERSIONED lessonExpect 1, 1, 0, then a null result. The old value stays out. Stop the server with Ctrl-C; this standalone example is in memory. The README also includes a local three-process Raft demo.
Sources & scope
Based on the local README, versioned store, and Raft implementation. This page distinguishes the standalone and replicated modes.