Cache invalidation for a CMS nobody wants to wait for
Editors expect a publish to be live immediately; the CDN expects to be left alone. Where we drew the line.
A streaming CMS has two populations with incompatible expectations. Editors publish and want the change visible before they finish reading their own sentence. Readers arrive in bursts of tens of thousands and want the page served from the closest, dumbest layer available.
What we cache
Not pages. Pages are cheap to assemble and expensive to invalidate correctly. We cache the resolved content graph — the shape a page needs after permissions, localisation and scheduling have been applied — keyed by entity id and version.
- Entity reads: Redis, keyed
entity:{id}:{version}, no TTL. - Listings: Redis sorted sets, TTL 60s, rebuilt by a Hangfire job on publish.
- Delivery: CDN, long TTL, purged by surrogate key.
Why versions beat invalidation
A version bump makes the old key unreachable rather than wrong. Nothing has to be deleted at the moment of publish, which is the moment least likely to tolerate a slow Redis round trip.
The publish path writes one row and one integer. Everything else is a consequence.
The cost is garbage: dead keys linger until eviction. On a fixed-memory Redis with an LRU policy that is a feature, not a leak.