Koksuk

← All notes

A small HTTP cache in front of an API

2026-07-02

Caching discussions tend to start with technology and end with invalidation. A quieter approach is to start with the request itself. Which parts of it actually change the response? Everything else is noise, and noise makes cache keys useless.

Keys before stores

A cache key should include the representation, not the transport. Authorization usually belongs in the key or in a separate namespace; a shared cache with per-user data and a long TTL is a breach waiting for a load test. Query parameter order should not matter. Tracking parameters should not matter at all.

GET /v1/items?page=2&sort=name
key: items:v3:page=2:sort=name:lang=en
vary: accept-encoding, accept-language

Then pick a TTL you can defend. Five seconds is often enough to absorb a spike and short enough that nobody argues about stale data. Add stale-while-revalidate if the origin can tolerate one old response while a refresh runs in the background.

The boring failure modes

Two things break small caches: unbounded cardinality and stampedes. Cardinality is a key-design problem. A stampede is a concurrency problem: when a popular key expires, one request should refresh it and the rest should wait or serve stale. A single-flight lock around the origin call solves most of it.

None of this requires a new service. It requires deciding what a response is, and being consistent about it.