Feature Flags at the Edge
Why traditional flag architectures fall apart over distance, what changes when your app runs in 300 cities, and how edge-native systems hit sub-5ms evals from anywhere on the planet.
Most feature flag SDKs were built when “the app” meant a Rails server in us-east-1 and a CDN for static assets. They evaluate locally, that part still works fine, but their idea of how rules get to the SDK assumes one origin that everyone talks to.
That assumption breaks the moment your app runs at the edge. This chapter is about what changes, and what to look for in a flag system that’s been built for it.
What “edge” means here
When we say “edge”, we mean code that runs in the network rather than in a fixed datacenter. Cloudflare Workers, Vercel Edge Functions, Fastly Compute, AWS Lambda@Edge, Deno Deploy. The defining property: a request is served by the POP nearest the user, anywhere from Singapore to São Paulo to Stockholm, not by your origin server.
The benefit is that user-perceived latency drops from “RTT to origin” to “RTT to nearest POP”, typically from 80–300ms down to 5–30ms. The cost is that your code now runs in 300 places instead of 1, and any external call it makes, including to your feature flag service, pays the round-trip from whichever city it landed in.
If that external call goes back to us-east-1, you’ve thrown away the entire point of edge.
The three architectures, ranked by suffering
Bad: SDK calls a central API on every eval
Every flag evaluation makes an HTTP call to api.flagvendor.com (which is, somewhere, ultimately one or two datacenters). A user in Sydney pays 150ms round-trip per flag. A page with three flags pays 450ms before it even starts rendering.
Nobody actually ships this on purpose. It’s the failure mode of “I integrated the SDK in five minutes and didn’t read the docs”.
Acceptable: SDK polls and caches rules locally
The SDK fetches the rules blob on startup and re-polls every N seconds. Evaluations are local. Updates take up to one polling interval to propagate.
This works for most server-side use cases. It collapses at the edge for two reasons:
-
Worker isolates are short-lived. A Cloudflare Worker isolate might serve a few hundred requests over a few minutes, then get reaped. Every cold start re-fetches the rules. If that fetch is to us-east-1, every cold start pays the round-trip, and edge workloads have constant cold starts because they horizontally scale to handle traffic in every POP.
-
Polling doesn’t survive in stateless edge runtimes. A polling loop assumes a long-lived process. Workers don’t have one. You can’t run
setIntervalfor an hour in a runtime that lives for 30 seconds.
Good: rules are pushed to the edge and SDKs read from local storage
The flag service pushes rules into edge-local storage, Cloudflare KV, Vercel Edge Config, Fastly Config Store, R2/S3 in every region. The SDK reads from that local store. Eval latency is now <5ms anywhere in the world, including cold starts.
This is the architecture that actually works at the edge. ShipSilently is built on top of it; so are the modern edge-native flag systems. If you’re evaluating a vendor for an edge workload, this is the question to ask: where does my SDK go to read the rules? If the answer involves a hop back to the vendor’s origin, keep looking.
The control plane problem
Pushing rules to the edge is the easy part. Invalidating them is where the interesting engineering lives.
The flow looks like this:
- User clicks “save” in the dashboard.
- Control plane writes to its database (the source of truth).
- Control plane writes the new rule blob to edge storage in every region.
- Edge storage replicates globally over some number of seconds.
- Every active SDK is told to re-read.
- SDKs evaluate the new rules.
The question is: how long does that whole loop take, end-to-end?
A good system is at single-digit seconds globally. A bad system is at minutes, usually because step 5 is “just wait for the SDKs to poll” rather than “actively notify them”. And without step 5, you can have edge storage that’s perfectly up-to-date and SDKs that still serve stale values for the next polling interval, because they cached the previous rules in-process and have no idea anything changed.
The solution is some form of push notification: SSE stream from a regional coordinator, Cloudflare Durable Object broadcasting via WebSocket, NATS, MQTT, pick your poison. The pattern is “edge storage for fast reads, lightweight pub-sub for invalidation”. ShipSilently uses a Durable Object per environment for this.
Multi-region consistency
The natural question: if rules are pushed to every region and a write happens in one region first, can different users see different flag values for a few seconds?
Yes. This is an unavoidable property of distributed systems. The question is whether you care.
For most flag use cases, the answer is no:
- Release flags: A 30-second skew across regions during a flag flip is invisible. The rollout was already going to take days.
- Kill switches: During an incident you want the fastest propagation possible, but a few seconds of split-brain is acceptable, and the alternative (waiting for a globally-consistent commit) makes the kill switch slower.
- Experimentation: Sticky bucketing means each user keeps their variant; regional skew only affects newly-bucketed users during the window.
For some use cases, the answer is yes:
- Entitlements: A user upgrading to Pro should see Pro features immediately. Cross-region skew here looks like a billing bug. The fix is usually to evaluate entitlement flags against your own user-state cache, not against the global rules cache, entitlements live with the user, not with the flag.
Don’t try to make your flag system globally linearizable. The few cases where you need it are the cases that shouldn’t be flags.
What this means for mobile
A native mobile app is, in a sense, the most extreme edge. The “POP” is the user’s phone, the network is whatever LTE they happened to get, and once the app is installed it can serve flag evaluations for months without re-checking. The constraints are different enough that they get their own chapter, and their own write-up in the mobile feature flag problem.
The common ground: the further your code is from your origin, the more your flag system has to push rules toward your code rather than expect your code to come find them. Server-in-a-rack flag systems can get away with polling. Edge and mobile cannot.
A simple test
If you want to know whether a flag system is built for the edge, run this experiment:
- Deploy a Worker (or Edge Function) in Sydney that evaluates a single flag.
- Measure the p99 latency of
getFlag()on a cold isolate.
If it’s under 10ms, the system is built for the edge. If it’s 100ms, the SDK is talking back to a central API on cold start. If it’s 500ms, something is really wrong.
The deeper write-up, and the full architecture of an edge-native flag system, is in the blog post Edge computing meets feature flags.
Previous: Chapter 7, Trunk-based development with flags · Back to: Academy index