← All projects

minikv Backend · Infra

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

GitHub ↗
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

  1. New value, v2Write accepted
  2. Invalidate v2Keep marker v2
  3. Late fill, v1Write rejected
Illustrative race. Empty does not mean the version has been forgotten.
  1. 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.

  2. 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.

  3. 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 lesson

Expect 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.