The modern CDN is no longer a stateless cache. With compute at 300+ points of presence, we can now run stateful logic within 20ms of every user — but choosing the wrong state primitive will cost you consistency, latency, or both. This guide breaks down the three core edge state models and when to use each.
For years, edge logic was simple: cache static assets, terminate TLS, and forward dynamic requests to origin. State lived exclusively in your origin database. That model is now a bottleneck. As interactive workloads move to the edge — personalization, A/B testing, real-time collaboration, and per-user rate limiting — you need state that is both local and durable. Understanding how CDNs work at the network layer is prerequisite, but state management is where architecture decisions become critical.
1. The Stateless Edge Problem
Traditional CDN PoPs are stateless by design. Any request can land on any server, and horizontal scaling is trivial because no local data must be preserved. This is ideal for caching but fails for three common use cases:
- Personalization: Serving a different price, banner, or feature flag per user or geography without a round-trip to origin.
- Coordination: Enforcing a global rate limit, managing a shared websocket room, or sequencing writes to a single shopping cart.
- Low-latency writes: Accepting writes from users in Tokyo and Berlin with <50ms acknowledgement, not 200ms+ to us-east-1.
If you force these through origin, you lose the primary benefit of the edge: latency. If you try to fake state with cookies or local memory, you get inconsistency and data loss on eviction. The solution is to treat state as a first-class edge concern with explicit consistency guarantees.
Before optimizing state, ensure your baseline is solid. Review your CDN performance metrics — especially TTFB, cache hit ratio, and origin offload — so you can measure the impact of moving state to the edge.
2. KV Stores: Global, Eventually Consistent Config
Edge KV stores — Cloudflare Workers KV, Fastly Config Store / Edge Dictionary, Akamai EdgeKV — are the simplest state primitive. They provide a globally replicated key-value map optimized for read-heavy workloads.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
How they work
Writes go to a central control plane and then propagate asynchronously to all PoPs. Reads are served from local memory or SSD at the PoP, typically in <5ms. Values are usually limited to 2–25 MB, keys to 512 bytes, and eventual consistency means a write in Virginia may not be visible in Singapore for 5 to 60 seconds.
Ideal use cases
- Feature flags and kill switches
- URL redirects and rewrites (10k–1M entries)
- A/B test assignments and edge personalization config
- Blocked IPs, bot signatures, and WAF rules
- Static JSON for localization or pricing tiers
KV is not suitable for counters, sessions that require read-after-write consistency, or transactional data. It also has no built-in atomic increments or conditional writes beyond simple CAS in some implementations.
// Read flag from KV with local cache + fallback
export default {
async fetch(req, env) {
const url = new URL(req.url);
// Try edge KV first - <5ms hit
const flag = await env.FEATURE_FLAGS.get("new_checkout", { type: "json", cacheTtl: 60 });
if (flag?.enabled && flag.rollout > Math.random()) {
return fetch("https://origin.example.com/checkout-v2" + url.search);
}
return fetch(req);
}
}
For a deeper primer on caching semantics that underpin KV, see our CDN fundamentals guide.
3. Durable Objects: Strong Consistency and Coordination
Durable Objects, pioneered by Cloudflare, solve the opposite problem: what if you need a single, authoritative instance for a specific entity? Think of a Durable Object as a tiny, single-threaded server that lives at the edge and is addressed by name.
Each object has a unique ID (e.g., cart:8f3a or room:lobby-42) and provides strongly consistent storage plus in-memory state. The runtime guarantees that only one instance of that ID is active globally at any time. All requests for that ID are routed to the same PoP, where they are processed sequentially. This eliminates race conditions without distributed locks.
What this enables
- WebSockets & realtime: Keep all participants of a chat room or multiplayer session on the same object, broadcasting via in-memory connections.
- Rate limiting: Atomic counters per IP or API key without origin contention.
- Transactional carts & sessions: Serialize updates to a single cart to prevent overselling.
- Leader election & queues: One object acts as coordinator for a workflow.
export class RateLimiter {
constructor(state, env) { this.state = state; }
async fetch(req) {
const ip = req.headers.get("cf-connecting-ip");
let count = (await this.state.storage.get(ip)) || 0;
if (count >= 100) return new Response("Rate limited", { status: 429 });
await this.state.storage.put(ip, count + 1, { expirationTtl: 60 });
return new Response("OK", { headers: { "x-remaining": 100 - count -1 } });
}
}
// In worker: id = env.RATE_LIMITER.idFromName(ip); stub = env.RATE_LIMITER.get(id);
The tradeoff is locality. A Durable Object lives in one region at a time. If your users are globally distributed for the same object, some will incur cross-region latency. The pattern is to shard aggressively — millions of objects, each close to its users — not one global object. For global e-commerce workloads, this sharding strategy is essential; see CDN for ecommerce patterns for cart and inventory isolation.
4. Edge Databases: Relational State Near Users
Edge databases bring SQLite or MySQL-compatible SQL to the edge with replication. Options include Cloudflare D1, Turso (libSQL), Fly.io LiteFS, and PlanetScale/Neon read replicas at the edge. They aim to give you familiar relational semantics with much lower read latency than origin.
Architectures vary:
- Primary + read replicas at edge (Turso, PlanetScale): Writes go to a primary region, reads are served from nearby replicas with replication lag of 10–100ms. Great for read-heavy apps like product catalogs or user profiles.
- SQLite at the edge (D1): Each database is automatically replicated across regions. Recent versions support read replication and bookmark-based consistency for read-after-write.
Edge DBs are ideal when you need secondary indexes, JOINs, or ad-hoc queries that KV cannot provide, but you still need <50ms reads. They are not yet a replacement for high-write transactional systems — write throughput is typically 10–100x lower than a dedicated Aurora or Spanner cluster, and cross-region writes still incur latency.
import { createClient } from "@libsql/client/web";
const db = createClient({ url: env.TURSO_URL, authToken: env.TURSO_TOKEN });
export async function getProduct(slug, requestRegion) {
// Read from nearest replica automatically
const rs = await db.execute({
sql: "SELECT id, name, price, inventory FROM products WHERE slug = ? LIMIT 1",
args: [slug]
});
// Stale-while-revalidate pattern for inventory
if (rs.rows[0]?.inventory < 5) {
// Revalidate from origin for low-stock items
ctx.waitUntil(revalidateInventory(slug));
}
return rs.rows[0];
}
For image-heavy catalogs backed by edge DBs, pair this with CDN image optimization to keep product pages fast end-to-end.
5. Choosing the Right Primitive
Use access pattern, not hype, to decide. The table below summarizes the tradeoffs we see in production.
| Dimension | KV Store | Durable Objects | Edge Database |
|---|---|---|---|
| Consistency | Eventual (1–60s replication) | Strong, single-threaded per ID | Read-replica eventual; bookmark consistency optional |
| Latency (read) | 1–5 ms (local cache) | 5–20 ms (routed to object location) | 5–25 ms (nearest replica) |
| Latency (write) | 200–800 ms (global propagation) | 10–40 ms (local to object) | 50–150 ms (to primary) |
| Data model | Key → blob / JSON | Key-value + in-memory + alarms | Relational SQL, indexes, JOINs |
| Best for | Flags, redirects, config | Coordination, sessions, realtime | Catalogs, profiles, queries |
| Scale unit | Billions of keys | Millions of objects (shard per entity) | Thousands of DBs / replicas |
| Cost driver | Reads (cheap), writes & storage | Requests + duration (GB-s) | Rows read, replicas, storage |
A practical architecture often combines all three: KV for feature flags that gate a new edge DB query, and a Durable Object to serialize writes to that DB for a single user. For an overview of how these fit into overall CDN design, see the CDN architecture overview.
6. Production Patterns and Pitfalls
Pattern 1: Read-through with stale-while-revalidate
Serve from KV or edge DB immediately, then revalidate asynchronously if data is older than a threshold. This keeps p95 low while bounding staleness. Set cacheTtl per key type — 60s for pricing, 300s for CMS content.
Pattern 2: Durable Object as write-through cache
Place a Durable Object in front of your edge DB for hot entities (e.g., trending product inventory). The object holds the authoritative count in memory and flushes to the DB every second or on alarm. This gives you strong consistency for increments without hammering the DB.
Pattern 3: Regional pinning
For latency-sensitive Durable Objects, create IDs that encode region (e.g., cart:eu:123) and route users to the nearest region via smart placement or jurisdiction hints. This avoids a US object serving EU users.
Common pitfalls
- Using KV for counters: Lost increments under concurrent writes. Use Durable Objects or edge DB with atomic operations.
- Single global Durable Object: Becomes a hotspot and latency sink. Shard per user/room.
- Ignoring replication lag: Showing stale inventory after purchase. Use bookmark tokens or read from primary for critical reads.
- No fallback: Edge storage can throw. Always have an origin fallback and circuit breaker.
Instrument everything: emit per-primitive hit rate, replication lag, and error rate to your observability stack. Edge state failures are often silent and manifest as stale data, not 500s.
Edge state management is not about picking a winner — it is about composing primitives. KV gives you global config at memory speed, Durable Objects give you coordination without distributed systems pain, and edge databases give you relational power near users. Start with KV for config, add Durable Objects when you need serialization, and introduce an edge DB when you need queries. Measured this way, you keep the edge fast, consistent where it matters, and operationally simple.