Architecture

Designed to never take
your app down

Every feature flag check reads from your local adapter. Zero calls to Flipper Cloud. Zero external dependency. If Flipper Cloud goes offline, your app doesn’t notice.

Running
Your app calls Flipper.enabled?(:feature)
< 1ms
Your Application
Background Poller
Separate thread pulls from Flipper Cloud and updates your local adapter every 15s–5min based on plan. All errors silently rescued, never crashes.
async, background thread
Local Adapter
Your local data store, typically ActiveRecord backed by your own database. Persistent, always available, updated automatically on sync.
< 1ms, local to your app
Per-Request Memoization
One bulk read per HTTP request, then hash lookups for every subsequent check.
~0ms, hash lookup
network
Flipper Cloud
Online
Flipper Cloud API
Feature flag management, audit logs, access controls. Source of truth for configuration.
remote, not in critical path
ETag & Conditional Requests
304 Not Modified when nothing has changed. Minimal bandwidth, maximum efficiency.
If-None-Match, conditional GET
Webhooks
Instant signed push notifications on flag changes. Polling slows to 1-hour safety net when webhooks are enabled.
instant, signed POST
App continues, zero impact

What happens when you check a flag

Every call to Flipper.enabled? follows this path, never calling Flipper Cloud.

1
if Flipper.enabled?(:new_dashboard, current_user)
Your app checks a feature flag. This looks like a method call, because it is.
2
# Memoizer middleware, one read per request
Per-request memoization kicks in. If this is the first check in the request, all flags are loaded from the local adapter in a single bulk read. Subsequent checks are pure hash lookups.
3
# Local Adapter, your own database via ActiveRecord
Reads from your local adapter, typically ActiveRecord backed by your own database. No calls to Flipper Cloud. Your data is always there, even if Cloud is unreachable. Updated automatically when the background poller syncs.
4
true # → takes < 1ms
The result is returned. Your response renders. Flipper Cloud was never called. Your availability is not tied to ours.
# Meanwhile, in a background thread...
A separate poller thread pulls the latest flag data from Flipper Cloud and writes it to your local adapter every 15s to 5min depending on your plan. Uses ETag-based conditional requests so if nothing changed, the response is a tiny 304. With webhooks enabled, changes arrive instantly and polling slows to a 1-hour safety net. Errors are silently rescued. The thread never crashes. Your app never notices.

Layers of resilience

Multiple independent mechanisms ensure your app is never affected by external failures.

Reads are always local

The DualWrite adapter routes every read to your local adapter, typically ActiveRecord backed by your own database. Feature flag checks never call Flipper Cloud. Reads are fast and always available.

DualWrite → local adapter (ActiveRecord)

Fire-and-forget polling

The background poller runs in its own thread, rescues all exceptions, and retries on the next interval. Sync frequency is based on your plan. A failed sync never propagates to your application code.

rescue => e # swallowed, retried next interval

Graceful boot

If Cloud is unreachable at boot, the initial sync is rescued and the process starts anyway. With ActiveRecord, your app boots with the last known flag state from your database. Your deploys are never blocked.

rescue => # process starts, local adapter has data

ETag-based sync

Conditional HTTP requests with If-None-Match headers. When nothing has changed, the response is a tiny 304. Minimal bandwidth, minimal CPU. Only actual changes transfer data.

If-None-Match: "etag" → 304 Not Modified

Webhook + polling

Webhooks push changes instantly via signed POST requests. When webhooks are enabled, polling slows to a 1-hour safety net. If a webhook is ever missed, the poller eventually catches it.

webhook → instant sync | poll → 1hr safety net

Aggressive timeouts

2-second open timeout, 5-second read timeout, zero retries. If Cloud is slow, connections fail fast. Your app threads are never blocked waiting on external services.

open_timeout: 2, read_timeout: 5, max_retries: 0

What happens when things go wrong

Every failure mode has been accounted for. Here's how each one plays out.

Scenario What happens App impact
☁️Cloud goes down Poller gets errors, silently rescues them. Local adapter retains last known state. Poller retries next interval. Zero impact
🌐Network timeout 2s open / 5s read timeouts. Connection fails fast. Poller thread stays alive, tries again next interval. Zero impact
🚀Boot without Cloud Initial sync is rescued. With a persistent adapter like ActiveRecord, your app boots with the last known flag state. Poller keeps trying in background. Zero impact
🔄Poller thread error Exception rescued in run loop. Thread stays alive. Same data served until next successful sync. Zero impact
🍴Process fork (Puma) Fork detection via Process.pid check. New poller thread started automatically in child process. Zero impact
Webhook missed Background polling acts as safety net. With webhooks on, the poller runs hourly and catches any missed changes. Zero impact

Flip your first feature today

Feature flags that never slow you down. Get started free, no credit card required.

Try Flipper Cloud free
Flipper mascot