Implement Optimistic Decisions
Why use optimistic decisions
Section titled “Why use optimistic decisions”Standard SDK initialization waits for the datafile to download before serving decisions. On cold starts or in high-latency environments, this delay blocks the critical path. Optimistic decisions flip the model: serve immediately from a cached datafile, then sync the latest configuration in the background. Users get instant responses, and flag changes propagate within one polling cycle.
What you will do
Section titled “What you will do”- Cache the datafile locally for instant startup
- Configure the SDK for optimistic initialization
- Handle the transition from cached to fresh data
- Monitor for stale-data edge cases
Step 1: Cache the datafile
Section titled “Step 1: Cache the datafile”Store the datafile on disk, in a database, or in an in-memory cache so the SDK can read it before making any network request.
import fs from 'fs';
import { createInstance } from '@optimizely/optimizely-sdk';
const DATAFILE_PATH = '/tmp/optimizely-datafile.json';
// Save after each successful fetch
function cacheDatafile(datafile) {
fs.writeFileSync(DATAFILE_PATH, JSON.stringify(datafile));
}
// Load on startup
function loadCachedDatafile() {
try {
const raw = fs.readFileSync(DATAFILE_PATH, 'utf-8');
return JSON.parse(raw);
} catch {
return null; // No cache available
}
} import json
import os
DATAFILE_PATH = '/tmp/optimizely-datafile.json'
def cache_datafile(datafile):
with open(DATAFILE_PATH, 'w') as f:
json.dump(datafile, f)
def load_cached_datafile():
try:
with open(DATAFILE_PATH, 'r') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return None using System.IO;
using System.Text.Json;
public class DatafileCache
{
private const string Path = "/tmp/optimizely-datafile.json";
public static void Save(string datafile)
{
File.WriteAllText(Path, datafile);
}
public static string? Load()
{
try { return File.ReadAllText(Path); }
catch { return null; }
}
} Step 2: Initialize with cached data, poll in background
Section titled “Step 2: Initialize with cached data, poll in background”Create the SDK instance with the cached datafile so decisions are available immediately. Enable polling so the SDK fetches updates without blocking.
const cachedDatafile = loadCachedDatafile();
const optimizely = createInstance({
sdkKey: process.env.OPTIMIZELY_SDK_KEY,
datafile: cachedDatafile, // Serve from cache immediately
datafileOptions: {
autoUpdate: true,
updateInterval: 30_000, // Poll every 30 seconds
urlTemplate: 'https://cdn.optimizely.com/datafiles/%s.json',
},
});
// Decisions are available NOW if cache exists
// The SDK polls for updates in the background
if (cachedDatafile) {
console.log('SDK ready from cache -- serving optimistic decisions');
} else {
// No cache -- must wait for first fetch
await optimizely.onReady();
console.log('SDK ready from network');
} from optimizely import optimizely as opti
from optimizely.config_manager import PollingConfigManager
cached = load_cached_datafile()
config_manager = PollingConfigManager(
sdk_key=os.environ['OPTIMIZELY_SDK_KEY'],
update_interval=30,
datafile=json.dumps(cached) if cached else None,
)
client = opti.Optimizely(config_manager=config_manager)
if cached:
print('SDK ready from cache')
else:
# Block until first datafile fetch completes
import time
while not client.config_manager.get_config():
time.sleep(0.1)
print('SDK ready from network') var cached = DatafileCache.Load();
var optimizely = cached != null
? OptimizelyFactory.NewDefaultInstance("YOUR_SDK_KEY", cached)
: OptimizelyFactory.NewDefaultInstance("YOUR_SDK_KEY");
if (cached != null)
{
Console.WriteLine("SDK ready from cache");
}
else
{
// Block until datafile is available
await Task.Delay(2000); // Allow time for initial fetch
Console.WriteLine("SDK ready from network");
} Step 3: Update the cache on each poll
Section titled “Step 3: Update the cache on each poll”Register a notification listener to save the datafile each time the SDK receives a fresh copy. This keeps the cache current for the next cold start.
optimizely.notificationCenter.addNotificationListener(
'OPTIMIZELY_CONFIG_UPDATE',
() => {
const config = optimizely.getOptimizelyConfig();
if (config) {
cacheDatafile(config);
console.log('Datafile cache updated:', config.revision);
}
}
); from optimizely.notification_center import NotificationCenter
def on_config_update():
config = client.get_optimizely_config()
if config:
cache_datafile(config.get_datafile())
print(f'Datafile cache updated: revision {config.revision}')
client.notification_center.add_notification_listener(
'OPTIMIZELY_CONFIG_UPDATE',
on_config_update
) optimizely.NotificationCenter.AddNotification(
NotificationType.OptimizelyConfigUpdate,
() =>
{
var config = optimizely.GetOptimizelyConfig();
if (config != null)
{
DatafileCache.Save(config.GetDatafile());
Console.WriteLine($"Cache updated: revision {config.Revision}");
}
}
); Step 4: Handle stale-data scenarios
Section titled “Step 4: Handle stale-data scenarios”A cached datafile can be outdated. Consider these edge cases:
| Scenario | Impact | Mitigation |
|---|---|---|
| New flag added | decide returns enabled: false for unknown flags | Default-off is safe; the flag activates after the next poll |
| Flag archived | User gets a decision for a removed flag | Harmless; clean up references in code during maintenance |
| Variation removed | User gets an outdated variation key | Validate the variation key before acting on it |
| Cache file corrupted | SDK fails to parse | Wrap load in try/catch, fall back to network fetch |
Set a cache TTL if your environment demands freshness guarantees. Reject cached datafiles older than a threshold (e.g., 24 hours) and force a network fetch instead.
Verification checklist
Section titled “Verification checklist”- Cold start with cache serves decisions within 5ms
- Cold start without cache waits for network, then serves decisions
- Cache updates after each successful poll
- Corrupted cache falls back to network fetch without errors
- New flags added in the dashboard appear after one poll cycle