plugins/backend-toolkit/skills/caching-strategy/SKILL.md
Design a cache layer — cache-aside read/write/invalidate, TTL + jitter, stampede prevention (single-flight / probabilistic refresh), and explicit invalidation. Use when read latency is high, the DB is read-bound, or a hot key causes thundering-herd load. Not for fixing the slow query at its source (use query-optimization first) or HTTP/browser caching (a frontend concern).
npx skillsauth add jaykim88/claude-ai-engineering caching-strategyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Add caching deliberately — with a clear read/write/invalidate flow, stampede protection, and an invalidation plan — so it reduces load without serving stale or inconsistent data or collapsing under a hot-key herd.
Universal — cache-aside flow, TTL+jitter, stampede prevention, and invalidation strategy are caching principles independent of the cache store; Redis is the default implementation.
Optimize the query FIRST, cache second
query-optimization before adding a cache layerUse cache-aside (lazy loading) as the default pattern
Set TTL with jitter
3b. Bound the cache: memory budget + eviction policy
maxmemory and choose an eviction policy deliberately (allkeys-lru for general read-through caches; volatile-lru if you mix persistent state into the same instance — but ideally don't)Prevent cache stampede on hot keys
Plan invalidation explicitly — the hard part
user:{id}:profile) so invalidation is targeted5b. Cache is an optimization, not a source of truth
resilience-patterns circuit breaker around the cache client so a cache outage doesn't cascade| ❌ Anti-pattern | ✅ Correct |
|---|---|
| Caching before optimizing the query | Optimize first, cache second |
| No TTL (infinite cache, no invalidation plan) | TTL + explicit invalidation strategy |
| All keys same TTL | TTL + jitter to desynchronize expiry |
| Update-cache-on-write (race-prone) | Delete-cache-on-write (cache-aside) |
| No stampede protection on hot keys | Single-flight lock or probabilistic refresh |
| No maxmemory / eviction policy (OOM under load) | maxmemory + allkeys-lru (or chosen policy) |
| Unbounded distinct cache keys (memory leak) | Cap or hash high-cardinality keys |
| 404s hitting the DB on every retry (negative-cache miss) | Cache "not found" sentinel with a short TTL |
| Auth/session state stored only in cache | Cache is optimization, not source of truth |
| Tier | Examples | Action SLA | |---|---|---| | Critical | Hot key with no stampede protection causing DB overload; cache serving stale auth/permission data | Fix immediately | | Major | Infinite-TTL cache with no invalidation plan; update-on-write races | Fix this sprint | | Minor | Uniform TTLs (no jitter); cache key naming inconsistency | Schedule within 2 sprints |
docs/cache-invalidation.md — write → invalidated keysperf(cache): add cache-aside for <query> / fix(cache): single-flight lock on <hot key>GET/SETEX/DELSET key val NX PX ttl lock, or ioredis + Lua EVAL for atomicity(value, computed_at, ttl) and recompute when now - computed_at > ttl * random_thresholdredis-py + aiocache; same cache-aside + Lua patternsgo-redis + singleflight package (stdlib-adjacent) for stampedequery-optimization — cache only after the query itself is optimizedresilience-patterns — cache as a fallback when a dependency is downtransaction-management — invalidate cache after the write commits, not beforedevelopment
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
testing
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).