Why Your Cloudflare Workers KV Bill Just Spiked (And How to Prevent It)
KV writes cost $5 per million — 16x more than reads. Learn why apps using KV as a general-purpose cache hit surprise bills, and how to monitor KV costs in real time.
You checked your Cloudflare bill this morning and did a double-take. Your Workers project — a simple API with some caching — just cost $80 more than expected. The culprit? KV writes.
This happens more often than Cloudflare's docs suggest. Here's why KV bills spike, and how to catch it before it hurts.
The KV Pricing Trap
Cloudflare KV pricing looks harmless at first glance:
| Operation | Cost |
|---|---|
| KV Read | $0.50 per million |
| KV Write | $5.00 per million |
| KV Delete | $5.00 per million |
| KV List | $5.00 per million |
Writes are 16x more expensive than reads. This asymmetry catches developers off guard because:
- Local development hides the cost —
wranglerdoesn't show write costs during testing - Cache invalidation patterns generate writes — every time you "refresh" cache, that's a write
- High-frequency updates — session data, rate limiting counters, or analytics incrementing on every request
A Worker handling 100K requests/day with just one KV write per request costs $15/month in writes alone. Two writes per request? $30. Ten? $150.
Common KV Cost Traps
1. Using KV as a Session Store
Storing user sessions in KV seems natural, but session writes on every request add up fast:
// Expensive: writes on every request
export default {
async fetch(request, env) {
const sessionId = getSessionId(request);
// This is a WRITE — $5 per million
await env.SESSIONS.put(sessionId, JSON.stringify({
lastSeen: Date.now(),
requestCount: (await env.SESSIONS.get(sessionId))?.requestCount + 1 || 1
}));
return new Response("OK");
}
};Better approach: Use Durable Objects for mutable state, or Cache API for ephemeral session data.
2. Cache-Aside with Aggressive Invalidation
// Every cache miss + refresh = 1 read + 1 write
async function getDataWithCache(env, key) {
let data = await env.CACHE.get(key);
if (!data || isStale(data)) {
data = await fetchFromOrigin(key);
await env.CACHE.put(key, data); // $5 per million writes
}
return data;
}If your cache hit ratio is 50%, you're doing one write per two requests. At 1M requests/day, that's $75/month in writes.
3. Rate Limiting Counters
// Incrementing a counter on every request
await env.RATE_LIMITS.put(
`ratelimit:${ip}`,
JSON.stringify({ count: current + 1, window: Date.now() })
);One write per request = $150/month at 1M requests/day.
Why You Don't See It Coming
Cloudflare's cost metrics have a 15-minute delay. By the time you see the spike in the dashboard, you've already paid for it. There's no real-time cost alerting built into the platform.
This is especially dangerous because:
- Traffic spikes (viral post, DDoS, bot traffic) generate sudden write bursts
- Code changes that accidentally increase write frequency go unnoticed
- Retry storms from failed Workers can generate millions of writes in minutes
Monitoring KV Costs in Real Time
Since Cloudflare doesn't provide real-time cost alerts, you need to track it yourself. Here's a pattern using Tail Workers:
// tail-worker.js — track KV operations per request
export default {
async tail(events, env, ctx) {
for (const event of events) {
// Count KV subrequests
const kvWrites = event.subrequests?.filter(
r => r.url?.includes('kv') && r.method === 'PUT'
).length || 0;
if (kvWrites > 0) {
// Send to your monitoring system
await fetch('https://api.flarelog.dev/ingest', {
method: 'POST',
headers: { 'Authorization': `Bearer ${env.FLARELOG_API_KEY}` },
body: JSON.stringify({
level: 'INFO',
message: `KV write detected`,
metadata: {
kvWrites,
estimatedCost: kvWrites * 0.000005, // $5 per million = $0.000005 per write
worker: event.scriptName,
colo: event.eventTimestamp
}
})
});
}
}
}
};With this setup, you can:
- Alert when KV writes exceed a threshold (e.g., >1000 writes/minute)
- Track cost per request and identify expensive endpoints
- Detect anomalies like sudden write spikes from traffic bursts
Better Alternatives for High-Write Workloads
If KV writes are eating your budget, consider these alternatives:
| Use Case | Better Alternative | Why |
|---|---|---|
| Session data | Durable Objects | Mutable state without write costs |
| Ephemeral cache | Cache API | Free, in-memory, per-request |
| Rate limiting | Durable Objects | Atomic increments, no per-write cost |
| Analytics counters | Durable Objects | Batch updates, reduce write frequency |
| Config/data that rarely changes | KV | This is what KV is actually for |
The 15-Minute Blind Spot
The real danger isn't just the cost — it's the delay in discovering it. A runaway Worker or traffic spike can generate thousands of dollars in KV writes before Cloudflare's dashboard updates.
This is where real-time monitoring becomes essential. With Tail Workers capturing subrequest data immediately, you can:
- Set cost thresholds and get alerted before the bill grows
- Identify the exact Worker and endpoint causing writes
- Correlate write spikes with traffic patterns or code deployments
Summary
KV is powerful for read-heavy, rarely-changing data. But using it as a general-purpose store or cache with frequent writes leads to surprise bills.
Key takeaways:
- Writes cost 16x more than reads — design for read-heavy patterns
- Cache API and Durable Objects are better for high-write use cases
- Cloudflare's 15-minute cost delay means you find out too late
- Real-time monitoring via Tail Workers catches spikes as they happen
Monitor your KV costs before they monitor your bank account.
Written by Chiheb Nabil, founder of FlareLog. Building observability tools for developers shipping on Cloudflare Workers.
Never miss an invisible crash again
FlareLog catches the errors Cloudflare can't log. Set up the Tail Worker in 5 minutes and see every crash, timeout, and cost spike in real time.
Start logging free →