Architecture
A two-tier design: each worker in the fleet keeps a small, fast, per-process in-memory LRU cache
(least recently used, meaning that when the cache is full, the item that hasn't been accessed in the
longest time is the one evicted to make room) as its L1, and Redis serves as a shared L2 that every
worker in the fleet can read from and write to.
Consistency model: eventual, deliberately
Strong consistency across every worker's L1 (guaranteeing all workers agree on a value at every
instant) would require a synchronous invalidation broadcast on every write, reaching every worker
before the write is considered complete, which is real coordination overhead for a workload where the
cache exists purely to speed up repeated computation, not to serve as a system of record. Eventual
consistency, where a worker's L1 can be briefly stale relative to Redis or another worker's L1, is the
practical choice: a worker occasionally recomputing something that another worker already produced a
moment earlier is wasted work, not a correctness bug, as long as the shared L2 (Redis) is the one
place treated as authoritative for anything that must be correct.
Eviction coordination
L1 eviction needs no cross-worker coordination at all: each worker manages its own memory budget
independently, evicting its own least-recently-used entries with no awareness of what any other
worker's L1 currently holds. L2 (Redis) eviction is handled by Redis's own maxmemory-policy
(allkeys-lru or allkeys-lfu depending on whether recency or frequency better predicts what's worth
keeping for this workload), and doesn't need to know anything about individual workers' L1 contents
either, since it's a shared resource independent of any one worker.
Cache-aside versus write-through
Cache-aside (a worker checks L1, then L2, and on a miss computes or loads the value itself and
populates both tiers) fits a data-processing pipeline well when writes are comparatively rare relative
to reads, which is typical when most cached values are derived/computed results rather than frequently
mutated state. Write-through (write to L2 synchronously as part of producing a new result, rather than
waiting for a later reader to populate the cache) is the better fit when one worker's output is meant to
be immediately available to other workers, since it removes the gap where another worker could redo the
same computation before the producer's result ever reaches the shared cache.
Warm-up and rehydration
A restarted worker (deploy, crash-restart, autoscale-up event) starts with an empty L1, causing a burst
of L2 reads until it re-warms naturally from traffic, which is generally fine precisely because L2
already holds the data, that's the point of having a shared tier at all. Where the cold-start latency
dip matters, pre-populate the worker's L1 at startup with the top-N most-frequently-accessed keys:
track access counts in a Redis sorted set with ZINCRBY access:counts 1 <key> on every L2 read, then
at startup fetch the top N with ZREVRANGE access:counts 0 N-1 (or, on Redis 6.2+, the unified
ZRANGE access:counts 0 N-1 REV). Plain ZRANGE returns members in ascending score order, so it
would hand back the LEAST-accessed keys, the opposite of what a warm-up needs; I confirmed the
direction against a real Redis instance with a small sorted set (keyD at score 100, keyB at 50,
keyC at 10, keyA at 5, keyE at 1):
ZRANGE access:counts 0 2 WITHSCORES -> keyE 1, keyA 5, keyC 10 (ascending: least-accessed first)
ZREVRANGE access:counts 0 2 WITHSCORES -> keyD 100, keyB 50, keyC 10 (descending: most-accessed first)
ZREVRANGE (or ZRANGE ... REV) is the one that actually produces a useful warm-up set; using plain
ZRANGE would silently pre-populate L1 with the coldest keys in the whole keyspace and defeat the
point of pre-warming.
Minimizing cross-worker misses and network overhead
- Size L1 to capture each worker's own hot working set, measured by tracking each worker's own L1 hit
rate rather than guessing a size.
- Where the workload allows it, route the same keys consistently to the same worker (partitioning a
batch job by key range, or consistent hashing across workers) so a given worker's L1 accumulates a
genuinely relevant hot set instead of a random slice of the whole keyspace, structurally raising L1
hit rate rather than relying on chance.
- Batch L2 reads (a single
MGET for many keys) instead of many sequential round trips when a worker
needs several keys at once, since round-trip latency, not Redis's own per-command cost, is usually
the dominant overhead at that scale.