Caching Patterns
A well-placed cache is the cheapest way to buy speed. A misplaced cache is the most expensive way to buy bugs.
Cache Strategies
| Strategy |
How It Works |
When to Use |
| Cache-Aside (Lazy) |
App checks cache → miss → reads DB → writes to cache |
Default choice — general purpose |
| Read-Through |
Cache fetches from DB on miss automatically |
ORM-integrated caching, CDN origin fetch |
| Write-Through |
Writes go to cache AND DB synchronously |
Read-heavy with strong consistency |
| Write-Behind |
Writes go to cache, async flush to DB |
High write throughput, eventual consistency OK |
| Refresh-Ahead |
Cache proactively refreshes before expiry |
Predictable access patterns, low-latency critical |
Cache-Aside Flow:
App ──► Cache ──► HIT? ──► Return data
│
▼ MISS
Read DB ──► Store in Cache ──► Return data
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install caching
Cache Invalidation
| Method |
Consistency |
When to Use |
| TTL-based |
Eventual (up to TTL) |
Simple data, acceptable staleness |
| Event-based |
Strong (near real-time) |
Inventory, profile updates |
| Version-based |
Strong |
Static assets, API responses, config |
| Tag-based |
Strong |
CMS content, category-based purging |
TTL Guidelines
| Data Type |
TTL |
Rationale |
| Static assets (CSS/JS/images) |
1 year + cache-busting hash |
Immutable by filename |
| API config / feature flags |
30–60 seconds |
Fast propagation needed |
| User profile data |
5–15 minutes |
Tolerable staleness |
| Product catalog |
1–5 minutes |
Balance freshness vs load |
| Session data |
Match session timeout |
Security requirement |
HTTP Caching
Cache-Control Directives
| Directive |
Meaning |
max-age=N |
Cache for N seconds |
s-maxage=N |
CDN/shared cache max age (overrides max-age) |
no-cache |
Must revalidate before using cached copy |
no-store |
Never cache anywhere |
must-revalidate |
Once stale, must revalidate |
private |
Only browser can cache, not CDN |
public |
Any cache can store |
immutable |
Content will never change (within max-age) |
stale-while-revalidate=N |
Serve stale for N seconds while fetching fresh |
Common Recipes
# Immutable static assets (hashed filenames)
Cache-Control: public, max-age=31536000, immutable
# API response, CDN-cached, background refresh
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
# Personalized data, browser-only
Cache-Control: private, max-age=0, must-revalidate
ETag: "abc123"
# Never cache (auth tokens, sensitive data)
Cache-Control: no-store
Conditional Requests
| Mechanism |
Request Header |
Response Header |
How It Works |
| ETag |
If-None-Match: "abc" |
ETag: "abc" |
Hash-based — 304 if match |
| Last-Modified |
If-Modified-Since: <date> |
Last-Modified: <date> |
Date-based — 304 if unchanged |
Prefer ETag over Last-Modified — ETags detect content changes regardless of timestamp granularity.
Application Caching
| Solution |
Speed |
Shared Across Processes |
When to Use |
| In-memory LRU |
Fastest |
No |
Single-process, bounded memory, hot data |
| Redis |
Sub-ms (network) |
Yes |
Production default — TTL, pub/sub, persistence |
| Memcached |
Sub-ms (network) |
Yes |
Simple key-value at extreme scale |
| SQLite |
Fast (disk) |
No |
Embedded apps, edge caching |
Redis vs Memcached
| Feature |
Redis |
Memcached |
| Data structures |
Strings, hashes, lists, sets, sorted sets |
Strings only |
| Persistence |
AOF, RDB snapshots |
None |
| Pub/Sub |
Yes |
No |
| Max value size |
512 MB |
1 MB |
| Verdict |
Default choice |
Pure cache at extreme scale |
Distributed Caching
| Concern |
Solution |
| Partitioning |
Consistent hashing — minimal reshuffling on node changes |
| Replication |
Primary-replica — writes to primary, reads from replicas |
| Failover |
Redis Sentinel or Cluster auto-failover |
Rule of thumb: 3 primaries + 3 replicas minimum for production Redis Cluster.
Cache Eviction Policies
| Policy |
How It Works |
When to Use |
| LRU |
Evicts least recently accessed |
Default — general purpose |
| LFU |
Evicts least frequently accessed |
Skewed popularity distributions |
| FIFO |
Evicts oldest entry |
Simple, time-ordered data |
| TTL |
Evicts after fixed duration |
Data with known freshness window |
Redis default is noeviction. Set maxmemory-policy to allkeys-lru or volatile-lru for production.
Caching Layers
Browser Cache → CDN → Load Balancer → App Cache → DB Cache → Database
| Layer |
What to Cache |
Invalidation |
| Browser |
Static assets, API responses |
Versioned URLs, Cache-Control |
| CDN |
Static files, public API responses |
Purge API, surrogate keys |
| Application |
Computed results, DB queries, external API |
Event-driven, TTL |
| Database |
Query plans, buffer pool, materialized views |
ANALYZE, manual refresh |
Cache Stampede Prevention
When a hot key expires, hundreds of requests simultaneously hit the database.
| Technique |
How It Works |
| Mutex / Lock |
First request locks, fetches, populates; others wait |
| Probabilistic early expiration |
Random chance of refreshing before TTL |
| Request coalescing |
Deduplicate in-flight requests for same key |
| Stale-while-revalidate |
Serve stale, refresh asynchronously |
Cache Warming
| Strategy |
When to Use |
| On-deploy warm-up |
Predictable key set, latency-sensitive |
| Background job |
Reports, dashboards, catalog data |
| Shadow traffic |
Cache migration, new infrastructure |
| Priority-based |
Limited warm-up time budget |
Cold start impact: A full cache flush can increase DB load 10–100x. Always warm gradually or use stale-while-revalidate.
Monitoring
| Metric |
Healthy Range |
Action if Unhealthy |
| Hit rate |
> 90% |
Low → cache too small, wrong TTL, bad key design |
| Eviction rate |
Near 0 steady state |
High → increase memory or tune policy |
| Latency (p99) |
< 1ms (Redis) |
High → network issue, large values, hot key |
| Memory usage |
< 80% of max |
Approaching max → scale up or tune eviction |
NEVER Do
- NEVER cache without a TTL or invalidation plan — data rots; every entry needs an expiry path
- NEVER treat cache as durable storage — caches evict, crash, and restart; always fall back to source of truth
- NEVER cache sensitive data (tokens, PII) without encryption — cache breaches expose everything in plaintext
- NEVER ignore cache stampede on hot keys — one expired popular key can take down your database
- NEVER use unbounded in-memory caches in production — memory grows until OOM-killed
- NEVER cache mutable data with
immutable Cache-Control — browsers will never re-fetch
- NEVER skip monitoring hit/miss rates — you won't know if your cache is helping or hurting
1---2name: caching-23description: Caching strategies, invalidation, eviction policies, HTTP caching, distributed caching, and anti-patterns. Use when designing cache layers, choosing eviction policies, debugging stale data, or optimizing read-heavy workloads.4---5
6# Caching Patterns
7
8> A well-placed cache is the cheapest way to buy speed. A misplaced cache is the most expensive way to buy bugs.
9
10## Cache Strategies
11
12| Strategy | How It Works | When to Use |
13|----------|-------------|-------------|
14| **Cache-Aside (Lazy)** | App checks cache → miss → reads DB → writes to cache | **Default choice** — general purpose |
15| **Read-Through** | Cache fetches from DB on miss automatically | ORM-integrated caching, CDN origin fetch |
16| **Write-Through** | Writes go to cache AND DB synchronously | Read-heavy with strong consistency |
17| **Write-Behind** | Writes go to cache, async flush to DB | High write throughput, eventual consistency OK |
18| **Refresh-Ahead** | Cache proactively refreshes before expiry | Predictable access patterns, low-latency critical |
19
20```
21Cache-Aside Flow:
22
23 App ──► Cache ──► HIT? ──► Return data
24 │
25 ▼ MISS
26 Read DB ──► Store in Cache ──► Return data
27```
28
29
30## Installation
31
32### OpenClaw / Moltbot / Clawbot
33
34```bash
35npx clawhub@latest install caching
36```
37
38
39---
40
41## Cache Invalidation
42
43| Method | Consistency | When to Use |
44|--------|-------------|-------------|
45| **TTL-based** | Eventual (up to TTL) | Simple data, acceptable staleness |
46| **Event-based** | Strong (near real-time) | Inventory, profile updates |
47| **Version-based** | Strong | Static assets, API responses, config |
48| **Tag-based** | Strong | CMS content, category-based purging |
49
50### TTL Guidelines
51
52| Data Type | TTL | Rationale |
53|-----------|-----|-----------|
54| Static assets (CSS/JS/images) | 1 year + cache-busting hash | Immutable by filename |
55| API config / feature flags | 30–60 seconds | Fast propagation needed |
56| User profile data | 5–15 minutes | Tolerable staleness |
57| Product catalog | 1–5 minutes | Balance freshness vs load |
58| Session data | Match session timeout | Security requirement |
59
60---
61
62## HTTP Caching
63
64### Cache-Control Directives
65
66| Directive | Meaning |
67|-----------|---------|
68| `max-age=N` | Cache for N seconds |
69| `s-maxage=N` | CDN/shared cache max age (overrides max-age) |
70| `no-cache` | Must revalidate before using cached copy |
71| `no-store` | Never cache anywhere |
72| `must-revalidate` | Once stale, must revalidate |
73| `private` | Only browser can cache, not CDN |
74| `public` | Any cache can store |
75| `immutable` | Content will never change (within max-age) |
76| `stale-while-revalidate=N` | Serve stale for N seconds while fetching fresh |
77
78### Common Recipes
79
80```
81# Immutable static assets (hashed filenames)
82Cache-Control: public, max-age=31536000, immutable
83
84# API response, CDN-cached, background refresh
85Cache-Control: public, s-maxage=60, stale-while-revalidate=300
86
87# Personalized data, browser-only
88Cache-Control: private, max-age=0, must-revalidate
89ETag: "abc123"
90
91# Never cache (auth tokens, sensitive data)
92Cache-Control: no-store
93```
94
95### Conditional Requests
96
97| Mechanism | Request Header | Response Header | How It Works |
98|-----------|---------------|-----------------|-------------|
99| **ETag** | `If-None-Match: "abc"` | `ETag: "abc"` | Hash-based — 304 if match |
100| **Last-Modified** | `If-Modified-Since: <date>` | `Last-Modified: <date>` | Date-based — 304 if unchanged |
101
102Prefer ETag over Last-Modified — ETags detect content changes regardless of timestamp granularity.
103
104---
105
106## Application Caching
107
108| Solution | Speed | Shared Across Processes | When to Use |
109|----------|-------|------------------------|-------------|
110| **In-memory LRU** | Fastest | No | Single-process, bounded memory, hot data |
111| **Redis** | Sub-ms (network) | Yes | **Production default** — TTL, pub/sub, persistence |
112| **Memcached** | Sub-ms (network) | Yes | Simple key-value at extreme scale |
113| **SQLite** | Fast (disk) | No | Embedded apps, edge caching |
114
115### Redis vs Memcached
116
117| Feature | Redis | Memcached |
118|---------|-------|-----------|
119| Data structures | Strings, hashes, lists, sets, sorted sets | Strings only |
120| Persistence | AOF, RDB snapshots | None |
121| Pub/Sub | Yes | No |
122| Max value size | 512 MB | 1 MB |
123| **Verdict** | **Default choice** | Pure cache at extreme scale |
124
125---
126
127## Distributed Caching
128
129| Concern | Solution |
130|---------|----------|
131| **Partitioning** | Consistent hashing — minimal reshuffling on node changes |
132| **Replication** | Primary-replica — writes to primary, reads from replicas |
133| **Failover** | Redis Sentinel or Cluster auto-failover |
134
135**Rule of thumb:** 3 primaries + 3 replicas minimum for production Redis Cluster.
136
137---
138
139## Cache Eviction Policies
140
141| Policy | How It Works | When to Use |
142|--------|-------------|-------------|
143| **LRU** | Evicts least recently accessed | **Default** — general purpose |
144| **LFU** | Evicts least frequently accessed | Skewed popularity distributions |
145| **FIFO** | Evicts oldest entry | Simple, time-ordered data |
146| **TTL** | Evicts after fixed duration | Data with known freshness window |
147
148> Redis default is `noeviction`. Set `maxmemory-policy` to `allkeys-lru` or `volatile-lru` for production.
149
150---
151
152## Caching Layers
153
154```
155Browser Cache → CDN → Load Balancer → App Cache → DB Cache → Database
156```
157
158| Layer | What to Cache | Invalidation |
159|-------|--------------|--------------|
160| **Browser** | Static assets, API responses | Versioned URLs, Cache-Control |
161| **CDN** | Static files, public API responses | Purge API, surrogate keys |
162| **Application** | Computed results, DB queries, external API | Event-driven, TTL |
163| **Database** | Query plans, buffer pool, materialized views | `ANALYZE`, manual refresh |
164
165---
166
167## Cache Stampede Prevention
168
169When a hot key expires, hundreds of requests simultaneously hit the database.
170
171| Technique | How It Works |
172|-----------|-------------|
173| **Mutex / Lock** | First request locks, fetches, populates; others wait |
174| **Probabilistic early expiration** | Random chance of refreshing before TTL |
175| **Request coalescing** | Deduplicate in-flight requests for same key |
176| **Stale-while-revalidate** | Serve stale, refresh asynchronously |
177
178---
179
180## Cache Warming
181
182| Strategy | When to Use |
183|----------|-------------|
184| **On-deploy warm-up** | Predictable key set, latency-sensitive |
185| **Background job** | Reports, dashboards, catalog data |
186| **Shadow traffic** | Cache migration, new infrastructure |
187| **Priority-based** | Limited warm-up time budget |
188
189> **Cold start impact:** A full cache flush can increase DB load 10–100x. Always warm gradually or use stale-while-revalidate.
190
191---
192
193## Monitoring
194
195| Metric | Healthy Range | Action if Unhealthy |
196|--------|--------------|---------------------|
197| **Hit rate** | > 90% | Low → cache too small, wrong TTL, bad key design |
198| **Eviction rate** | Near 0 steady state | High → increase memory or tune policy |
199| **Latency (p99)** | < 1ms (Redis) | High → network issue, large values, hot key |
200| **Memory usage** | < 80% of max | Approaching max → scale up or tune eviction |
201
202---
203
204## NEVER Do
205
2061. **NEVER cache without a TTL or invalidation plan** — data rots; every entry needs an expiry path
2072. **NEVER treat cache as durable storage** — caches evict, crash, and restart; always fall back to source of truth
2083. **NEVER cache sensitive data (tokens, PII) without encryption** — cache breaches expose everything in plaintext
2094. **NEVER ignore cache stampede on hot keys** — one expired popular key can take down your database
2105. **NEVER use unbounded in-memory caches in production** — memory grows until OOM-killed
2116. **NEVER cache mutable data with `immutable` Cache-Control** — browsers will never re-fetch
2127. **NEVER skip monitoring hit/miss rates** — you won't know if your cache is helping or hurting