Senior 5 min · March 06, 2026

PHP Cache Stampede — Why 500 Requests Killed Our Database

One expired cache key triggered 500 database queries, spiking response times from 200ms to 50s.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Caching stores expensive computation results for reuse across requests.
  • APCu: in-memory per-server cache, sub-microsecond (<1µs) reads, limited to single node.
  • Redis: shared distributed cache, ideal for multi-server apps, adds ~1ms network latency per operation.
  • File cache: simple, no external services, but degrades above ~500 writes/sec due to disk I/O.
  • HTTP cache (Cache-Control/ETag): offloads work to browsers/CDNs, best for public content with zero server cost.
  • Production insight: cache stampede is the #1 killer under load — always use locking or stale-serve to survive.
Plain-English First

Imagine your favourite pizza shop. Every time someone orders a Margherita, the chef could start from scratch — kneading dough, chopping tomatoes, grating cheese. Or, he could make twenty Margheritas at once and keep them warm on a shelf. When the next order comes in, he grabs one instantly. Caching is that warm shelf. Your PHP app does expensive work once — a database query, an API call, a rendered HTML block — stores the result, and hands it out instantly to every request that follows. No repeated heavy lifting.

Every PHP app eventually meets the same wall: the database query that snapped at 100 users crawls at 10,000. You can scale horizontally, but that burns money. Caching is the cheap fix — compute once, serve a million times. The catch? Get it wrong and you'll serve stale data or crash the database during a stampede.

Here's the hard truth: caching isn't free. Every cache hit saves time, but every cache miss costs more than a direct computation because of serialisation overhead. That's why your cache hit ratio matters more than the cache type you pick.

By the end of this article you'll know exactly when to use APCu, Redis, file cache, or HTTP headers. You'll understand TTL design, stampede prevention, invalidation patterns, and the production mistakes that silently corrupt data. Real, runnable code accompanies every concept.

What Is Caching in PHP? — The Core Problem and How to Fix It

Caching in PHP isn't about storing everything — it's about identifying the expensive operations your app repeats. Think: the same database query returning identical results, an API call that hasn't changed, or a heavyweight HTML snippet. Caching stores that result once and serves it directly for subsequent requests.

Here's what no one tells you: a cache miss costs more than a direct computation because of serialisation overhead. If your hit ratio drops below 90%, caching may actually hurt performance. That's why you must monitor it — not just set it and forget it.

A typical example: a product list query that takes 200ms. If you cache it for 60 seconds and serve 1000 requests per minute, you save 200ms × 999 = ~200 seconds of DB time per minute. That's the ROI. But get the TTL wrong and the stampede will burn you.

src/Caching/ApplicationCache.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace TheCodeForge\Caching;

class ApplicationCache
{
    private int $warmupCount = 0;

    public function get(string $key, callable $expensiveCall, int $ttl = 3600): mixed
    {
        $cached = apcu_fetch($key, $hit);
        if ($hit) {
            return $cached;
        }
        $this->warmupCount++;
        $value = $expensiveCall();
        apcu_store($key, $value, $ttl);
        return $value;
    }

    public function warmups(): int
    {
        return $this->warmupCount;
    }
}
Forge Tip:
Type this code yourself rather than copy-pasting. The muscle memory of writing it will help it stick.
Production Insight
Caching isn't magic. It trades memory for speed.
If your hit ratio drops below 90%, caching becomes a liability.
Rule: always monitor hit ratio and eviction rates in production.
Key Takeaway
Caching is the highest-ROI optimisation in PHP.
But every cache is a promise — and broken promises cause bugs.
Cache only what you can afford to serve stale for a few seconds.
Is Caching Worth It?
IfExpensive operation runs more than 100 times per minute
UseCaching is almost certainly beneficial.
IfData changes on every request (e.g., real-time stock price)
UseCaching won't help — you'd serve stale data.
IfComputation is <5ms and memory is scarce
UseThe overhead of caching may not be worth it.

APCu: In-Process Cache for Microsecond Reads

APCu (APC User Cache) stores key-value pairs in shared memory available to all PHP-FPM workers on the same machine. It's the fastest cache you can get because there's no network I/O — reads take 100–300 nanoseconds. Perfect for computed data that is cheap to recompute and shared across requests on a single server.

But APCu's speed is also its biggest limitation: it only lives on one server. If you have multiple web servers, each has its own copy, and cache invalidation becomes a coordination problem. You also burn RAM per server — a 1 GB APCu store consumes 1 GB of memory on every node.

Here's a typical pattern: caching an expensive database query result that changes rarely. APCu fragmentation silently increases memory usage — monitor it via apcu_sma_info() in production and restart PHP-FPM when fragmentation exceeds 20%.

src/Caching/ApcuProductLookup.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace TheCodeForge\Caching;

class ApcuProductLookup
{
    private int $ttlSeconds;

    public function __construct(int $ttlSeconds = 300)
    {
        $this->ttlSeconds = $ttlSeconds;
    }

    public function getProductData(int $productId): array
    {
        $key = 'product:' . $productId;
        $cached = apcu_fetch($key, $hit);

        if ($hit) {
            return $cached;
        }

        // Cache miss: fetch from database
        $data = $this->loadFromDatabase($productId);
        apcu_store($key, $data, $this->ttlSeconds);
        return $data;
    }

    private function loadFromDatabase(int $productId): array
    {
        // Simulated heavy query
        usleep(50_000); // 50ms
        return ['id' => $productId, 'name' => 'Widget', 'price' => 9.99];
    }
}
Shared Memory Gotcha
APCu's memory is fixed-size. When full, it evicts the oldest entries (LRU). If you store too much, your hot data gets pushed out. Monitor apc.shm_size and eviction stats.
Production Insight
APCu is ideal for high-frequency keys (< 1 KB) on a single server.
When you add a second web server, each has a separate APCu cache — stale data appears on different requests.
Rule: if your app runs on more than one server, do NOT use APCu for anything that must be globally consistent.
Key Takeaway
APCu is smoking fast but server-bound.
Perfect for single-server apps; dangerous for multi-server without other coordination.
When scaling horizontally, APCu becomes a liability — not an asset.
When to Use APCu
IfSingle PHP server, hot data fits in RAM
UseAPCu is the fastest option. Use it for config objects, user sessions (if sticky), and computed results.
IfMultiple web servers, need consistent cached data
UseAvoid APCu. Switch to Redis or a shared file system (NFS) with locking — but Redis is strongly preferred.
IfData changes frequently (seconds-level)
UseAPCu's LRU eviction and per-server nature make invalidation messy. Use Redis with pub/sub instead.

Redis: Shared Distributed Cache for Multi-Server Apps

Redis is an in-memory data store accessed over TCP. In PHP, you'll typically use the PhpRedis extension or the predis/predis library. Redis gives you a single cache that every PHP worker — no matter which server — can read from and write to. This makes cache invalidation straightforward: delete the key and every server sees the change instantly.

The trade-off is network latency: each cache operation adds ~0.2–1ms round-trip. At 10,000 requests per second, that's 10 seconds of network overhead. But that's still orders of magnitude faster than querying a database.

A common pattern: cache an API response to avoid hitting rate limits or third-party latency. Redis connections are persistent — always reuse the same connection across requests. Opening a new connection per request adds ~5ms overhead, which destroys the benefit.

Monitoring tip: track keyspace_hits and keyspace_misses in Redis info. A hit ratio below 80% means your cache is mostly wasting memory.

src/Caching/RedisWeatherService.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

namespace TheCodeForge\Caching;

use Redis;

class RedisWeatherService
{
    private Redis $redis;
    private int $ttlSeconds;

    public function __construct(string $host = '127.0.0.1', int $port = 6379, int $ttlSeconds = 600)
    {
        $this->redis = new Redis();
        $this->redis->connect($host, $port);
        $this->ttlSeconds = $ttlSeconds;
    }

    public function getTemperature(string $city): ?float
    {
        $key = 'weather:' . $city;
        $cached = $this->redis->get($key);

        if ($cached !== false) {
            return (float) $cached;
        }

        // Simulated external API call
        $temp = $this->fetchFromApi($city);
        $this->redis->setex($key, $this->ttlSeconds, $temp);
        return $temp;
    }

    private function fetchFromApi(string $city): float
    {
        usleep(200_000); // 200ms
        return 22.5;
    }
}
Redis as a Hash Map That Never Forgets
  • All servers see the same data, always.
  • Operations are atomic (INCR, SETNX) – great for counters and locks.
  • Memory is finite – set eviction policy (allkeys-lru is common).
  • Network can be a bottleneck – pipeline multiple commands in one round trip.
Production Insight
Redis connections are persistent — do NOT open a new connection per request.
Use connection pooling via persistent Redis connections or a singleton client.
If Redis goes down, your app either fails or falls back to DB — always implement a fallback strategy.
Key Takeaway
Redis is the workhorse of distributed caching in PHP.
It solves the multi-server consistency problem that APCu creates.
But it's not free: every read bears network cost, and every failure requires a plan.
Redis vs APCu Decision
IfSingle server, minimal latency requirement
UseAPCu wins. Redis adds unnecessary network overhead.
IfMulti-server, consistent cache needed, acceptable ~1ms overhead
UseRedis is the only sensible choice.
IfNeed atomic operations or data structures beyond key-value
UseRedis — APCu only supports simple key-value with TTL.

File Cache: Simple, No Dependencies, but Watch the I/O

Sometimes you can't install extensions or run a separate service. A file-based cache writes serialised data to disk. It's trivial to implement — file_put_contents to write, file_get_contents to read, plus a timestamp check for TTL. This works great for small deployments, shared hosting, or as a fallback when Redis is unavailable.

The catch: file I/O is orders of magnitude slower than memory. At high concurrency, multiple processes writing to the same file create corruption risks. And there's no built-in eviction — your storage grows unbounded unless you clean it up.

Here's a safe implementation with proper locking and TTL. Under high concurrency, file locks cause contention — measure with iostat or lsof. If you see high wait time, switch to memory-based cache immediately.

Pro tip: for low-traffic sites (<10 req/s), file cache is perfectly fine. For anything above 100 req/s, you need memory.

src/Caching/FileCache.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

namespace TheCodeForge\Caching;

class FileCache
{
    private string $cacheDir;

    public function __construct(string $cacheDir)
    {
        $this->cacheDir = rtrim($cacheDir, '/');
        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0775, true);
        }
    }

    private function path(string $key): string
    {
        // Sanitise: only alphanumeric and dash
        return $this->cacheDir . '/' . preg_replace('/[^a-zA-Z0-9\-]/', '_', $key) . '.cache';
    }

    public function get(string $key, int $ttlSeconds = 0): mixed
    {
        $file = $this->path($key);
        if (!file_exists($file)) return null;

        $expires = filemtime($file) + $ttlSeconds;
        if ($ttlSeconds > 0 && time() > $expires) {
            unlink($file);
            return null;
        }

        $data = file_get_contents($file);
        return unserialize($data);
    }

    public function set(string $key, mixed $value): void
    {
        $fp = fopen($this->path($key), 'c+');
        flock($fp, LOCK_EX);
        fwrite($fp, serialize($value));
        fflush($fp);
        flock($fp, LOCK_UN);
        fclose($fp);
    }
}
File Cache Pitfalls
Without locking, concurrent writes corrupt the file. Use LOCK_EX. Also, file stat cache can cause stale reads — call clearstatcache() if polling often.
Production Insight
File cache works well for low-traffic sites (< 10 req/s).
Above that, disk I/O becomes a bottleneck — especially if using HDDs.
Always monitor disk space: a runaway cache can fill the disk and crash the app.
Key Takeaway
File cache is the 'duct tape' of PHP caching.
It works when you have nothing better, but it's not a long-term strategy.
If you hit disk I/O limits, migrate to memory — even Redis on localhost is better.

HTTP Cache: Use Browser and Edge to Eliminate Requests Entirely

HTTP caching works by telling the browser (or a reverse proxy like Varnish or CloudFlare) how long it can reuse a response without asking your PHP server at all. This isn't just fast — it eliminates the PHP execution entirely for cached resources. For public, static-like content (images, CSS, API responses that don't change per user), HTTP caching is the highest-leverage optimisation.

Two key headers: Cache-Control: max-age=3600 tells the browser to cache for 1 hour. ETag provides an opaque hash — the browser sends If-None-Match on subsequent requests, and you return 304 Not Modified without sending the body.

Important nuance: HTTP caching doesn't happen inside PHP. It's a contract between your server and the client. You must ensure your PHP correctly sets headers and respects If-Modified-Since or If-None-Match. For global audiences, use a CDN with revalidation — stale content gets cleared from the edge within minutes when you invalidate via the CDN API.

src/Caching/HttpCacheHandler.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace TheCodeForge\Caching;

class HttpCacheHandler
{
    public function handleRequest(): void
    {
        $etag = '"product-list-v3"';
        header('Cache-Control: public, max-age=3600');
        header('ETag: ' . $etag);

        if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
            http_response_code(304);
            exit;
        }

        // ... generate full response ...
    }
}
HTTPS and Cache Proxies
HTTPS doesn't prevent caching. But some CDNs require specific headers (s-maxage) to cache. Test with curl -I to see what your server sends.
Production Insight
HTTP caching is invisible to PHP — your app might never know a request happened.
This can lead to confusion when debugging stale content: the browser cached it, not PHP.
Rule: always add Cache-Control: no-cache for dynamic user-specific pages, or use private.
Key Takeaway
HTTP caching is the only cache that offloads work from your server entirely.
It's not a PHP cache — it's a contract that says 'don't even call me'.
Use it for public resources; avoid it for dynamic, per-user data.
HTTP Cache Scenarios
IfPublic resource, identical for all users
UseUse public, max-age=86400 with ETag. Highly effective for assets.
IfPer-user but rarely changes (e.g., user profile)
UseUse private, must-revalidate with ETag. Store user-specific ETag in session or DB.
IfReal-time data, should never be cached
UseUse no-cache, no-store, must-revalidate and Pragma: no-cache.

Cache Invalidation: The Hardest Problem in Computer Science

‘There are only two hard things in computer science: cache invalidation and naming things.’ — Phil Karlton. Cache invalidation is about ensuring that when the underlying data changes, the cached version is either removed or updated. Without it, users see stale data. Over-invalidation trashes your cache hit ratio and kills performance.

Three common patterns
  • TTL-based: Entry expires after a fixed time. Simple but you either serve stale or recompute early.
  • Write-through: On every write to the database, also update or delete the cache key. Ensures consistency but adds write latency.
  • Publish-subscribe: Another component (e.g., a queue worker) broadcasts invalidation events. All cache layers (APCu, Redis) listen and evict.

In practice, most applications use a combination: a short TTL as a safety net, plus explicit invalidation on writes. Cache poisoning happens when an attacker controls the cache key (e.g., SQL injection in the key). Always sanitise cache keys.

src/Caching/ProductInvalidator.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace TheCodeForge\Caching;

class ProductInvalidator
{
    public function __construct(
        private Redis $redis,
    ) {}

    public function onProductUpdate(int $productId): void
    {
        // Delete multiple cache keys
        $keys = [
            'product:' . $productId,
            'product_list:category:' . $this->getCategory($productId),
            'home_page:featured'
        ];

        foreach ($keys as $key) {
            $this->redis->del($key);
        }

        // Optionally notify other layers via pub/sub
        $this->redis->publish('cache:invalidate', json_encode([
            'type' => 'product',
            'id' => $productId,
            'timestamp' => time()
        ]));
    }

    private function getCategory(int $productId): int { /* implementation */ }
}
Stampede on Invalidation
Invalidating a hot key removes it immediately. Next request triggers an expensive recomputation. If that request is one of many concurrent ones, you get a stampede. Prefer 'lazy recomputation' — keep the old value while asynchronously computing a new one.
Production Insight
TTL-only invalidation is safe but wasteful: you serve stale data longer than necessary.
Write-through is more consistent but adds write latency — measure the impact on high-write endpoints.
Rule: always have a 'fallback stale' option when the cache-miss computation is expensive (e.g., >100ms).
Key Takeaway
Cache invalidation isn't just about removing keys.
It's about balancing staleness, write cost, and stampede risk.
The safest invalidation is TTL + explicit delete on write + serve stale during recompute.

Cache Monitoring and Observability in Production

You can't fix what you don't measure. Cache monitoring is often an afterthought — teams add caching, see a performance boost, then never look again until something breaks. The two numbers that matter: hit ratio and eviction rate.

For APCu, use apcu_cache_info(true) to see hits, misses, and memory usage. For Redis, INFO stats gives keyspace_hits and keyspace_misses. For file cache, track disk usage and file age.

Set up alerts: when hit ratio drops below 90% for more than 5 minutes, something changed — a deployment, a growth spike, or a bug in key naming. Missing keys silently degrade performance. Also monitor evictions; a sudden increase means your cache size is too small.

Real example: a team used Redis with a 256 MB limit. Traffic grew, evictions jumped, hit ratio dropped to 50%. The app slowed to a crawl. They increased maxmemory to 2 GB and added monitoring — problem solved before the next spike.

src/Caching/CacheMonitor.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace TheCodeForge\Caching;

class CacheMonitor
{
    public static function report(): array
    {
        $data = [];
        if (function_exists('apcu_cache_info')) {
            $info = apcu_cache_info(true);
            $data['apcu_hits'] = $info['num_hits'];
            $data['apcu_misses'] = $info['num_misses'];
            $data['apcu_hit_ratio'] = $info['num_hits'] / max($info['num_hits'] + $info['num_misses'], 1);
        }

        // For Redis, parse INFO stats
        // $redis->info();

        return $data;
    }
}
Baseline First
Record hit ratio and eviction rate immediately after deployment. Compare against that baseline. A 10% drop in hit ratio is a fire drill.
Production Insight
Most cache failures are slow and silent: hit ratio decays, memory fills, evictions accelerate.
By the time a user reports slowness, your cache has been degraded for hours.
Rule: monitor hit ratio and evictions in real time — set alerts at 90% hit ratio and any evictions.
Key Takeaway
Cache without monitoring is like driving blindfolded.
Your best caching architecture is useless if you don't know when it fails.
Measure hit ratio, evictions, and memory usage — then react before users feel it.
● Production incidentPOST-MORTEMseverity: high

50s Response Time After Cache Stampede on Black Friday

Symptom
Product catalogue page response time jumped from ~200ms to 50+ seconds. Server CPU hit 100%. Database connection pool exhausted.
Assumption
The 60-second cache TTL was long enough to absorb traffic spikes. A single cache miss would be harmless.
Root cause
When the cache entry for the product list expired, 500 concurrent requests all called the database simultaneously (cache stampede). The database couldn't keep up, timeouts cascaded, and PHP workers blocked waiting for connections.
Fix
Implemented lock-based cache stampede prevention: the first request acquires a mutex lock (Redis SET NX) and refreshes the cache; subsequent requests wait briefly and read the stale entry or the freshly computed one.
Key lesson
  • Always assume cache entries will expire under load. Use locking or probabilistic early recomputation (like 'likely to be stale' at 80% of TTL).
  • A single stale cache entry is vastly better than a stampede. Serve stale + refresh async.
  • Monitor cache hit ratios and stampede events. A drop from 99% to 95% can kill your app.
  • Add capacity planning: if your cache key is accessed >1000 req/s, design for cache miss spikes.
Production debug guideSymptom to action — find the root cause fast5 entries
Symptom · 01
Pages return stale data long after update
Fix
Check TTL on the cache entry (phpinfo/apcu.php/Redis TTL). Verify cache invalidation logic fires correctly. Use logging to confirm the invalidation event occurs.
Symptom · 02
Sporadic high latency on a single endpoint
Fix
Look for cache stampede: enable locking around cache generation. Check if multiple workers compute the same key simultaneously using Redis MONITOR or slow query log.
Symptom · 03
Memory usage grows unboundedly with APCu
Fix
APCu is LRU by default. If memory limit is hit, old entries are evicted. Increase apc.shm_size or use a dedicated cache for hot data. Check apc.php for fragmentation.
Symptom · 04
File cache directory grows huge, disk I/O spikes
Fix
File cache lacks automatic eviction. Implement TTL-based garbage collection or switch to in-memory cache. Measure iowait with iostat.
Symptom · 05
Redis memory full, evictions started
Fix
Check maxmemory policy. Set allkeys-lru for pure cache, or volatile-lru if you have persisted keys. Monitor evicted_keys in INFO stats.
★ Quick Debug Cheat Sheet: PHP Cache IssuesWhen a cache-related problem surfaces, run these checks in order. Real commands for each layer.
Cache appears empty / high miss ratio
Immediate action
Check if cache process is running and accessible
Commands
apcu_cache_info(true) or redis-cli PING
stat -c %Y /path/to/cache/files | tail -5
Fix now
Restart the cache daemon (APCu via PHP-FPM reload, Redis via systemctl restart redis). Verify connectivity from PHP.
Stale data served after invalidation+
Immediate action
Log invalidation calls and verify they hit the correct key
Commands
redis-cli KEYS '*product:*' | head -20
tail -f /var/log/php-fpm/error.log | grep cache.invalidate
Fix now
Remove the cache entry manually: redis-cli DEL product:12345. Temporarily shorten TTL to invalidate faster while debugging.
High memory usage, APCu evictions+
Immediate action
Check APCu memory statistics
Commands
echo apcu_sma_info(true); // in a PHP file
php -r "print_r(apcu_cache_info(true));"
Fix now
Increase apc.shm_size=128M in php.ini. Reduce TTL on non-essential keys. Use apcu_del() for manual cleanups.
Redis memory threshold reached+
Immediate action
Check eviction policy and current usage
Commands
redis-cli INFO stats | grep evicted_keys
redis-cli MEMORY STATS
Fix now
Set maxmemory-policy allkeys-lru. Add more RAM or reduce TTLs. If persisted data, use volatile-lru.
PHP Caching Comparison
Cache TypeLatencyConsistency (multi-server)Best for
APCu~200 nsPer-server, inconsistentSingle server, hot micro-data
Redis~1 msGlobal consistencyMulti-server, shared data
File Cache~10 ms (disk)Depends on filesystemLow-traffic, no-dependency fallback
HTTP Cache0 ms (browser)Per-user (private) or global (public)Public static resources

Key takeaways

1
APCu for single-server microsecond needs; Redis for multi-server consistency
2
File cache is a fallback, not a strategy; HTTP cache offloads the server entirely
3
Cache invalidation patterns matter more than cache storage choice
4
Always protect against stampedes
a locked cache is better than a dead server
5
HTTP caching is a contract
match Cache-Control to your content's sharing needs
6
Monitor eviction rates, TTL expiration, and hit ratios
they tell you when your cache is failing

Common mistakes to avoid

8 patterns
×

Using APCu on multi-server deployments without coordination

Symptom
Users see different data depending on which server handles their request. Cache hit ratio drops because each server has a separate cache.
Fix
Replace APCu with Redis for any data that must be consistent across servers. Keep APCu only for per-server temporary data (e.g., opcode cache).
×

Caching everything with a long TTL and no invalidation

Symptom
Stale data persists long after updates. You only notice when a user complains and you can't verify the fix until TTL expires.
Fix
Add explicit invalidation on write operations. Use TTL as a safety net, not as the primary invalidation mechanism. Set TTL to match the maximum acceptable staleness.
×

Not handling cache stampede when a hot key expires

Symptom
Under load, a single key expiry causes a thundering herd that overwhelms the database and skyrockets response times.
Fix
Implement lock-based or probabilistic recomputation. First request acquires a mutex and recomputes; others wait or use a stale value.
×

Forgetting to clearstatcache() when polling file cache

Symptom
File cache reads return stale data even after file is updated because PHP caches file metadata.
Fix
Call clearstatcache() before checking file existence or modification time in a polling loop.
×

Using HTTP Cache-Control: public for user-specific content

Symptom
One user's private data (e.g., cart contents) is served to another user by a shared proxy.
Fix
Use private directive for per-user responses. Never use public unless the response is identical for all users.
×

Not checking if APCu extension is loaded before calling apcu_fetch()

Symptom
PHP throws a fatal error: 'Call to undefined function apcu_fetch()' when the extension is missing, bringing down the entire page.
Fix
Wrap all APCu calls in function_exists('apcu_fetch') checks or use a dependency injection container that provides a null adapter.
×

Using Redis without persistence and losing cache on restart

Symptom
After a server restart, Redis memory is empty. All keys are cold, causing a stampede on the first request flood.
Fix
Enable RDB or AOF persistence if cache data must survive restarts. Alternatively, use Redis as a pure cache and warm it gradually after restart with lazy loading.
×

Not setting an eviction policy in Redis

Symptom
When memory limit is hit, Redis returns OOM errors instead of evicting keys. Write operations fail.
Fix
Set maxmemory-policy to allkeys-lru (for pure cache) or volatile-lru (if some keys are persisted). Always test eviction behaviour under load.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What are the differences between APCu and Redis when used as PHP caches?...
Q02SENIOR
Explain the cache stampede problem and how to prevent it in PHP applicat...
Q03SENIOR
How do you implement cache invalidation for a product catalogue that sup...
Q04SENIOR
What factors affect the choice between file-based caching and in-memory ...
Q05JUNIOR
Describe how HTTP caching headers work in PHP and give an example of whe...
Q06SENIOR
How do you handle cache warming after a deployment?
Q07SENIOR
You notice cache hit ratio dropped from 98% to 70% after a deployment. W...
Q08SENIOR
Explain how you would implement a locking mechanism for cache stampede p...
Q01 of 08SENIOR

What are the differences between APCu and Redis when used as PHP caches? When would you choose one over the other?

ANSWER
APCu is an in-process cache stored in shared memory on a single server. Reads are sub-microsecond because no network is involved. Redis is a separate network service that all servers can access, providing consistency across a cluster. You choose APCu for single-server, latency-critical, small data (<1KB) that doesn't need cross-server coordination. You choose Redis when you have multiple web servers, need atomic operations (counters, locks), or want persistent storage options. APCu's downside: each server has its own copy and eviction is LRU. Redis's downside: network overhead (~0.2–1ms per round trip) and a single point of failure without replication.
FAQ · 7 QUESTIONS

Frequently Asked Questions

01
What is Caching in PHP in simple terms?
02
Should I use APCu or Redis for my PHP application?
03
How do I clear a file cache automatically?
04
What is the most common cause of cache stampede in PHP?
05
Can I use session data with file caching?
06
How can I monitor cache performance in production?
07
What is the best eviction policy for Redis as a cache?
🔥

That's Advanced PHP. Mark it forged?

5 min read · try the examples if you haven't

Previous
WebSockets in PHP
9 / 13 · Advanced PHP
Next
PHP and Redis Integration