Files
claudekit/skills/caching/references/caching-decision-tree.md
T
2026-04-19 14:10:38 +07:00

6.5 KiB

Caching Decision Tree

Primary Decision Tree

What are you caching?
│
├─ PURE FUNCTION RESULT (same input = same output)
│  │
│  ├─ In React component?
│  │  └─ useMemo(() => compute(data), [data])
│  │
│  ├─ Expensive computation called repeatedly?
│  │  └─ Memoize the function
│  │     Python: @functools.lru_cache or @functools.cache
│  │     JS: hand-rolled Map cache or lodash.memoize
│  │
│  └─ Shared across requests/processes?
│     └─ Use external cache (Redis) -- see below
│
├─ HTTP RESPONSE (browser or CDN caching)
│  │
│  ├─ Is it public (same for all users)?
│  │  │
│  │  ├─ Static asset (JS, CSS, images)?
│  │  │  └─ Cache-Control: public, max-age=31536000, immutable
│  │  │     (Use content hash in filename for busting)
│  │  │
│  │  ├─ API response that changes occasionally?
│  │  │  └─ Cache-Control: public, max-age=60, stale-while-revalidate=300
│  │  │     + ETag or Last-Modified for conditional requests
│  │  │
│  │  └─ HTML page?
│  │     └─ Cache-Control: public, max-age=0, must-revalidate
│  │        + ETag (let CDN/browser validate freshness)
│  │
│  └─ Is it private (user-specific)?
│     └─ Cache-Control: private, max-age=60
│        (Never cache auth tokens or sensitive data at CDN)
│
├─ DATABASE QUERY RESULT (shared across requests)
│  │
│  ├─ Read-heavy, rarely changes?
│  │  └─ Redis/Memcached with TTL
│  │     Pattern: Cache-aside (read-through)
│  │
│  ├─ Must always be fresh?
│  │  └─ Don't cache. Optimize the query instead.
│  │     (Add indexes, denormalize, materialized view)
│  │
│  └─ Needs real-time invalidation?
│     └─ Write-through cache or event-driven invalidation
│        (Update cache when DB changes)
│
├─ EXTERNAL API RESPONSE
│  │
│  ├─ API has rate limits?
│  │  └─ Cache aggressively. Respect Cache-Control from API.
│  │     Fallback: cache with reasonable TTL (5-60 min)
│  │
│  ├─ API is slow (>500ms)?
│  │  └─ Cache + stale-while-revalidate pattern
│  │     Serve stale, refresh in background
│  │
│  └─ API data is critical and must be fresh?
│     └─ Short TTL (10-30s) + circuit breaker on failure
│
└─ EDGE/CDN CACHING
   │
   ├─ Global audience, same content?
   │  └─ CDN with long TTL + purge on deploy
   │     (Cloudflare, CloudFront, Vercel Edge)
   │
   ├─ Personalized at edge?
   │  └─ Edge compute (Cloudflare Workers, Vercel Edge Functions)
   │     Cache shared parts, inject personalization
   │
   └─ A/B testing at edge?
      └─ Vary by cookie or header
         Vary: Cookie (careful: reduces cache hit rate)

Cache-Aside Pattern (Most Common)

Read:
  1. Check cache for key
  2. HIT  --> return cached value
  3. MISS --> query DB, store in cache with TTL, return value

Write:
  1. Update DB
  2. Delete cache key (don't update -- avoids race conditions)
  3. Next read will repopulate cache
# Python + Redis
import redis, json

r = redis.Redis()
TTL = 300  # 5 minutes

def get_user(user_id: str) -> dict:
    key = f"user:{user_id}"
    cached = r.get(key)
    if cached:
        return json.loads(cached)
    user = db.query("SELECT * FROM users WHERE id = %s", user_id)
    r.setex(key, TTL, json.dumps(user))
    return user

def update_user(user_id: str, data: dict):
    db.execute("UPDATE users SET ... WHERE id = %s", user_id)
    r.delete(f"user:{user_id}")  # Invalidate, don't update

TTL Strategy Guide

Data Type TTL Rationale
User session 15-60 min Balance security and UX
User profile 5-15 min Changes infrequently
Product catalog 1-5 min Needs reasonable freshness
Search results 30s-2 min Changes frequently
Static config 1-24 hours Rarely changes
Feature flags 30s-1 min Needs fast propagation
API rate limit counters Match the rate limit window Exact timing matters
Dashboard aggregations 1-5 min Expensive to compute

TTL Anti-Patterns

Anti-Pattern Problem Fix
No TTL (cache forever) Stale data, memory leak Always set a TTL
TTL too short (<1s) Cache provides no benefit Remove cache or increase TTL
Same TTL for everything Over/under-caching Tune per data type
Stampede on expiry All caches expire at once, DB overload Jitter: TTL + random(0, 60s)

Cache Invalidation Strategies

Strategy How Best For
TTL expiry Automatic, time-based Most cases
Explicit delete Delete key on write Strong consistency needs
Write-through Update cache on every write Read-heavy, write-infrequent
Event-driven Invalidate on DB change event Microservices
Version key Append version to cache key Bulk invalidation
Tag-based Group keys by tag, purge by tag CDN, grouped content

Cache Headers Quick Reference

Header Example Purpose
Cache-Control max-age=3600 Primary caching directive
ETag "abc123" Content fingerprint for conditional requests
Last-Modified Wed, 29 Jan 2025 12:00:00 GMT Timestamp for conditional requests
Vary Accept-Encoding, Authorization Cache varies by these headers
CDN-Cache-Control max-age=86400 CDN-specific (Cloudflare, etc.)

Common Cache-Control Patterns

# Immutable static asset (hashed filename)
Cache-Control: public, max-age=31536000, immutable

# API data with background refresh
Cache-Control: public, max-age=60, stale-while-revalidate=300

# Private user data
Cache-Control: private, no-cache
# (no-cache = must revalidate, NOT "don't cache")

# Never cache
Cache-Control: no-store

# HTML pages (revalidate every time)
Cache-Control: public, max-age=0, must-revalidate
ETag: "content-hash-here"

When NOT to Cache

Scenario Why
Data changes on every request Cache hit rate ~0%
Data must be real-time consistent Stale data is unacceptable
Write-heavy workload Constant invalidation negates benefit
Data is cheap to compute/fetch Cache overhead exceeds savings
Sensitive data (PII, financial) Risk of serving wrong user's data
Early in development Premature optimization; adds complexity