Caching Strategies
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints.tools field is not specified. Common caching mistakes — missing key namespacing, caching inside transactions, cache stampede, missing TTL — produce silent bugs visible only in runtime conditions (stale data served to wrong users, post-rollback inconsistency). No standard linter or SAST tool reliably catches these patterns; they surface through code review or production monitoring.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix shows a single cache-aside call is easy to add, but the common_mistakes reveal that fixing systemic issues (key namespacing across all call sites, removing cache calls from all transactions, applying per-entity TTL policies, adding stampede protection) requires changes across multiple service and repository classes rather than a single one-line swap.
Closest to 'persistent productivity tax' (b5). Caching strategy applies to both web and cli contexts and touches any data-access layer. Once a caching pattern is in place — especially if key naming conventions, TTL policies, or cache population logic are inconsistent — every future feature that reads or writes cached data must be aware of and conform to those choices. It slows many work streams but is not fully architectural in the b7/b9 sense.
Closest to 'serious trap' (t7). The misconception field lists multiple serious traps: 'caching always improves performance' contradicts real-world outcomes; caching without user/tenant namespacing silently leaks data between users; caching inside transactions produces inconsistent state on rollback; no TTL means perpetually stale data. These contradict intuitive expectations and parallel patterns from simpler use cases elsewhere, making t7 appropriate — they are documented gotchas that competent developers frequently get wrong on first encounter.
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.
Watch Out
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
}
}