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.
✦ Definition~90s read
What is Caching in PHP?
PHP Cache Stampede is a concurrency failure pattern that occurs when a cached data item expires or is invalidated under high traffic, causing multiple concurrent PHP requests to simultaneously attempt to regenerate the same expensive computation or database query. This creates a sudden, massive load spike on backend resources (e.g., database, API, file system) as each request bypasses the cache and recomputes the value independently, often leading to degraded performance, resource exhaustion, or cascading failures.
★
Imagine your favourite pizza shop.
The stampede exists because standard caching strategies (like TTL-based expiration or lazy population) lack coordination between concurrent processes. When a cache key expires, every request that misses the cache races to rebuild it. Without a mechanism to serialize or deduplicate the regeneration work, the backend is hit by a burst of identical, redundant operations.
This is particularly acute in PHP applications using shared-nothing architectures (e.g., multiple web servers with local caches) or simple key-value stores like Memcached or Redis without built-in stampede protection.
This pattern fits squarely in the domain of distributed caching and high-traffic PHP application architecture. It is a critical concern for any system where cache regeneration is expensive (e.g., complex SQL aggregations, external API calls, heavy computations) and traffic is bursty.
Mitigation techniques include: lock-based regeneration (mutex around cache rebuild), probabilistic early expiration (randomly refresh before TTL ends), or using cache stampede prevention features in tools like Redis (e.g., SET NX with TTL).
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.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
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.
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.
thecodeforge.io
Caching Php
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%.
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.
IfNeed atomic operations or data structures beyond key-value
→
UseRedis — APCu only supports simple key-value with TTL.
thecodeforge.io
Caching Php
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.
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.
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.
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.
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.
OpCache: The Free Cache You're Already Running Wrong
Most devs treat OpCache as a set-it-and-forget-it checkbox. That's a production fire waiting to happen. OpCache stores precompiled PHP bytecode in shared memory, skipping the parse/compile phase on every request. PHP 8.x makes this even faster with JIT compilation, but only if you configure it for your actual workload. The default settings are optimized for shared hosting, not for your app. The whocares: validation timestamps are off by default. That means if you deploy new code via FTP (don't) or a symlink swap, OpCache serves stale bytecode until you restart PHP-FPM or call opcache_reset(). Set opcache.validate_timestamps=1 in production, or use a deployment script that invalidates the cache. OpCache is always the first caching layer because it's free, already installed, and reduces response time by 10-30ms.
Restart PHP-FPM to apply. Run phpinfo() to verify.
⚠ Production Trap:
Setting validate_timestamps=0 in CI/deployment pipelines is common. Add a post-deploy script that touches a timestamp file or calls opcache_invalidate() on changed files. Your deployment is not complete until the cache is warm.
🎯 Key Takeaway
OpCache is always the first cache layer. Always validate timestamps in production or script your invalidation. Never trust defaults.
Cache Stampede Protection: Don't Let a Miss Kill Your DB
A cache stampede is when 100 requests all miss at once, and they all hit the database. It's not a theory — it's the reason your pager went off at 3 AM. The fix is simple: use a mutex or probabilistic early expiration. PHP 8.x gives you fiber-based concurrency, but for cache stampedes, you want a distributed lock via Redis or APCu. The pattern: when a cache miss happens, try to acquire a lock. Only the first request rebuilds the cache; the rest wait or serve stale data. Redis SET NX + EXPIRE is the standard. APCu can also serve as a single-server lock. Another approach: set the TTL to 2x the expected rebuild time, and randomly expire earlier. This spreads out the refresh load. Don't use file-based mutexes on NFS — they're not atomic. Test your stampede handling with a chaos monkey script that flushes your cache in production during peak load.
StampedeGuard.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
// io.thecodeforgefunctiongetExpensiveData(Redis $redis, string $key, callable $loader, int $ttl = 300): mixed {
$cached = $redis->get($key);
if ($cached !== false) {
returnjson_decode($cached, true);
}
// Attempt to acquire lock for 10 seconds
$lockKey = $key . ':lock';
$lock = $redis->set($lockKey, 1, ['nx', 'ex' => 10]);
if ($lock) {
try {
$data = $loader();
$redis->setex($key, $ttl, json_encode($data));
return $data;
} finally {
$redis->del($lockKey);
}
}
// Wait and retryusleep(100_000); // 100ms
$cached = $redis->get($key);
if ($cached !== false) {
returnjson_decode($cached, true);
}
// Fallback: let this thread be the loader if lock expiredreturngetExpensiveData($redis, $key, $loader, $ttl);
}
Output
In production logs: avoid recursive calls; use a max retry count to prevent infinite loops.
⚠ Production Trap:
Don't use a single Redis instance for both cache and locks — a network partition will hang all waiters. Pair lock Redis with a fallback in APCu for local-first stampede protection.
🎯 Key Takeaway
Always guard against cache stampedes with a distributed mutex or probabilistic early expiration. Your database will thank you.
● 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
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 Type
Latency
Consistency (multi-server)
Best for
APCu
~200 ns
Per-server, inconsistent
Single server, hot micro-data
Redis
~1 ms
Global consistency
Multi-server, shared data
File Cache
~10 ms (disk)
Depends on filesystem
Low-traffic, no-dependency fallback
HTTP Cache
0 ms (browser)
Per-user (private) or global (public)
Public static resources
⚙ Quick Reference
9 commands from this guide
File
Command / Code
Purpose
srcCachingApplicationCache.php
namespace TheCodeForge\Caching;
What Is Caching in PHP?
srcCachingApcuProductLookup.php
namespace TheCodeForge\Caching;
APCu
srcCachingRedisWeatherService.php
namespace TheCodeForge\Caching;
Redis
srcCachingFileCache.php
namespace TheCodeForge\Caching;
File Cache
srcCachingHttpCacheHandler.php
namespace TheCodeForge\Caching;
HTTP Cache
srcCachingProductInvalidator.php
namespace TheCodeForge\Caching;
Cache Invalidation
srcCachingCacheMonitor.php
namespace TheCodeForge\Caching;
Cache Monitoring and Observability in Production
php.ini
[opcache]
OpCache
StampedeGuard.php
function getExpensiveData(Redis $redis, string $key, callable $loader, int $ttl ...
Cache Stampede Protection
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.
Q02 of 08SENIOR
Explain the cache stampede problem and how to prevent it in PHP applications.
ANSWER
A cache stampede occurs when a cached key expires and multiple concurrent requests all attempt to recompute the value simultaneously. This overloads the backend (database, API). Prevention strategies: 1) Use mutex locking – the first request acquires a lock (e.g., Redis SET NX with TTL), recomputes, and others either wait briefly or use a stale value. 2) Serve stale and recompute asynchronously – return the expired cache entry while a background job refreshes it. 3) Probabilistic early expiration – use ‘likely to be stale’ by recomputing before the TTL expires, based on a random factor (e.g., recompute if time() - cache_creation_time > 0.8 * TTL). In PHP, the Redis SET NX approach is common: $lock = $redis->set($lockKey, 1, ['nx', 'ex' => 5]);.
Q03 of 08SENIOR
How do you implement cache invalidation for a product catalogue that supports updates and deletes?
ANSWER
Use a combination of TTL and explicit invalidation. Define a naming scheme for cache keys, e.g., product:{id}, product_list:category:{catId}, product_list:featured. On every update or delete, call an invalidation method that deletes all relevant keys. Optionally, use a version tag: product:v4:{id} and increment the version number when a bulk update occurs. This avoids deleting many keys. For Redis, you can also use pub/sub – the update handler publishes an invalidation event, and workers or other servers listen and evict. A safety TTL of 1 hour ensures that forgotten invalidation bugs don't live forever.
Q04 of 08SENIOR
What factors affect the choice between file-based caching and in-memory caching in PHP?
ANSWER
File caching is slower (disk I/O), has no built-in eviction, and is prone to corruption under high concurrency without locking. It works for low-traffic apps (<10 req/s) and when you cannot install extensions. In-memory caching (APCu, Redis) is ~1000x faster and handles concurrency natively. However, file cache persists across server restarts and doesn't use RAM. Choose file cache for very small deployments or as a fallback. In-memory is standard for any production app with >100 users. Also consider that file cache can be shared via NFS but introduces network latency and locking issues.
Q05 of 08JUNIOR
Describe how HTTP caching headers work in PHP and give an example of when to use ETags vs Cache-Control max-age.
ANSWER
Cache-Control: max-age=3600 tells the browser and proxies to reuse the response for 1 hour without asking the server. ETag provides a unique hash – the browser sends If-None-Match on subsequent requests; if the ETag matches, the server returns 304 Not Modified with no body. Use max-age for static assets that change infrequently (images, CSS). Use ETag for dynamic content that hasn't changed – it avoids a full response body even when the cached max-age has expired. In PHP, set headers with header() and respond to $_SERVER['HTTP_IF_NONE_MATCH'].
Q06 of 08SENIOR
How do you handle cache warming after a deployment?
ANSWER
Cache warming pre-populates cache with the most critical data immediately after deployment, avoiding a cold-start stampede. In PHP, use a post-deploy script that calls the expensive functions (e.g., listing top 100 products) and stores results in Redis or APCu. Be careful not to warm too many keys at once – stagger the warm-up over several minutes or use a queue to avoid overwhelming the database. Also, ensure the cache keys match the ones your application will request. Monitor cache hit ratio after warming; a drop indicates missed keys.
Q07 of 08SENIOR
You notice cache hit ratio dropped from 98% to 70% after a deployment. What steps do you take?
ANSWER
First, verify the deployment didn't change the cache key naming scheme or namespace. Check if new code is using different keys for the same data. Use Redis MONITOR or APCu cache info to see which keys are being fetched and missed. Look for missing invalidation calls — a common cause is forgetting to clear cache when a write operation runs. Also check if the new code introduced additional cache key dependencies that aren't being populated. After identifying the cause, either adjust key generation or warm the missing keys.
Q08 of 08SENIOR
Explain how you would implement a locking mechanism for cache stampede prevention using Redis.
ANSWER
Use Redis SET with NX (not exists) and EX (expiry) to acquire a lock. The first request to compute the cache key tries to set a lock key, e.g., $redis->set('lock:product:123', 1, ['nx', 'ex' => 10]). If successful, it recomputes the cache and then deletes the lock. Other requests either wait a short time (say 200ms) and retry, or serve a stale cache entry if one exists. Use a unique lock identifier to avoid deleting locks held by another process. For high-reliability, implement a 'lock watchdog' that extends the lock TTL if computation is slow. Penalty: lock overhead adds ~1-2ms to the cache miss path.
01
What are the differences between APCu and Redis when used as PHP caches? When would you choose one over the other?
SENIOR
02
Explain the cache stampede problem and how to prevent it in PHP applications.
SENIOR
03
How do you implement cache invalidation for a product catalogue that supports updates and deletes?
SENIOR
04
What factors affect the choice between file-based caching and in-memory caching in PHP?
SENIOR
05
Describe how HTTP caching headers work in PHP and give an example of when to use ETags vs Cache-Control max-age.
JUNIOR
06
How do you handle cache warming after a deployment?
SENIOR
07
You notice cache hit ratio dropped from 98% to 70% after a deployment. What steps do you take?
SENIOR
08
Explain how you would implement a locking mechanism for cache stampede prevention using Redis.
SENIOR
FAQ · 7 QUESTIONS
Frequently Asked Questions
01
What is Caching in PHP in simple terms?
Think of it as a warm shelf for expensive computations. You store the result of a heavy operation once and serve it instantly to every subsequent request. No repeated database queries or API calls.
Was this helpful?
02
Should I use APCu or Redis for my PHP application?
If you have a single web server and need the absolute fastest reads with no network overhead, use APCu. If you have multiple servers, need to share cache across them, or require atomic operations (counters, locks), use Redis. For most production apps with more than one server, Redis is the right choice.
Was this helpful?
03
How do I clear a file cache automatically?
File cache doesn't evict automatically. You need to implement TTL checks as shown in the FileCache example. Alternatively, run a cron job periodically to delete files older than a threshold. Monitor disk usage to avoid filling the disk.
Was this helpful?
04
What is the most common cause of cache stampede in PHP?
A single hot cache key expires under high traffic. For example, a frequently accessed product list with a TTL of 60 seconds expires and 500 concurrent requests all hit the database simultaneously. The fix is to implement a lock around the cache generation so only one request recomputes and the rest wait or use the stale copy.
Was this helpful?
05
Can I use session data with file caching?
PHP sessions are stored in files by default, but that's not the same as file caching for computed data. For session storage, consider using Redis (via php-session handler) for better performance and scalability. File-based sessions are fine for single-server small apps but fail under multi-server load (unless you use a shared filesystem with locking).
Was this helpful?
06
How can I monitor cache performance in production?
Track cache hit ratio via APCu or Redis stats. For APCu, use apcu_cache_info() to see hits/misses. For Redis, use INFO stats for keyspace_hits and keyspace_misses. Alert when hit ratio drops below 95%. Also monitor evictions – a sudden increase means your cache memory is too small.
Was this helpful?
07
What is the best eviction policy for Redis as a cache?
For a pure cache (all keys can be evicted), use allkeys-lru. This evicts the least recently used keys when memory is full. If you have some keys that must persist (e.g., rate limit counters), use volatile-lru to evict only keys with an expiry set. Never use noeviction in a cache context — it will cause write errors.