| name | scale |
| description | Recommend sharding, caching strategies, and read-replication patterns for Cloudflare architectures. Use this skill when preparing for growth, hitting limits, or optimizing for high traffic. |
Cloudflare Scaling Skill
Strategies for scaling Cloudflare architectures beyond default limits while maintaining cost efficiency.
Scaling Decision Matrix
| Bottleneck | Symptom | Solution |
|---|
| D1 read latency | >50ms queries | Add KV cache layer |
| D1 write throughput | Queue backlog | Batch writes, add queue buffer |
| D1 storage | Approaching 10GB | Archive to R2, partition tables |
| KV read latency | Cache misses | Key prefixing, predictable keys |
| KV write rate | 1 write/sec/key limit | Shard keys, batch writes |
| R2 throughput | Slow uploads | Presigned URLs, multipart |
| Worker memory | 128MB limit | Streaming, chunked processing |
| Worker CPU | 30s timeout | Queues, Workflows, DO |
| Subrequests | 1000/request limit | Service Bindings RPC |
| Queue throughput | Consumer lag | Increase concurrency, batch size |
Caching Strategies
Cache Hierarchy
Request → Edge Cache → KV Cache → D1 → Origin
(Tiered) (Global) (Primary)
KV Cache Patterns
Write-Through Cache
async function getWithCache<T>(
kv: KVNamespace,
db: D1Database,
key: string,
query: () => Promise<T>,
ttl: number = 3600
): Promise<T> {
const cached = await kv.get(key, 'json');
if (cached !== null) {
return cached as T;
}
const fresh = await query();
kv.put(key, JSON.stringify(fresh), { expirationTtl: ttl });
return fresh;
}
Cache Invalidation
await kv.put(key, value, { expirationTtl: 300 });
const version = await kv.get('cache:version');
const key = `data:${id}:v${version}`;
await kv.put('cache:version', String(Number(version) + 1));
await kv.put(`user:${userId}:profile`, data);
await kv.put(`user:${userId}:settings`, settings);
const keys = await kv.list({ prefix: `user:${userId}:` });
for (const key of keys.keys) {
await kv.delete(key.name);
}
Tiered Cache (Cloudflare CDN)
Enable in Worker for static-like responses:
const cache = caches.default;
app.get('/api/products/:id', async (c) => {
const cacheKey = new Request(c.req.url);
const cached = await cache.match(cacheKey);
if (cached) {
return cached;
}
const product = await getProduct(c.env.DB, c.req.param('id'));
const response = c.json(product);
response.headers.set('Cache-Control', 's-maxage=300');
c.executionCtx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
});
Sharding Strategies
Key-Based Sharding (KV)
When hitting 1 write/sec/key limit:
await kv.put('page:views', views);
const SHARD_COUNT = 10;
async function incrementCounter(kv: KVNamespace, key: string) {
const shard = Math.floor(Math.random() * SHARD_COUNT);
const shardKey = `${key}:shard:${shard}`;
const current = Number(await kv.get(shardKey)) || 0;
await kv.put(shardKey, String(current + 1));
}
async function getCounter(kv: KVNamespace, key: string): Promise<number> {
let total = 0;
for (let i = 0; i < SHARD_COUNT; i++) {
const value = kv.();
total += (value) || ;
}
total;
}
Time-Based Sharding (D1)
For high-volume time-series data:
CREATE TABLE events_2025_01 (
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
data TEXT
);
CREATE TABLE events_2025_02 (
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
data TEXT
);
function getEventsTable(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
return `events_${year}_${month}`;
}
Entity-Based Sharding (D1)
For multi-tenant applications:
interface Bindings {
DB_TENANT_A: D1Database;
DB_TENANT_B: D1Database;
}
function getDbForTenant(env: Bindings, tenantId: string): D1Database {
const dbMapping: Record<string, D1Database> = {
'tenant-a': env.DB_TENANT_A,
'tenant-b': env.DB_TENANT_B,
};
return dbMapping[tenantId] ?? env.DB_DEFAULT;
}
Read Replication Patterns
D1 Read Replicas
D1 automatically creates read replicas. Optimize access:
{
"placement": { "mode": "smart" }
}
Multi-Region with Durable Objects
For global coordination with regional caching:
export class RegionalCache {
private state: DurableObjectState;
private cache: Map<string, { value: unknown; expires: number }>;
constructor(state: DurableObjectState) {
this.state = state;
this.cache = new Map();
}
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
const key = url.searchParams.get('key');
if (request.method === 'GET' && key) {
const cached = this.cache.get(key);
if (cached && cached.expires > Date.now()) {
return Response.({ : cached., : });
}
.({ : , : });
}
(request. === && key) {
{ value, ttl } = request.();
..(key, {
value,
: .() + (ttl * ),
});
.({ : });
}
.({ : }, { : });
}
}
Eventual Consistency Pattern
For data that can tolerate staleness:
interface CacheEntry<T> {
data: T;
cachedAt: number;
staleAfter: number;
expireAfter: number;
}
async function getWithStaleWhileRevalidate<T>(
kv: KVNamespace,
key: string,
fetcher: () => Promise<T>,
options: {
staleAfter: number;
expireAfter: number;
}
): Promise<T> {
const cached = await kv.get<CacheEntry<T>>(key, 'json');
const now = Date.now();
if (cached) {
if (now < cached.staleAfter) {
return cached.data;
}
if (now < cached.expireAfter) {
kv.put(key, JSON.stringify(await buildCacheEntry(fetcher, options)));
cached.;
}
}
entry = (fetcher, options);
kv.(key, .(entry));
entry.;
}
buildCacheEntry<T>(
: <T>,
: { : ; : }
): <<T>> {
now = .();
{
: (),
: now,
: now + options.,
: now + options.,
};
}
Queue Scaling
Horizontal Scaling (Concurrency)
{
"queues": {
"consumers": [
{
"queue": "events",
"max_batch_size": 100,
"max_concurrency": 20,
"max_retries": 1,
"dead_letter_queue": "events-dlq"
}
]
}
}
Batch Processing Optimization
export default {
async queue(batch: MessageBatch, env: Bindings) {
const byType = new Map<string, unknown[]>();
for (const msg of batch.messages) {
const type = msg.body.type;
if (!byType.has(type)) byType.set(type, []);
byType.get(type)!.push(msg.body.payload);
}
for (const [type, payloads] of byType) {
await processBatch(type, payloads, env);
}
batch.ackAll();
},
};
async function processBatch(
type: string,
payloads: unknown[],
env:
) {
= ;
( i = ; i < payloads.; i += ) {
chunk = payloads.(i, i + );
(env., , chunk);
}
}
Memory Management
Streaming for Large Payloads
const data = await r2.get(key);
const json = await data.json();
app.get('/export/:key', async (c) => {
const object = await c.env.R2.get(c.req.param('key'));
if (!object) return c.json({ error: 'Not found' }, 404);
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
'Content-Length': String(object.size),
},
});
});
Chunked Processing with Durable Objects
export class ChunkedProcessor {
private state: DurableObjectState;
async processFile(r2Key: string, chunkSize: number = 1024 * 1024) {
let offset = (await this.state.storage.get<number>('offset')) ?? 0;
const object = await this.env.R2.get(r2Key, {
range: { offset, length: chunkSize },
});
if (!object) {
await this.state.storage.delete('offset');
return { complete: true };
}
await this.processChunk(await object.());
...(, offset + chunkSize);
...(.() + );
{ : , : offset + chunkSize };
}
}
Scaling Checklist
Before Launch
At 10K req/day
At 100K req/day
At 1M req/day
At 10M req/day
Cost Implications
| Scaling Strategy | Additional Cost | When to Use |
|---|
| KV caching | $0.50/M reads | D1 read heavy |
| Key sharding | More KV reads | >1 write/sec/key |
| Time partitioning | None (same D1) | >10GB data |
| Tiered Cache | None (CDN) | Cacheable responses |
| DO coordination | CPU time | Global state |
| Queue scaling | Per message | High throughput |
Anti-Patterns
| Pattern | Problem | Solution |
|---|
| Cache everything | KV costs add up | Cache hot data only |
| Shard too early | Complexity without benefit | Monitor first |
| Ignore TTLs | Stale data | Set appropriate TTLs |
| Skip DLQ | Lost messages | Always add DLQ |
| Over-replicate | Cost multiplication | Right-size replication |