Persistent Cache

SDK snapshots are stored locally so applications can evaluate flags immediately after restart without waiting for network synchronization.

Introduction

SOASAP SDKs evaluate flags from an in-memory snapshot. Memory is lost when a process exits, so SDKs persist the latest successfully synchronized snapshot to local storage.

After restart, the SDK restores the last known snapshot from disk before connecting to SOASAP Cloud. Flag evaluation is available immediately — applications do not wait for the first SSE payload or an HTTP download to serve traffic.

Two distinct layers. Persistent Cache improves startup reliability and restart durability. It does not participate in the runtime evaluation path. Every getter reads from memory, never from disk.

Why persistent cache exists

Without local persistence, every cold start depends on network synchronization before configuration is available:

This model has predictable consequences:

  • Slower startup — readiness probes and health checks fail until sync completes
  • Delayed readiness — autoscaling and rolling deploys wait on network round trips
  • Network dependency at boot — hosts without connectivity start with no configuration
  • Reduced resilience during restarts — pod rescheduling, node failovers, and crashes replay the network wait

SOASAP avoids this model. The SDK loads a persisted snapshot during construction, hydrates the in-memory snapshot, and connects SSE in the background. Configuration from the last successful sync is available before synchronization completes.

Cold start lifecycle

  1. Application start — the SDK client is constructed during host or app initialization
  2. Load cache — the SDK reads the persisted JSON snapshot from local storage (when present and valid) and loads it into memory
  3. Flags available immediately — getters return values from the restored in-memory snapshot; code defaults apply for keys not in the cache
  4. Connect SSE — the background worker opens a long-lived SSE connection to SOASAP Cloud (typically started by preload)
  5. Receive updates — SSE delivers configuration events; the SDK validates and applies them atomically to memory
  6. Refresh snapshot — updated memory snapshots are debounced to disk in the background; evaluation uses memory only

Flag evaluation is available before synchronization completes. Applications can accept traffic, pass readiness checks, and evaluate flags while SSE connects and catches up in the background.

On some platforms — notably React Native with AsyncStorage — disk hydration is asynchronous. A brief window may exist where defaults apply until storage is read. See AsyncStorage Cache.

What problem does persistent cache solve?

Compare startup behavior with and without persistent cache:

Without persistent cache

With persistent cache

Persistent cache improves reliability across common production scenarios:

  • Cold starts — new processes serve the last known configuration without waiting for SSE
  • Deployments — rolling updates and blue/green cutovers restore flags before sync reconnects
  • Container restarts — Kubernetes pod rescheduling and OOM kills recover configuration from disk
  • Node failovers — replacement instances inherit behavior from persisted snapshots on shared or local volumes
  • Mobile app launches — apps render with cached flags before network connectivity is confirmed

What is stored?

The cache file contains a JSON snapshot of the SDK's bound environment — the same payload structure received over SSE. Typically this includes:

  • Flag definitions — keys, types, and metadata for flags in the environment
  • Environment values — the resolved values for each flag in the bound environment
  • Snapshot metadata — information the SDK uses to validate and apply the persisted state

The cache represents the most recent successfully synchronized and validated state — not live dashboard edits that have not yet been delivered over SSE.

The cache is not authoritative. SOASAP Cloud remains the source of truth. When synchronization succeeds, the in-memory snapshot and persisted cache are updated to reflect the control plane. When they diverge — during disconnect or before first sync — the cache may be stale relative to the dashboard.

Each API key maps to a separate cache file on server and desktop platforms. Override the storage location with platform-specific configuration — for example .WithCacheDirectory(...) on .NET or the equivalent in other SDKs.

What persistent cache is not

Persistent cache is a durability layer. It is not a general-purpose configuration store.

  • Not a database — a single JSON snapshot file per environment binding, not a queryable datastore
  • Not a source of truth — SOASAP Cloud owns authoritative configuration; the cache is a local replica
  • Not a synchronization replacement — SSE remains required to receive updates after restart; cache does not poll or push
  • Not part of the evaluation hot path — getters never open the cache file; only startup hydration and background writes touch disk
  • Not a configuration management system — operators change flags in the dashboard, not by editing cache files on disk

The cache exists solely to improve startup durability and resilience. Do not treat cache files as editable configuration, backup stores, or cross-environment transfer mechanisms.

Runtime architecture

Synchronization and evaluation flow through memory. Persistence is a side channel:

Flag evaluation uses memory only. The cache is consulted during startup (read once into memory) and during background persistence operations (write after snapshot changes). Disk access never occurs during flag evaluation.

SSE updates the memory snapshot first; debounced writes persist the snapshot to disk afterward. A slow or failed disk write does not block evaluation or synchronization.

Evaluation never reads from disk

A common misconception is that SDKs read from the cache file during evaluation. This is false.

At startup, the SDK loads the cache file into an in-memory snapshot. All subsequent getter calls read from that memory structure — the same path as Local Evaluation. Disk access is completely outside the evaluation path.

Keeping evaluation off disk provides:

  • Predictable latency — no I/O variance on the request path
  • O(1) lookups — constant-time reads from an in-memory structure
  • No blocking I/O — getters never wait on filesystem or storage APIs
  • No storage dependency — evaluation continues if disk is full, read-only, or temporarily unavailable

Cache writes

Snapshot changes are persisted in the background. When SSE delivers an update, the SDK applies it to the in-memory snapshot first, then schedules a disk write. Writes are debounced so rapid successive updates do not generate an I/O operation per event.

Debounced persistence provides:

  • Reduced I/O — coalesced writes during burst dashboard edits or bulk flag changes
  • Reduced SSD wear — fewer write cycles on container ephemeral disks and mobile storage
  • Lower resource consumption — background worker handles persistence without contending with request threads

SDKs typically use atomic file replacement — write to a temporary file, then rename — so a crash mid-write does not leave a partially written cache. Invalid persisted payloads are ignored on load; the SDK falls back to defaults and synchronizes from SOASAP Cloud.

Startup behavior

When cache exists

  • The SDK validates and restores the persisted snapshot into memory during client construction
  • Flags are available immediately from the restored snapshot
  • SSE connects in the background and applies any changes published since the cache was written
  • Health checks and readiness probes can pass before synchronization completes

When cache does not exist

  • First deploy on a new host, cleared storage, or invalid cache file — no snapshot to restore
  • Getters return explicit code defaults until the first successful SSE payload arrives
  • Synchronization creates the in-memory snapshot and persists the first cache file after validation
  • Subsequent restarts benefit from caching — the cold-start gap applies only once per storage location and API key

Enable preload so SSE starts during construction rather than on first getter. See Non-Blocking Startup.

Relationship to other architecture components

Persistent cache is one layer in SOASAP's local-first architecture. Each component addresses a distinct concern:

Component Problem solved
Persistent Cache Startup durability — restore last snapshot after restart
Local Evaluation Runtime performance — O(1) memory reads on the request path
Real-Time Synchronization Configuration freshness — SSE pushes dashboard changes to SDKs
Offline Operation Runtime resilience — evaluation continues during network outages
Default Values Safe fallback — behavior when configuration is missing or invalid

Persistent cache covers the restart gap. Offline operation covers the runtime disconnect gap. Default values cover the gap when no snapshot or key exists. Together they keep applications operational without coupling flag reads to network availability.

Failure scenarios

Cache missing

No persisted file exists — first start, cleared volume, or new API key on the host. The SDK starts with an empty in-memory snapshot. Getters return code defaults. SSE synchronizes normally and creates the first cache file after a valid payload is received.

Cache corrupted

Invalid JSON, truncated file, or failed validation. The SDK ignores the cache file, reports the error through OnError / onError with a disk/parser source, and restores state from SSE. Evaluation uses defaults until the first successful sync.

Cache write failure

Disk full, permission denied, or read-only filesystem. Evaluation continues from the in-memory snapshot. Synchronization continues over SSE. Only persistence is affected — the process runs in memory-only mode until disk writes succeed again.

First application start

No cache exists yet on a new machine or environment binding. Defaults are used until synchronization completes and the first snapshot is persisted. This is expected behavior, not an error condition — plan explicit safe defaults for first-start scenarios.

See Cache Not Restored.

Common misconceptions

"The SDK reads from disk on every evaluation"

False. Disk is read once at startup (or asynchronously on some mobile platforms). Evaluation uses the in-memory snapshot only.

"The cache is the source of truth"

False. SOASAP Cloud is the source of truth. The cache is a local replica of the last successful sync, potentially stale during disconnect.

"Applications cannot start without synchronization"

False. Cache restoration — or code defaults when no cache exists — allows immediate evaluation. Synchronization runs in the background.

"Cache failures stop feature flags"

False. Evaluation continues using the in-memory snapshot and explicit defaults. Cache failures affect durability across restarts, not runtime flag reads.

Production recommendations

  • Enable persistent caching — use default SDK cache locations or configure a writable path; do not disable persistence unless you accept first-start-only defaults on every restart.
  • Keep cache storage writable — ensure container volumes, ephemeral disks, and mobile app storage permissions allow read/write access to the configured cache directory.
  • Always provide explicit defaults — cache may be missing, corrupt, or stale; defaults define safe behavior on first start and after extended disconnect.
  • Test cold-start behavior — verify readiness probes, deploy rollouts, and pod restarts serve expected flag values before SSE reconnects; test with and without an existing cache file.
  • Monitor synchronization health — cache age indicates last successful sync, not live dashboard state; alert on prolonged SSE disconnect independently of application HTTP health.
  • Treat cache as a durability layer, not a configuration source — change flags in the dashboard; never edit cache files manually or use them for environment promotion.

Related documentation