The CLOCK Cache Eviction Pattern: LRU's Approximation That Runs at Hardware Speed

2026-07-15

True LRU requires updating a doubly-linked list on every cache hit — pulling the node out and moving it to the head. That's fine for a small in-process cache, but at OS page-cache or database buffer-pool scale, taking a lock and mutating pointers on every read is a scalability disaster. The CLOCK algorithm is the workaround that virtually every operating system and major database actually ships.

The idea: arrange cache entries in a circular buffer. Each entry has a single reference bit. On a hit, set the bit to 1 — no lock, no list surgery, just a single-byte write. When you need to evict, a "hand" (like a clock hand) sweeps around the circle:

Entries that get touched between sweeps survive. Entries that don't get touched get evicted on the next pass. It approximates LRU without any per-hit bookkeeping.

Real-world example: PostgreSQL's buffer manager uses a variant called CLOCK-Sweep. Instead of a single reference bit, each buffer has a usage counter (0–5). A hit increments it (capped at 5); the sweep decrements it and evicts anything at 0. Frequently-used pages accumulate a "grace count" and survive multiple sweeps. Linux's page cache uses a two-list CLOCK variant (active/inactive lists) for the same reason: at millions of page accesses per second, you cannot afford an LRU list mutation per access.

Rule of thumb — when CLOCK beats LRU: if your cache serves more than ~100K reads/sec, or if reads and writes contend on the same lock, switch to CLOCK. The hit-path cost drops from "acquire lock + unlink + relink + release" (~50–200ns under contention) to a single unsynchronized byte store (~1ns). The eviction path is slightly more expensive — you might sweep several entries before finding a victim — but eviction is rare compared to hits.

The tradeoff: CLOCK is approximate LRU. An entry accessed once right before the hand arrives gets the same treatment as one accessed a thousand times. This is why production systems use enhanced variants (CLOCK-Pro, GCLOCK with counters, or CLOCK combined with a small LRU "protected" segment). Pure CLOCK is rarely optimal; pure LRU is rarely fast enough. The winner is almost always a hybrid.

If you find yourself building an in-memory cache and reaching for LinkedHashMap, ask: what happens when 32 threads hit this simultaneously? That's usually where CLOCK earns its keep.

Key Takeaway: CLOCK trades a tiny bit of LRU accuracy for lock-free hit paths, which is why every OS page cache and database buffer pool uses it instead of true LRU.

All newsletters