Back in 2019, we flipped a major retail client to write-back caching right before Black Friday to shave milliseconds off their cart updates. It worked beautifully — until a single edge PoP in Ashburn went dark for 47 seconds and took 3,200 unflushed cart mutations with it. We spent the next six hours replaying checkout logs. That incident taught me that your cache write policy isn't just a performance tuning knob; it's a durability contract with your users, and picking the wrong CDN caching strategy will bite you when you can least afford it.
Write-Through Caching: When Consistency Beats Raw Speed
With a write-through cache, every write operation hits the edge cache and the origin synchronously before you get an acknowledgement. The client doesn't get a 200 OK until both places have confirmed the write. I've relied on this model for anything where losing a write isn't an option — user profiles, inventory counts, auth tokens, anything financial. You pay for that safety with latency, but you don't wake up wondering if your edge and origin have diverged.
The mechanics are simple, but the implications for your CDN are significant. On a cache miss, you fetch from origin as usual. On a write, your edge worker or caching proxy intercepts the PUT/POST, writes to its local store, then proxies that same payload to origin and waits. If the origin write fails, you roll back or invalidate the cached entry. This keeps your distributed caches strongly consistent without needing a complex reconciliation job later. For teams struggling to keep dozens of PoPs aligned, pairing write-through with solid Cache Coherence Protocols at the CDN Edge: Keeping Distributed Caches in Sync is often the most predictable path, as described in the HTTP Working Group Specifications for cache validation and revalidation.
Don't assume write-through means slow for reads, though. Reads are still served directly from edge RAM or SSD, so your p95 for GETs stays excellent. The hit is isolated to writes, which for most content sites are under 15% of traffic. I typically recommend this CDN caching strategy when your write volume is moderate and your users would notice stale data immediately. Think CMS publishes, comment threads, or API responses where a user expects to see their own edit reflected instantly across regions.
// Edge worker example: write-through cache implementation
async function handleWriteThrough(request, cache, originUrl) {
const payload = await request.clone().text();
const cacheKey = new Request(request.url, { method: 'GET' });
// 1. Write to edge cache first
await cache.put(cacheKey, new Response(payload, {
headers: { 'Content-Type': 'application/json', 'X-Cache-Status': 'WRITTEN' }
}));
// 2. Synchronously replicate to origin - client waits for this
const originResponse = await fetch(originUrl, {
method: request.method,
body: payload,
headers: request.headers
});
if (!originResponse.ok) {
// Roll back edge on origin failure to avoid divergence
await cache.delete(cacheKey);
throw new Error(`Origin write failed: ${originResponse.status}`);
}
return new Response(JSON.stringify({ status: 'synced' }), { status: 200 });
}
Write-Back Caching: Trading Durability for Edge Performance
Write-back cache — sometimes called write-behind — flips the logic. You acknowledge the write as soon as it's in the edge cache, mark the entry as dirty, and flush it to origin asynchronously after a delay, in batches, or when the entry is evicted. From the client's perspective, writes feel instant. We've seen write latency drop from 80ms to under 10ms at the edge with this switch, and origin QPS can fall by 60-70% because you're coalescing multiple edits to the same object.
That speed comes with real risk that you can't hand-wave away. If that edge node crashes, reboots, or gets evicted before the flush, dirty data is gone. I learned to never run write-back without a persistent buffer in front of origin — usually a durable queue like Kafka or Kinesis, plus an NVM-backed write-ahead log on the edge itself. You also need to think carefully about ordering. If a user updates their cart three times in five seconds, you don't want to flush those out of order or collapse them incorrectly. Without a proper Probabilistic Caching with Bloom Filters: Memory-Efficient Cache Lookups layer or version vector to track dirty keys, you'll end up flushing cold data or missing hot keys entirely.
Where does write-back actually shine? High-frequency, low-criticality writes. Analytics beacons, A/B test assignments, session heartbeat counters, or user-generated content drafts where losing the last few seconds isn't catastrophic. It also pairs well with Machine Learning for Cache Warming: Predictive Prefetching That Actually Works because you can batch your predicted writes and flush them during off-peak windows. The guidance in web.dev Articles on stale-while-revalidate is a good mental model here — you're essentially serving stale-but-fast and reconciling later.
# Pseudo-config: write-back with async flush and dirty tracking
cache_policy {
mode write_back;
flush_interval 2s;
max_dirty_keys 10000;
coalesce_writes on; # merge multiple writes to same key
wal_path /var/lib/edge/wal.log; # survive edge restart
}
# Background flusher - runs every 2s or when threshold hit
on_flush_trigger(dirty_set):
for key in dirty_set.coalesced():
payload = edge_cache.get(key)
try:
origin.put(key, payload, headers={'X-Edge-Flushed': '1'})
edge_cache.clear_dirty_flag(key)
wal.ack(key)
except OriginTimeout:
retry_queue.push(key, backoff=exponential)
metrics.increment("write_back_origin_retry")
Picking the Right Cache Write Policy for Your Workload
There's no universal winner; I've run both policies in the same CDN for different routes. The decision usually comes down to three questions: How much write latency can your users tolerate? How painful is data loss for that route? And how much operational complexity can your team carry? If you're a two-person team without a durable queue or cross-PoP replication, write-back will create more incidents than it prevents. If you're pushing 10k writes per second on a personalization API and 50ms matters, write-through will choke your origin.
I tell teams to segment by path, not by entire domain. Use write-through cache for /api/user, /api/inventory, /api/checkout and write-back cache for /api/events, /api/drafts, /beacon. You can even mix them dynamically — start with write-through, then switch a hot key to write-back if it sees more than N writes per minute. That hybrid approach requires good observability: track dirty key age, flush lag, and origin reconciliation errors as first-class SLOs, not afterthoughts.
When we benchmarked both strategies on an identical 12-node edge cluster serving a mixed e-commerce workload (70% reads, 30% writes), the trade-offs were stark. The numbers below are from that test — your mileage will vary, but the pattern holds across most CDN caching strategy evaluations I've done over the last decade.
| Metric (p95 unless noted) | Write-Through Cache | Write-Back Cache (2s Flush) |
|---|---|---|
| Write Latency (client-observed) | 42 - 68 ms | 7 - 12 ms |
| Read Latency (cache hit) | 8 ms | 9 ms |
| Origin Write QPS Reduction | 0% (every write hits origin) | 62% (via coalescing) |
| Data Loss Window on Edge Failure | 0 ms (no dirty data) | ~2,000 ms (flush interval) |
| Operational Complexity | Low | High (requires WAL + retry queue) |
Implementing Your Cache Write Strategy at the Edge
Theory's great, but the config is where most teams shoot themselves in the foot. You don't just flip a "write-through" switch — you're wiring together origin logic, edge behavior, and invalidation. For a standard write-through setup, we want every POST/PUT to synchronously update the origin and then immediately warm the edge. That means your API shouldn't return a 200 until both writes ack. Yes, you'll add 15-30ms to your P95 write latency, but you buy yourself the guarantee that the next GET — whether it hits IAD, LHR, or SIN — serves the fresh object.
Here's how we typically implement it with a Fastly-style VCL + origin worker. The key is handling the write at the edge, pushing to origin, and only then caching. Don't cache optimistically before origin confirms — that's how you end up with phantom objects that never actually persisted. If you're running a multi-tier setup, pair this with Consistent Hashing for CDN Cache Distribution: A Practical Implementation Guide so your writes land on the correct shard and you don't create duplicate hot keys across your shield layer.
For write-back, the logic inverts. You'll ack the write at the edge in under 25ms, queue it (we use Kafka or SQS with a 1-2 second flush interval), and coalesce. This is where you'll see TTFB drop from ~180ms to 22ms for write-heavy workloads like user-generated comments or A/B test assignments. But you *must* have a durable buffer. We've seen teams lose 4-7 minutes of data during an edge PoP flap because their queue was in-memory only. If you can't afford to lose it, don't write-back without persistence.
// Edge Worker: Hybrid Write-Through / Write-Back Router
async function handleWrite(request, env) {
const isCritical = request.headers.get('X-Cache-Policy') === 'critical';
const payload = await request.json();
if (isCritical) {
// WRITE-THROUGH: Consistency path for pricing, inventory, auth
const originRes = await fetch(env.ORIGIN_URL + '/api/update', {
method: 'PUT',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' }
});
if (!originRes.ok) return originRes; // Don't cache failed writes
// Only cache after origin confirms - 200ms TTL for validation
await env.EDGE_CACHE.put(request.url, payload, {
ttl: 300,
surrogateKeys: ['product-' + payload.id]
});
return new Response('OK', { status: 200, headers: { 'X-Cache': 'WRITE_THROUGH' } });
} else {
// WRITE-BACK: Performance path for analytics, UGC, personalization
await env.EDGE_CACHE.put(request.url, payload, { ttl: 60 });
await env.WRITE_QUEUE.send(payload); // Durable queue, flushed every 1500ms
return new Response('Accepted', { status: 202, headers: { 'X-Cache': 'WRITE_BACK_QUEUED' } });
}
}
Beyond the Basics: Shielding, Collapse, and When Writes Fail
Once you're in production, the write policy isn't your biggest headache — failure modes are. With write-through, your nightmare is origin latency spikes. If your origin P99 jumps from 80ms to 1.2s during a deploy, every edge write now blocks. We mitigate this with request collapsing and a 500ms origin shield timeout. If the shield can't reach origin, fail open and serve stale with a stale-while-revalidate: 60 grace period rather than queueing indefinitely. You'll serve data that's 60 seconds old, but you won't cascade a 502 across 300 PoPs.
Write-back fails differently, and it's sneakier. Because you've already told the client "202 Accepted," you can't just error out. You need a reconciliation loop. We run a background worker that drains the queue to origin every 1.5 seconds and a sweeper that compares edge vs. origin checksums every 5 minutes. If an edge node dies before flush — and it will, about 0.03% of writes in our last 90-day window — that sweeper is your only safety net. That's also why granular invalidation matters so much here. When you finally flush that batch of 500 coalesced updates, you don't want to purge /*. Use Cache Tag Design Patterns: Organizing Content for Granular Invalidation and Surrogate Keys for Targeted Cache Purging: Beyond URL-Based Invalidation to purge just surrogate-key: user-12345 or product-987 instead of nuking your entire 94% hit ratio.
And don't ignore thundering herds on cold keys. We saw a client switch to pure write-back for a flash sale and end up with 12,000 concurrent edge writes for the same SKU because there was no coalescing window. Their origin recovered, but the edge queue backed up for 18 minutes. Fix? Add a 100-200ms collapse window at the shield: if 50 writes for the same key arrive in that window, coalesce them into one origin write and broadcast the final state. You’ll cut origin write QPS by 80-90% without any visible staleness to the end user.
Frequently Asked Questions
Can I mix write-through and write-back on the same CDN?
Absolutely, and you should. That's the hybrid model most large CDNs run. Use write-through for anything where consistency matters — inventory counts, pricing, user entitlements — and write-back for high-volume, low-risk data like view counts, comments, or personalization pings. Route by URL path or a header like X-Cache-Policy: critical so your edge logic can decide in <5ms.
How much data can I realistically lose with write-back?
It depends on your flush interval and queue durability. With an in-memory queue flushing every 2 seconds, you're exposed to 2 seconds of data loss per PoP failure. With a durable queue like Kafka or SQS, that drops to near zero, but your acknowledgment latency goes up to 40-60ms. For us, the sweet spot is a 1.5-second batched flush to a durable queue — we average 22ms edge ack time and haven't lost a confirmed write in 14 months.
Does write-back hurt my cache hit ratio?
Temporarily, no — it actually helps, because you're warming the edge instantly. Long-term, it can if your invalidation is sloppy. If you purge too broadly after flushing to origin, you'll evict hot objects and your hit ratio can tank from 96% to 71% overnight. Use surrogate keys to make your post-flush purges surgical. That keeps your hit ratio stable while still getting the write performance win.
Which strategy is safer for e-commerce checkouts?
Always write-through for checkout, cart, and payment flows. You cannot afford to tell a customer their order succeeded and then lose it in a queue. The extra 20-30ms is invisible compared to the cost of an inconsistent order state. Save write-back for the surrounding content — product reviews, recently viewed, recommendation feeds — where a second or two of eventual consistency won't cost you revenue.