Cloudflare Storage Deep Dive: KV vs D1 vs Hyperdrive vs R2 Selection and Cost Comparison

When building serverless applications on Cloudflare Workers, developers face a crucial architectural decision: “Where should I store my data?” Cloudflare offers four distinct storage solutions tailored for edge computing:
- Workers KV: An ultra-low latency global Key-Value store with eventual consistency.
- Cloudflare D1: A serverless relational SQLite database running directly at the edge.
- Cloudflare Hyperdrive: A connection pooling and query caching layer for existing PostgreSQL databases (AWS Aurora, Neon, Supabase).
- Cloudflare R2: An S3-compatible object storage solution with $0 egress fees.
Choosing the wrong storage option can lead to unexpected latency degradation or cloud bill surprises. This guide breaks down the performance benchmarks, consistency models, pricing structures, and decision frameworks for all four services.
Key Takeaways
- Workers KV: Best for 99%+ read workloads (static config, redirect rules, session tokens) with 2–5ms edge reads, but global propagation for writes takes up to 60 seconds.
- Cloudflare D1: Edge relational DB with ACID support ideal for tenant data and user profiles, capped at 10GB per DB.
- Cloudflare Hyperdrive: Accelerates queries to external PostgreSQL databases by reducing connection handshake latency from 300ms down to 15ms.
- Cloudflare R2: S3-compatible blob storage with zero egress costs, cutting bandwidth bills by 90%+ compared to AWS S3.
Storage Comparison Matrix
| Spec | Workers KV | Cloudflare D1 | Cloudflare Hyperdrive | Cloudflare R2 |
|---|---|---|---|---|
| Data Model | Key-Value | Relational (SQLite) | PostgreSQL Proxy / Cache | Object Storage (Blob) |
| Consistency | Eventual Consistency | Strong (ACID) | Inherits Origin DB | Strong Consistency |
| Read Latency (p50) | 2ms – 5ms | 5ms – 15ms | 10ms – 25ms (Cached) | 25ms – 50ms |
| Max Scale | 25MB per key, Unlimited | 10GB per DB | Origin DB limits | 5TB per object, Unlimited |
| Transactions | No | Yes (SQLite) | Yes (PostgreSQL ACID) | No |
| Egress Fees | $0 | $0 | $0 | $0 (Egress Free) |
Pricing Structure Comparison
| Service | Storage Cost | Read Operations | Write Operations | Egress Bandwidth |
|---|---|---|---|---|
| Workers KV | $0.50 / GB / mo | $0.50 / 10M reads | $5.00 / 1M writes | $0 |
| Cloudflare D1 | $0.75 / GB / mo | $0.001 / 10M rows read | $1.00 / 1M rows written | $0 |
| Hyperdrive | Free | Included with Worker | Included with Worker | $0 |
| Cloudflare R2 | $0.015 / GB / mo | $0.36 / 10M (Class B) | $4.50 / 1M (Class A) | $0 |
| (Ref) AWS S3 | $0.023 / GB / mo | $0.40 / 10M reads | $5.00 / 1M writes | $90.00 / 1TB |
Decision Tree
1. Is it large binary media (images, video, PDF, archives)?
└── YES ──> [Cloudflare R2]
└── NO
2. Are you using an external PostgreSQL DB (RDS, Supabase, Neon)?
└── YES ──> [Cloudflare Hyperdrive]
└── NO
3. Do you need SQL JOINs, foreign keys, or ACID transactions?
└── YES ──> [Cloudflare D1]
└── NO
4. Is the read/write ratio 99%+ with sub-5ms edge lookup requirements?
└── YES ──> [Workers KV]
Production TypeScript Pattern: Multi-Storage Pipeline
export interface Env {
CACHE_KV: KVNamespace;
HYPERDRIVE: Hyperdrive;
}
import { Client } from "pg";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const id = url.searchParams.get("id");
if (!id) return new Response("Missing id", { status: 400 });
// 1. Check KV Edge Cache (2ms)
const cacheKey = `prod:${id}`;
const cached = await env.CACHE_KV.get(cacheKey);
if (cached) {
return new Response(cached, { headers: { "Content-Type": "application/json", "X-Cache": "HIT" } });
}
// 2. Query Postgres via Hyperdrive (12ms)
const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });
await client.connect();
const result = await client.query("SELECT id, name, price FROM products WHERE id = $1", [id]);
await client.end();
if (result.rows.length === 0) return new Response("Not Found", { status: 404 });
const payload = JSON.stringify(result.rows[0]);
// 3. Update KV Cache
await env.CACHE_KV.put(cacheKey, payload, { expirationTtl: 3600 });
return new Response(payload, { headers: { "Content-Type": "application/json", "X-Cache": "MISS" } });
},
};
Conclusion
Mastering Cloudflare’s storage suite empowers developers to build global, sub-50ms applications while virtually eliminating egress bandwidth costs.