Turning a Flag Off
Turning a flag off is the kill switch: the switch on the flag's page, "is_active": false (or
POST …/deactivate), or a safety rollback to 0%. The server's answer changes at once: from the
next evaluation it answers enabled: false with reason: "inactive", and it keeps no cache of
flags. Each client, though, keeps serving an answer it already has until that answer expires. The
same goes for any change to a flag: its rollout percentage, its rules, a rollback to a lower
percentage.
How long each client takes
With the defaults, every client below serves the change within 5 minutes, the next time your code asks it for the flag, except the edge SDK: 1 minute, or about 2 with a KV store, plus the store's own propagation time (an instance can copy a KV entry up to 60 seconds old into its memory for another 60).
The server-mode JavaScript and Python SDKs ask the API again only after their cache lifetime ends, not before; a flag turned off is therefore served for at most that lifetime after the switch.
| SDK | Cache lifetime (default) | Clear it from your code |
|---|---|---|
| JavaScript | cacheTtlMs (300000 ms) |
clearCache() |
| React | config.cacheTtlMs (300000 ms) |
client.clearCache(), with client from useExperimentation() |
| React Native | cacheTtlMs (300000 ms) |
clearCache(), and clearStorage() for the copy in AsyncStorage |
| Python | cache_ttl_seconds (300) |
clear_cache() |
| Go | WithCacheTTL (5m); 0 or less never expires |
none: create a new client |
| Java | cacheTtlMs (300000); Spring experimentation.cache-ttl-seconds (300) |
clearCache(), invalidateCache(userId, key) |
| .NET | CacheTtlSeconds (300) |
ClearCache() |
| Ruby | cache_ttl (300) |
clear_cache |
| PHP | cacheTtl (300), and never past the PHP process |
clearCache() |
| Elixir | :cache_ttl (300) |
clear_cache(client) |
| iOS | cacheTTL (300 seconds) |
clearCache() |
| Android | cacheTtlMs (300000) |
clearCache(), invalidateCache(userId, key) |
| Flutter | cacheTtl (5 minutes) |
clearCache() |
| Edge | cacheTtlMs (60000 ms); with a KV store, kvTtlSeconds on Cloudflare (cacheTtlMs in seconds, at least 60) or kvTtlMs on Deno (cacheTtlMs) |
clearCache() (this instance's memory only) |
| OpenFeature provider (JavaScript, Python) | the provider's cacheTtlMs (300000 ms) / cache_ttl (300), passed to the SDK |
provider.client.clearCache() / provider.client.clear_cache() |
A React or React Native component already on screen does not ask again. It keeps the answer it rendered until it mounts again (a screen opened anew, a reloaded page or a restarted app), so a screen a navigator keeps mounted does not ask again until then; the SDKs neither poll nor stream. Nothing reaches a screen that is already open until it mounts the component again.
A server-rendered React page that uses a bootstrap shows the answer the server had when it rendered the page, then the answer its own request gets; if that request fails, it keeps the rendered answer.
Local evaluation (JavaScript and Python, beta)
The SDK refreshes the ruleset every 30 seconds, give or take 10% (refreshIntervalMs /
refresh_interval_seconds, at least 5 seconds), so a flag it answers locally serves the change
within 33 seconds. A flag it sends to the server (see
what the server still evaluates) follows
the cache lifetime above.
While a client cannot reach the API
| Client | What it serves |
|---|---|
| React Native, iOS | The last answer it stored for that user and flag, with no time limit, also after the app restarts (offlineFallback / enableOfflineFallback, on by default). A flag turned off while a device is offline stays on there until the device reaches the API once. Turn the fallback off if serving off is safer than serving a stale on. |
| Android, Flutter | The same, from the offline store: in memory by default, so until the app's process ends; across restarts with a persistent store (a SharedPreferences adapter on Android, SharedPreferencesOfflineStore on Flutter). |
| A React page rendered with a bootstrap | The answer it was rendered with, until it is reloaded, or for the life of a shared cache entry that serves it. |
| Every other SDK | The cached answer until its lifetime ends; then the call fails, and the SDK answers as for any failure (see its page). |
| Local evaluation | The last ruleset it loaded, with no time limit unless you set maxStaleMs / max_stale_seconds. A failed refresh is tried again later each time, up to 5 minutes apart, so once the API answers again a change can take up to 5 minutes more. A 401, 403 or 404 drops the ruleset at once: every flag then goes to the server, and the ruleset is asked for again every 10 minutes. |
With @controlvar/js-sdk 0.2.0 or controlvar 0.2.0 and earlier, set cacheTtlMs /
cache_ttl_seconds (5 minutes by default) lower if these limits must hold: once those versions
stop evaluating a flag locally (maxStaleMs / max_stale_seconds passed, the ruleset dropped,
close()), a user they answered locally keeps that answer from their cache for up to that lifetime.
Making it faster
- A shorter cache lifetime. Each user and flag then asks again once per lifetime, and each SDK
path, such as one flag's evaluate route, takes 6,000 requests a minute per IP address by default
(
SDK_RATE_LIMIT_PER_MINUTE). - Clearing the cache from your code when you know a flag changed.
- A shorter
refreshIntervalMsin local evaluation.
Safety rollbacks
A safety rollback changes the flag on the server like any other change, and reaches clients on the
same timetable. The safety monitor looks for a breach every 5 minutes by default
(SAFETY_CHECK_INTERVAL_MINUTES), so from a breach to every client serving the rolled-back answer
can take that long plus the client's time above.