Caching Strategies
Also Known As
TL;DR
Explanation
The four core caching strategies: Cache-aside (lazy loading) — the application checks the cache first; on a miss, loads from the database, stores in cache, returns the result. The application controls all cache interactions. Write-through — writes go to both the cache and the database synchronously; reads always hit cache. Strong consistency, higher write latency. Write-behind (write-back) — writes go to cache immediately and to the database asynchronously; fast writes, risk of data loss if cache fails before persisting. Read-through — the cache itself loads from the database on a miss; the application only ever talks to the cache layer. In PHP, cache-aside with Redis or Memcached is the most common pattern. Cache invalidation strategies: TTL-based (expire after N seconds), event-based (invalidate on write), and pattern-based (purge all keys matching a prefix). The hardest problem in caching is stale data — knowing when cached data no longer reflects the source of truth.
Common Misconception
Why It Matters
Common Mistakes
- Not namespacing cache keys by user or tenant — global keys like 'user_profile' serve the same cached data to all users.
- Caching inside database transactions — if the transaction rolls back, the cached data is inconsistent with the database.
- Using a single global TTL for all cache entries — frequently updated data needs short TTLs; rarely changing data can cache for hours.
- Cache stampede on TTL expiry — many concurrent requests all miss and hit the database simultaneously; use probabilistic early expiry or a mutex lock on cache population.
Avoid When
- Cache-aside on data that is written frequently — the cache is invalidated constantly and hit rate stays near zero.
- Write-through on data that is rarely read — you pay the write overhead for cache entries that are never served.
- Caching user-specific data in a shared cache without namespacing keys — data bleeds between users.
- Caching without a TTL — stale entries accumulate and are never evicted.
When To Use
- Cache-aside for read-heavy data that changes infrequently and tolerable staleness exists.
- Write-through for data that is written and then immediately read, where cache miss after write is unacceptable.
- Read-through when the application should never query the DB directly — the cache layer owns all reads.
- Write-behind (write-back) for high-write workloads where batching DB writes improves throughput.
Code Examples
// No caching — hits database on every request
class ProductController {
public function show(int $id): array {
// Expensive query on every page load
return $this->db->query(
'SELECT p.*, c.name as cat, AVG(r.score) as rating
FROM products p
JOIN categories c ON p.cat_id = c.id
LEFT JOIN reviews r ON r.product_id = p.id
WHERE p.id = ?
GROUP BY p.id', [$id]
)->fetch();
}
}
// Cache-aside with Redis — database hit only on miss
class ProductController {
public function show(int $id): array {
$key = 'product:' . $id;
$cached = $this->redis->get($key);
if ($cached) return json_decode($cached, true);
$product = $this->db->query('...expensive query...', [$id])->fetch();
$this->redis->setex($key, 3600, json_encode($product)); // 1hr TTL
return $product;
}
public function update(int $id, array $data): void {
$this->db->query('UPDATE products SET ...', $data);
$this->redis->del('product:' . $id); // invalidate on write
}
}