Versioned keys are MVCC, and the bill goes somewhere new
Who pays for old versions in a versioned cache, and which session guarantees come for free?
MVCC (multi-version concurrency control) is how many databases let readers and writers work at the same time: each change creates a new version of a row, old versions stay until nobody needs them, and a reader sees a consistent snapshot. A versioned cache is the same thing. The mapping is exact, not just an analogy:
| Cache | Transaction manager |
|---|---|
| version embedded in the key | version chain |
| superseded entries, now unreachable | dead tuples (old row versions) |
| eviction policy | vacuum or undo purge (the cleanup of old versions) |
| snapshot id or table fingerprint | transaction or snapshot id |
| read of (key, v) | snapshot read |
This is one more appearance of the pattern "immutable storage + an indirection layer + a garbage collector", now inside a cache. It has the same failure mode: the collector falls behind exactly when most garbage is produced. High churn produces dead versions fastest, and high churn is exactly when the hit rate matters most.
But the question who pays for old versions? gets a new answer here. PostgreSQL bills the writer: old row versions stay in the table and cause bloat. Oracle bills the reader: if a reader needs a version that has been purged, it fails with the error ORA-01555 ("snapshot too old"). A versioned cache bills neither. Dead versions simply use capacity, so the effective capacity falls from C to C/V, where V is the average number of stored versions per key. By The second copy's arithmetic (h ≈ ln C / ln N), the hit rate falls by
- versions per key
- items in the corpus
Data table
| Versions per key V | series | Hit rate lost (points) |
|---|---|---|
| 1 | N = 10⁶ | 0 |
| 1.04 | N = 10⁶ | 0.271 |
| 1.08 | N = 10⁶ | 0.542 |
| 1.12 | N = 10⁶ | 0.813 |
| 1.16 | N = 10⁶ | 1.08 |
| 1.21 | N = 10⁶ | 1.36 |
| 1.25 | N = 10⁶ | 1.63 |
| 1.3 | N = 10⁶ | 1.9 |
| 1.35 | N = 10⁶ | 2.17 |
| 1.4 | N = 10⁶ | 2.44 |
| 1.45 | N = 10⁶ | 2.71 |
| 1.51 | N = 10⁶ | 2.98 |
| 1.57 | N = 10⁶ | 3.25 |
| 1.63 | N = 10⁶ | 3.52 |
| 1.69 | N = 10⁶ | 3.79 |
| 1.75 | N = 10⁶ | 4.07 |
| 1.82 | N = 10⁶ | 4.34 |
| 1.89 | N = 10⁶ | 4.61 |
| 1.96 | N = 10⁶ | 4.88 |
| 2.04 | N = 10⁶ | 5.15 |
| 2.11 | N = 10⁶ | 5.42 |
| 2.2 | N = 10⁶ | 5.69 |
| 2.28 | N = 10⁶ | 5.96 |
| 2.37 | N = 10⁶ | 6.23 |
| 2.46 | N = 10⁶ | 6.51 |
| 2.55 | N = 10⁶ | 6.78 |
| 2.65 | N = 10⁶ | 7.05 |
| 2.75 | N = 10⁶ | 7.32 |
| 2.85 | N = 10⁶ | 7.59 |
| 2.96 | N = 10⁶ | 7.86 |
| 3.08 | N = 10⁶ | 8.13 |
| 3.19 | N = 10⁶ | 8.4 |
| 3.31 | N = 10⁶ | 8.67 |
| 3.44 | N = 10⁶ | 8.94 |
| 3.57 | N = 10⁶ | 9.22 |
| 3.71 | N = 10⁶ | 9.49 |
| 3.85 | N = 10⁶ | 9.76 |
| 4 | N = 10⁶ | 10 |
| 4.15 | N = 10⁶ | 10.3 |
| 4.31 | N = 10⁶ | 10.6 |
| 4.47 | N = 10⁶ | 10.8 |
| 4.64 | N = 10⁶ | 11.1 |
| 4.82 | N = 10⁶ | 11.4 |
| 5 | N = 10⁶ | 11.7 |
| 5.19 | N = 10⁶ | 11.9 |
| 5.39 | N = 10⁶ | 12.2 |
| 5.6 | N = 10⁶ | 12.5 |
| 5.81 | N = 10⁶ | 12.7 |
| 6.03 | N = 10⁶ | 13 |
| 6.26 | N = 10⁶ | 13.3 |
| 6.5 | N = 10⁶ | 13.6 |
| 6.75 | N = 10⁶ | 13.8 |
| 7.01 | N = 10⁶ | 14.1 |
| 7.28 | N = 10⁶ | 14.4 |
| 7.55 | N = 10⁶ | 14.6 |
| 7.84 | N = 10⁶ | 14.9 |
| 8.14 | N = 10⁶ | 15.2 |
| 8.45 | N = 10⁶ | 15.4 |
| 8.77 | N = 10⁶ | 15.7 |
| 9.11 | N = 10⁶ | 16 |
| 9.46 | N = 10⁶ | 16.3 |
| 9.82 | N = 10⁶ | 16.5 |
| 10.2 | N = 10⁶ | 16.8 |
| 10.6 | N = 10⁶ | 17.1 |
| 11 | N = 10⁶ | 17.3 |
| 11.4 | N = 10⁶ | 17.6 |
| 11.8 | N = 10⁶ | 17.9 |
| 12.3 | N = 10⁶ | 18.2 |
| 12.8 | N = 10⁶ | 18.4 |
| 13.2 | N = 10⁶ | 18.7 |
| 13.8 | N = 10⁶ | 19 |
| 14.3 | N = 10⁶ | 19.2 |
| 14.8 | N = 10⁶ | 19.5 |
| 15.4 | N = 10⁶ | 19.8 |
| 16 | N = 10⁶ | 20.1 |
| 16.6 | N = 10⁶ | 20.3 |
| 17.2 | N = 10⁶ | 20.6 |
| 17.9 | N = 10⁶ | 20.9 |
| 18.6 | N = 10⁶ | 21.1 |
| 19.3 | N = 10⁶ | 21.4 |
| 20 | N = 10⁶ | 21.7 |
| 1 | N = 10⁹ | 0 |
| 1.04 | N = 10⁹ | 0.181 |
| 1.08 | N = 10⁹ | 0.361 |
| 1.12 | N = 10⁹ | 0.542 |
| 1.16 | N = 10⁹ | 0.723 |
| 1.21 | N = 10⁹ | 0.903 |
| 1.25 | N = 10⁹ | 1.08 |
| 1.3 | N = 10⁹ | 1.26 |
| 1.35 | N = 10⁹ | 1.45 |
| 1.4 | N = 10⁹ | 1.63 |
| 1.45 | N = 10⁹ | 1.81 |
| 1.51 | N = 10⁹ | 1.99 |
| 1.57 | N = 10⁹ | 2.17 |
| 1.63 | N = 10⁹ | 2.35 |
| 1.69 | N = 10⁹ | 2.53 |
| 1.75 | N = 10⁹ | 2.71 |
| 1.82 | N = 10⁹ | 2.89 |
| 1.89 | N = 10⁹ | 3.07 |
| 1.96 | N = 10⁹ | 3.25 |
| 2.04 | N = 10⁹ | 3.43 |
| 2.11 | N = 10⁹ | 3.61 |
| 2.2 | N = 10⁹ | 3.79 |
| 2.28 | N = 10⁹ | 3.98 |
| 2.37 | N = 10⁹ | 4.16 |
| 2.46 | N = 10⁹ | 4.34 |
| 2.55 | N = 10⁹ | 4.52 |
| 2.65 | N = 10⁹ | 4.7 |
| 2.75 | N = 10⁹ | 4.88 |
| 2.85 | N = 10⁹ | 5.06 |
| 2.96 | N = 10⁹ | 5.24 |
| 3.08 | N = 10⁹ | 5.42 |
| 3.19 | N = 10⁹ | 5.6 |
| 3.31 | N = 10⁹ | 5.78 |
| 3.44 | N = 10⁹ | 5.96 |
| 3.57 | N = 10⁹ | 6.14 |
| 3.71 | N = 10⁹ | 6.32 |
| 3.85 | N = 10⁹ | 6.51 |
| 4 | N = 10⁹ | 6.69 |
| 4.15 | N = 10⁹ | 6.87 |
| 4.31 | N = 10⁹ | 7.05 |
| 4.47 | N = 10⁹ | 7.23 |
| 4.64 | N = 10⁹ | 7.41 |
| 4.82 | N = 10⁹ | 7.59 |
| 5 | N = 10⁹ | 7.77 |
| 5.19 | N = 10⁹ | 7.95 |
| 5.39 | N = 10⁹ | 8.13 |
| 5.6 | N = 10⁹ | 8.31 |
| 5.81 | N = 10⁹ | 8.49 |
| 6.03 | N = 10⁹ | 8.67 |
| 6.26 | N = 10⁹ | 8.85 |
| 6.5 | N = 10⁹ | 9.03 |
| 6.75 | N = 10⁹ | 9.22 |
| 7.01 | N = 10⁹ | 9.4 |
| 7.28 | N = 10⁹ | 9.58 |
| 7.55 | N = 10⁹ | 9.76 |
| 7.84 | N = 10⁹ | 9.94 |
| 8.14 | N = 10⁹ | 10.1 |
| 8.45 | N = 10⁹ | 10.3 |
| 8.77 | N = 10⁹ | 10.5 |
| 9.11 | N = 10⁹ | 10.7 |
| 9.46 | N = 10⁹ | 10.8 |
| 9.82 | N = 10⁹ | 11 |
| 10.2 | N = 10⁹ | 11.2 |
| 10.6 | N = 10⁹ | 11.4 |
| 11 | N = 10⁹ | 11.6 |
| 11.4 | N = 10⁹ | 11.7 |
| 11.8 | N = 10⁹ | 11.9 |
| 12.3 | N = 10⁹ | 12.1 |
| 12.8 | N = 10⁹ | 12.3 |
| 13.2 | N = 10⁹ | 12.5 |
| 13.8 | N = 10⁹ | 12.6 |
| 14.3 | N = 10⁹ | 12.8 |
| 14.8 | N = 10⁹ | 13 |
| 15.4 | N = 10⁹ | 13.2 |
| 16 | N = 10⁹ | 13.4 |
| 16.6 | N = 10⁹ | 13.6 |
| 17.2 | N = 10⁹ | 13.7 |
| 17.9 | N = 10⁹ | 13.9 |
| 18.6 | N = 10⁹ | 14.1 |
| 19.3 | N = 10⁹ | 14.3 |
| 20 | N = 10⁹ | 14.5 |
That cost is spread over the whole population of readers as a slightly worse experience. It is the clearest example of a cost that is relocated onto everybody, diffusely (costs are conserved and only move). It is also the hardest kind of cost to attribute, so it is the kind most likely to last for years. Nobody files a ticket about five points of hit rate they never knew they had.
Session guarantees come for free
Versioned keys also give you two session guarantees at no extra cost:
- Read-your-writes: after you write, your own reads see your write. If the client keeps the version token from its last write and puts it in the key, then a cache that does not have that version simply misses and goes to the store.
- Monotonic reads: you never see data go backwards in time. The client refuses to accept a version lower than the highest it has already seen.
Both use only client-side state, which is why they are cheap. They are also the only sound way to get session guarantees over an incoherent cache. Sticky routing (always sending one user to the same cache node) is the weaker substitute. It breaks at the first rebalance, and Making it fit shows that rebalances are routine, not exceptional.
Compression: Versioned keys are MVCC with eviction as the vacuum. The retention cost is shared by all readers as ln V / ln N points of hit rate. The version token in the client's hand buys read-your-writes.