Skip to main content
Production Eviction Patterns

Choosing an Eviction Policy Without Waiting for Production Meltdown

So you're running a cache layer—Redis, Memcached, maybe an in-process LRU map. Things are fine until they're not. One day your p99 latency doubles. The DB connections spike. Someone yells 'cache miss storm' in the on-call channel. That's when most teams start thinking about eviction policy. But by then, you're already in firefighting mode. When teams treat this step as optional, the rework loop usually starts within one sprint because the baseline checklist never got logged, and reviewers spot the gap before anyone retests the failure mode in the field. This article exists so you can choose before the meltdown. We'll skip the textbook definitions and talk about what actually breaks in production: miss ratio cliffs, write-heavy skews, and policies that work great on week one but silently degrade over a month. You'll get concrete steps, tooling advice, and a survival checklist.

图片

So you're running a cache layer—Redis, Memcached, maybe an in-process LRU map. Things are fine until they're not. One day your p99 latency doubles. The DB connections spike. Someone yells 'cache miss storm' in the on-call channel. That's when most teams start thinking about eviction policy. But by then, you're already in firefighting mode.

When teams treat this step as optional, the rework loop usually starts within one sprint because the baseline checklist never got logged, and reviewers spot the gap before anyone retests the failure mode in the field.

This article exists so you can choose before the meltdown. We'll skip the textbook definitions and talk about what actually breaks in production: miss ratio cliffs, write-heavy skews, and policies that work great on week one but silently degrade over a month. You'll get concrete steps, tooling advice, and a survival checklist.

Who needs this and what goes wrong without it

The typical team that ignores eviction policy until it hurts

I have seen the same scene unfold in three different companies. A team builds a caching layer—Redis, Memcached, something local like Caffeine—picks the default eviction policy, and ships it. The default is almost always LRU or no eviction at all. That feels safe. The service runs fine for weeks. Then traffic doubles during a product launch. Memory fills. And because nobody configured an eviction policy with intent, the system starts evicting the wrong data—the hot keys that were keeping p99 latency under control. The catch is you don't notice immediately. You notice when the database connection pool saturates and alerts start screaming at 2 AM. That's not a theoretical tuning knob. It's a reliability lever. Flip the wrong one and the whole system wobbles.

Most teams skip this because eviction policy looks like a performance micro-optimization.

Cut the extra loop.

Wrong. It's a fault-containment boundary. Without it, you invite cache churn—the cycle where evicted entries keep getting re-fetched, consuming bandwidth and CPU, crowding out the data that could have stayed. I have seen a single misconfigured maxmemory-policy in Redis cause a 4× spike in database reads. Not from a code bug—from an eviction policy that tossed hot TTL'd entries while keeping stale cold ones alive. That hurts. Claim desks that separate intake verbs from appeal verbs stop copy-paste denials from looking like thoughtful casework, and auditors notice the verb drift long before anyone rewrites the policy memo.

'We thought 'all keys are roughly equal' until the eviction thread proved otherwise. The hottest keys got dumped first.'

— Systems engineer, postmortem notes after a 90-minute outage

Real-world failure modes: cache churn, tail latency, and thundering herds

The first thing that breaks is tail latency. You evict a key that was under active write load. The next read misses, goes to the database, and that request now takes 200 ms instead of 2 ms. Fine for one request. Not fine when the eviction policy sweeps a whole hot shard because memory pressure peaked for thirty seconds. Now every read in that shard cascades into a database call. The database starts queueing. Latency for all requests—even unrelated ones—climbs. That's the second failure mode: tail latency snowballs into average latency. Nobody planned for it because the policy looked fine on a single-node test with uniform access patterns.

Then comes the thundering herd. An eviction policy that frees space by removing the least recently used entries can, under burst load, wipe out a batch of keys that were about to be accessed again by the next wave of requests. Every worker thread misses, every thread issues a recompute or a DB read simultaneously. That's a herd. I have debugged a production incident where a microservice's eviction policy triggered a 30-second gap of empty cache, followed by a 5× spike in origin latency until the herd subsided and the cache refilled. The policy had been left on the vendor default—no thought given to whether the workload was scan-heavy or write-heavy. It was both, and the default could not handle it. Not yet designed for that mix.

The pitfall most engineers miss: eviction policy interacts with TTL policy. Set TTLs too short and the eviction thread barely runs—you waste memory. Set TTLs too long and the policy becomes the dominant ejector, but without tuning the policy's behavior to the access pattern, you get the worst of both worlds—stale data and hot evictions. That sounds fine until you trace a production incident and discover the eviction histogram shows your most valuable keys were popped every 90 seconds while stale session data lived for hours. The fix was not more memory. It was swapping from allkeys-lru to volatile-ttl and shortening TTLs on disposable keys. Simple change. Took one escalation to force it.

Prerequisites you should settle before even thinking about policy

Workload characterization: hit ratio, access skew, write/read ratio

Before you even glance at an eviction policy's name in the config file, you need to know what your cache actually does at runtime. I have watched teams slap allkeys-lfu onto a Redis instance that mostly holds short-lived session tokens—waste of memory. The cache hit ratio barely budged. You have to ask: is your workload heavy on reads or writes? If 80% of your keys are written once and never touched again, LFU's frequency counter is useless decoration. That data is dead weight. Conversely, a read-heavy feed with strong skew—say 20% of keys get 95% of traffic—is exactly where LFU shines and LRU flops. The catch is that most production systems don't have one pure pattern. They shift. One hour your cache holds product catalog data; the next hour a flash sale floods it with ephemeral cart items. Picking a policy without this baseline characterization is like guessing the lock combination—you might get lucky, but the seam blows out under load.

Access skew is the real compass. A Zipfian distribution means you can evict aggressively and still serve most reads from a small cache. Uniform access means every eviction is a potential miss. I have seen a team cut their cache size by 40% after realizing their access was 90/10 skewed. They swapped from volatile-lru to allkeys-lfu and their hit ratio rose. That sounds backwards until you understand that TTLs on hot keys were expiring early, forcing re-fetches. Wrong order. The write/read ratio matters too. Write-heavy workloads with frequent updates turn LRU into a churn machine—every write bumps a key to the front, poisoning the eviction candidate pool. Honestly—I have debugged a case where a logging service wrote 10,000 keys per second to a 1 GB cache. LRU evicted every useful key within minutes. The fix wasn't a better policy; it was admitting those logs should never have been cached. So before you configure maxmemory, instrument your production traffic for at least a week. Log access frequency. Measure TTL compliance. If you can't name your hot keys and their expiration spread, you're not ready to choose an eviction policy.

Memory budget and maxmemory setting sanity checks

Most teams skip this: a wrong maxmemory setting makes any eviction policy behave like a drunk driver. The classic mistake is setting it too high—near the physical RAM limit—then wondering why Redis starts swapping or OOM kills happen under peak. Swap is death for latency; a single page fault can spike a read from microseconds to milliseconds. Set maxmemory to 70–80% of available RAM, leaving headroom for replication buffers, client output buffers, and the occasional spike. I once helped a team that ran with maxmemory set to exactly the instance size—16 GB on a 16 GB machine. It worked in staging because load was synthetic. In production, a background save forked the process, doubled memory briefly, and the kernel killed Redis. That hurts.

The second sanity check is whether your eviction policy can actually reach its candidates. If you use volatile-lru but 90% of your keys have no TTL, the evictor starves. It keeps scanning the volatile set, finding nothing, and eventually errors when memory fills. That's not a policy failure—it's a configuration mismatch. I have seen this pattern three times this year alone. The fix is either assign TTLs to most keys or switch to allkeys-lru.

‘Never let the eviction policy be a bandage for a memory budget you haven't audited. Audit first, configure second.’

— infrastructure engineer, after recovering a production cache meltdown

Another trap: assuming maxmemory-policy alone controls eviction. Many forget that maxmemory-samples (default 5 in Redis) affects how accurately LRU or LFU approximates the ideal eviction candidate. Low samples mean faster eviction but worse quality—you might throw out a hot key because the sample missed it. I have bumped samples to 10 on read-heavy workloads and seen hit ratio improve 3–5% without touching the policy itself. That's a configuration lever, not a policy decision, but it's one you must check before blaming the eviction algorithm. So settle the budget, verify the TTL coverage, and tune the sampling. Only then is your policy choice meaningful. Otherwise you're debugging symptoms, not the root cause.

Core workflow: How to pick an eviction policy step by step

Step 1: Measure your working set size and miss ratio curve

Stop guessing. I have watched teams waste weeks debating LRU versus LFU without a single number to ground the argument. You need two things before any policy talk: the working set size — how many unique keys your application touches in a typical window — and the miss ratio curve, which plots cache misses as you shrink or grow the allocated memory. Most caching platforms expose hit/miss counters; pull those over a full business cycle, not a quiet Sunday afternoon. Graph the miss ratio at 50%, 75%, 100%, and 125% of your current cache size. That curve tells you whether doubling memory drops misses from 12% to 4% — worth it — or from 6% to 5.5%, in which case your access pattern is spread too thin for any policy to save you.

The catch is that measuring during low traffic gives you a fairy-tale curve. One team I consulted collected data during a three-day holiday lull, then wondered why their shiny new policy collapsed under Black Friday volume. Run the scrape over peak hours, ideally multiple weeks. A 24-hour snapshot can lie — batch jobs, midnight cron runs, or weekly report queries distort patterns. If you can't instrument production traces yet, at minimum dump your cache’s current key distribution and compute the hot-key percentage: what fraction of requests hit the top-1% of keys? Above 60%? LFU might dominate. Below 20%? You're looking at a near-uniform spread where clock-based eviction or random TTL expiration performs just fine.

What usually breaks first is the assumption that your working set is static. It isn’t. A product launch, an API deprecation, a seasonal spike — each reshuffles which data matters. So measure again after any significant code change. — three-week trace, two different load levels

Step 2: Match policy to access pattern (LRU for temporal, LFU for frequency, etc.)

Now you have numbers. Use them. If your miss ratio curve shows steep improvement up to a certain memory threshold and then flattens, you likely have a strong temporal locality — meaning the same keys are requested in bursts, then rarely touched again. That's LRU territory: recently used items stay hot, old ones slide out. But LRU fails hard when patterns are periodic — think a daily report that runs every 12 hours. The cache purges the data minutes before the next request hits, evicting something that will be needed again. That's where LFU or a hybrid (like ARC or 2Q) buys you stability.

Honestly — if your workload is scan-heavy, no frequency-based policy helps. Scans touch thousands of unique keys in a short window, polluting the cache with one-hit wonders. I have seen LFU degrade worse than LRU here because the counter for each scanned key inflates before the scan finishes, poisoning the ranking. For scan-heavy patterns, size-aware eviction (evicting large objects first) or a bloom-filter admission guard beats frequency tracking every time. The trade-off: admission filters add latency per insert — typically 2–10 microseconds — which matters if your cache sits in a request-hot path returning sub-millisecond responses.

One rhetorical question worth asking: does your application tolerate stale data? If yes, TTL-based eviction works fine and requires zero policy tuning. If no, you need a policy that handles cache stampedes — multiple requests all missing simultaneously and hammering the database. LRU with a stochastic TTL jitter often tames this without the complexity of full adaptive policies. Match the policy to the pain point, not the textbook.

Step 3: Simulate with production traces before deploying

The biggest pitfall? Deploying a new eviction policy directly to production. You can't reason about cache behavior in your head — the interplay between write volume, TTL expiry, and eviction order produces emergent behavior that surprises even veteran engineers. Pull a replayable trace: log every cache key and operation (get/set/delete) over a 48-hour window. Use a simulation framework — Redis’s redis-benchmark with custom scripts, a Java simulator like caffeine-simulator, or even a Python script that feeds your trace to an in-memory model of each candidate policy.

We fixed a particularly nasty regression by replaying a trace that revealed LFU’s adaptive decay parameter was too aggressive, flushing nearly-hot keys after idle periods of only 30 seconds. The simulator caught it in two hours; production would have taken a week of pager duty. Run three scenarios: current baseline (your existing policy), the new policy, and a “worst-case” trace where you double request volume. Watch three metrics: miss ratio, average eviction age (how long an item survives before being kicked), and write amplification — how many extra writes the eviction policy triggers per second. A policy that halves misses but doubles write load might burn through disk bandwidth on persistent caches.

Tools, configuration, and environment realities

Redis eviction policy settings and their performance trade-offs

You set maxmemory-policy allkeys-lru and walk away. That's the default for a reason—but the reason might be wrong. I have debugged a production node where allkeys-lru evicted a hot key that had been accessed eight times in the last minute simply because a batch job read a million one-off session tokens. LRU doesn't track frequency, only recency. Your flame graph will show a sudden latency spike as the miss rate climbs, and the natural reaction is to double memory. Not yet. Check lfu-decay-time first: Redis's LFU can decay so fast that a popular key evaporates during a lunchtime lull. The gotcha that bites most teams is maxmemory-samples. Default is five. Bump it to ten and you spend more CPU per eviction cycle—maybe 12% more—but you get a truer picture of the keyset. That trade-off is invisible in staging because you never have ten million keys there.

The catch? volatile-lru seems safer but creates a tiered cache where non-expiring keys live forever. I watched a chat service grow its volatile set to 80% of total memory, then the policy could only evict from the remaining 20%—effectively turning a 32 GB node into a 6 GB cache. Choose allkeys-* variants unless you have a concrete reason to protect certain keys. And volatile-ttl? That's a trap for anyone who sets variable TTLs: if your application accidentally assigns a 24-hour TTL to placeholder objects, those objects become eviction-resistant. Wrong order. Teardown point: test eviction policy on a node with real data shapes, not synthetic uniform distributions.

Memcached's LRU crawler and slab rebalancing quirks

Memcached doesn't have a single LRU—it has one per slab class. Objects of 400 bytes live in slab 8, objects of 800 bytes in slab 9, and each slab runs its own LRU independently. That means a 100-byte key popular enough to fill slab 5 can be evicted while slab 6 sits half-empty with stale 200-byte values. The lru_crawler thread tries to fix this by walking the LRU tail and reclaiming expired items, but it's cooperative—it yields between slabs. On a busy node, the crawler may never finish a full pass before new writes force eviction. I once saw a media cache where slab 3 evicted every 2 minutes while slab 7 held 40% free space. The fix was not a policy change—it was slab_reassign and slab_automove, both of which default to off. Enable automove and set it to 2 (aggressive mode), but accept that slab rebalancing pauses writes momentarily. For latency-sensitive workloads, you might prefer manual assignment: pin slab classes to expected key sizes and accept the operational overhead.

The cleanest Memcached deployment I have seen used three slab classes, each sized by a week of object-size histograms. It never hit the slab-eviction mismatch again.

— ex-SRE, large ad-targeting pipeline

In-process caches (Guava, Caffeine) and clock-pro variants

Guava's maximumSize with LRU looks safe in microbenchmarks—until you realize Guava doesn't expire keys by time when maximumSize is the sole constraint. Your cache won't shrink until a new key forces eviction, so stale data accumulates silently. A 50 MB cache can hold yesterday's API responses that nobody touches, but they occupy space because no new key pushes them out. Caffeine's Window-TinyLFU solves this by maintaining a frequency sketch that remembers items longer than LRU would, and it uses a maximumWeight combined with expireAfterWrite. The trap in Caffeine is expireAfterAccess—if your read pattern touches all keys in a batch scan, the scanner resets the access timer for every object, pinning the entire dataset in memory. Use expireAfterWrite unless you explicitly want read-renewed leases.

Clock-Pro (the adaptive variant) sits somewhere between LRU and LFU. Its strength is handling scan-resistant workloads: a one-time bulk read doesn't flush the hot cache. The weakness is parameter sensitivity. The ghost list size, the cold/hot threshold—these need tuning against real traffic replay, not synthetic workloads. One team I consulted set Clock-Pro on a user-session cache and saw eviction correctness improve, but CPU usage jumped 7% because the ghost-list management added per-access overhead. That sounds trivial until you run 100,000 ops per second. Does your logging infra even measure that 7%? If not, you're tuning blind. The reality check: in-process caches tie eviction to garbage collection pressure. Caffeine uses concurrent rings, but a poorly tuned eviction policy can double your minor-GC pause time because the eviction work happens on the inserting thread. Profile before you pick.

Variations for different constraints: memory, latency, and write patterns

Low-latency vs. high-hit-rate trade-offs

I watched a team burn three sprints chasing a 99.9% hit rate while their p99 latency crawled from 2ms to 47ms. The eviction policy wasn't the problem—the goal was. When you optimize for hit rate, you keep more entries alive, which means bigger index scans and more TTL checks per read. That sounds fine until your cache sits between a user-facing API and a PostgreSQL replica that can't breathe. Low-latency workloads often benefit from a simpler LRU with aggressive early eviction—sacrifice 3–5% hit rate, drop latency by 60%. The reverse holds too: a recommendation engine that serves async batch calls can tolerate 20ms lookups but can't afford a cold miss that forces a 400ms database query. Most teams pick a policy before measuring their actual bottleneck. Don't. Profile miss cost vs. scan cost first, then choose.

'We cut our eviction scan window from 200ms to 12ms by switching from LFU to a clock-based LRU. The hit rate dropped 4%. Nobody noticed. The timeout alerts stopped.'

— Senior SRE, ad-tech platform, 2024 migration postmortem

The catch is that clock-probe variants (ARC, 2Q) try to bridge this gap by keeping a small hot set separate from the main LRU chain. They work—until your access pattern shifts every Tuesday during feature rollouts. Then the hot set warms stale, and you're back to tuning thresholds manually. Honest advice: start with simple LRU, instrument cache scan duration and miss penalty separately, and then decide if the complexity buys you anything.

Write-heavy workloads and the case for TTL-first or LFU

Write-heavy caches are a different animal. Every eviction policy has a cost per eviction—updating pointers, recalculating frequency, scanning for expiry. I fixed one system where Redis allkeys-lfu was thrashing on a 20:1 write-to-read ratio. The LFU counters updated on every write, even for entries the application never read again. That's wasted work. For workloads where most keys are written once and expire within minutes, a TTL-first policy (or simple volatile-lru) beats everything else. The eviction thread only touches entries whose TTL field is set—everything else is ignored. Write cost drops to nearly zero per eviction.

LFU shines when the same hot keys get hammered repeatedly and the working set exceeds memory. Think rate-limit counters, session stores for a login endpoint, or a leaderboard that recalculates every thirty seconds. But LFU carries a hidden pitfall: frequency aging. Without decay, a key written once and never touched again can sit in the cache for hours, blocking newer, more active keys. That hurts. We fixed this by combining LFU with a background decay job that halves all counters every five minutes—dirt simple but effective. If your write pattern is bursty, consider memoized-LRU (prefer LRU but keep a small separate hot set for top-1000 accessed keys). It adds one hash check per read but avoids the full LFU overhead.

Seasonal or bursty access patterns and adaptive policies

Black Friday traffic doesn't care about your carefully tuned LRU from June. Bursty patterns—flash sales, tweet storms, deployment rollbacks—create a spike of new hot keys that immediately push out older, still-valid entries. Standard LRU fails here because it treats recency as the only signal. An entry that was hot two hours ago is evicted the moment a burst starts, and every miss falls back to origin. That compounds latency exactly when traffic is highest.

Adaptive policies like ARC (Adaptive Replacement Cache) or TinyLFU with a frequency sketch handle this by maintaining two internal lists: recent and frequent. When a burst hits, the recent list absorbs the new keys without flushing the frequent list entirely. The downside? Memory overhead—ARC uses roughly 20% more metadata than plain LRU. And ARC tuning is not fire-and-forget: the adaptation rate itself needs bounds, or a slow ramp-up can oscillate between evicting everything old then everything new. We saw that in a CDN edge cache: ARC cycled through 80% of the cache every ninety seconds during a gradual traffic shift. The fix was capping the adaptation step size to 5% of the cache per minute. Not elegant. Worked.

For teams that can't run ARC, a pragmatic middle-ground is dual-stage LRU with a probationary segment. Let new entries live in a small buffer (5–10% of total cache) for one full eviction cycle. If they survive, promote them to the main LRU. This absorbs bursts without a full rewrite. The cost is one extra pointer per entry and a slightly longer eviction pass. Worth it if your traffic has spikes above 3× baseline. Measure first.

Pitfalls, debugging, and what to check when it fails

Misleading metrics: why hit ratio alone can lie to you

A team I once worked with celebrated hitting 98% cache hit ratio. They shipped to production, and response times actually rose. The catch? They were evicting aggressively from a cache that mostly held rarely-accessed metadata—their popular product data was blowing out every few minutes. Hit ratio measures what stays, not what matters. A high ratio can hide a cache that serves only trivial lookups while the expensive objects cycle through unused slots. Monitor miss latency distribution instead. If your 99th percentile miss takes 200ms and your hit saves 5ms, a 90% ratio on trivial keys is worse than 60% on heavy ones.

Another trap: cold-start bias. When a new node joins, hit ratio collapses and operators panic—wrongly blaming the eviction policy. You're seeing warm-up, not policy failure. Give the cache at least one full TTL cycle before reading ratios at all. That sounds obvious. Most teams skip this.

‘We swapped from LRU to LFU and the hit ratio dropped twenty points overnight. After three days it climbed back past the old number—but nobody told ops to wait.’

— principal engineer, fintech monitoring platform

The thundering herd problem and how eviction policy can make it worse

Here is the scenario: a popular object gets evicted. Twenty concurrent requests miss simultaneously. Each request attempts to regenerate the cached data—database queries, API calls, expensive computation. The cache fills again, but now twenty identical writes race for the same slot. That hurts. Certain eviction policies amplify this. LRU, for example, will often evict a differently hot key just before the herd completes, triggering a secondary miss cascade. I have seen Redis clusters fall over because a single eviction under LRU caused a chain reaction of misses that spiked CPU to 100%.

One fix: insert a per-key mutex or renaming pattern—allow only the first miss to regenerate, and have subsequent requests wait on that single result. Another: consider an expiration-tolerant policy like 2Q, which gives frequently-accessed items a probationary buffer and avoids evicting them the instant a burst arrives. Worth noting—TTL-based eviction doesn't protect you here either. If all keys share the same TTL window, they expire together. Stagger expirations artificially (add ±10% jitter). Otherwise your ‘eviction policy’ is really just ‘random failure at clock boundaries.’

Stale data vs. high churn: diagnosing policy misconfiguration

Two symptoms look identical on dashboards: low hit ratio and elevated writes. One comes from a policy that evicts too aggressively (LRU with a tiny pool), the other from data that legitimately changes fast. How to tell? Sample the eviction age. If evicted objects consistently lived less than one write interval, your policy is thrashing—it kicks out keys before they get a second read. If evicted objects lived multiple write intervals but were never read again, you have a TTL problem, not an eviction one. Different fix entirely.

What usually breaks first is the assumption that one policy fits all endpoints. A session cache benefits from TTL-first eviction because stale sessions are dangerous. A CDN edge cache wants size-aware eviction—evict the largest cold object first. When both share the same cache pool, policy choice becomes a root cause of production meltdown. Separate caches by access pattern. Or accept that one will suffer. Honest—I have never seen a single-policy cache serve mixed workloads well without manual tuning every quarter.

Debugging step when it fails: enable eviction counters per key class. Most caching platforms (Redis, Memcached, Caffeine) expose these. Compare eviction rates for hot vs. cold key groups. If eviction rate is uniform across both, your policy is effectively random. Reconfigure or split the cache. Last check—look at object size distribution. If your eviction policy is size-aware but your largest objects are also your hottest, you will evict your best data first. That's a design mismatch, not a bug.

FAQ or checklist: Quick sanity checks for eviction policy

Checklist before deployment

You're about to push an eviction policy into production. Stop. Run these checks first — one miss and you're debugging at 3 AM with a pager strapped to your wrist. Is your cache key space bounded? If you answered "I think so" rather than "yes", you haven't bounded it. Unbounded keys eat memory silently until the OOM killer shows up. Have you measured the working set size under realistic load? Not your staging environment with three users — real traffic, real cardinality. I have seen teams set maxmemory to 80% of RAM and wonder why everything falls over during a flash sale. That hurts.

Do you know what happens when the eviction policy fires? Most engineers simulate this once in a lab, see it work, and ship it. The catch is that eviction under pressure behaves differently: batch deletes spike latency, TTL storms cascade, and suddenly your upstream clients see a wall of timeouts. Is your monitoring wired to distinguish between "eviction working" and "eviction thrashing"? If your metrics only show memory usage, you're flying blind. Add eviction-rate-per-second and latency-per-eviction-cycle dashboards before you go live — or don't deploy.

An eviction policy that works at 60% memory may silently kill your service at 80%.

— Real postmortem from a Redis cluster that survived three months of normal load then died during a 2x traffic spike.

Last sanity check: Does your test suite include a "full cache, steady state, then sudden write burst" scenario? Not yet? That scenario is exactly what breaks LRU when write patterns shift from read-heavy to write-heavy. Fix it before you hit deploy.

Checklist during an incident

Your pager just went off. Cache hit rate cratered, latency spiked, and someone is yelling in the Slack channel. Don't start changing policies yet. First: is the eviction policy actually the problem? Check the eviction rate graph — if it's flat or zero, your issue is elsewhere (network, compaction, or a misconfigured client). If eviction rate is climbing vertically, you're in trouble. What is the TTL distribution of your keys? If 80% of your keys expire within the same five-minute window, you're not seeing an eviction bug — you're seeing a TTL stampede. The fix there is jitter, not a policy change.

Are you evicting hot keys? Run a quick histogram: top-100 accessed keys, and check how many got evicted in the last minute. If you see hot keys getting kicked out, your policy is choosing wrong — or your cache size is catastrophically undersized. Can you increase maxmemory temporarily without restarting? Most production systems allow online resize. Do that first. Buying five minutes of breathing room beats guessing the right policy under fire. I have seen teams swap from allkeys-lru to volatile-ttl during an incident and make things worse — because their volatile keys were already expired, so eviction did nothing.

One rhetorical question to ask yourself: is your cache supposed to be a hot-data accelerator or a system of record? If you answered the latter, you already lost — eviction policy can't fix a design that treats caches as databases. Final incident check: are you logging eviction victims? If not, you're debugging blind. Add a debug log for evicted key names (sampled, not every single one) — that single change has saved my team more than any policy analysis ever did.

Share this article:

Comments (0)

No comments yet. Be the first to comment!