Skip to content

Conversation

@leoromanovsky
Copy link
Member

Motivation

The precomputed client needs to store configuration both in-memory for fast access and on disk for offline/startup scenarios. This PR adds the storage layer that manages this caching.

Changes

Storage:

  • PrecomputedCacheFile - Disk cache file extending BaseCacheFile with precomputed-specific naming
  • PrecomputedConfigurationStore - Thread-safe storage manager with:
    • Volatile in-memory configuration for lock-free reads
    • Async disk persistence with proper synchronization
    • Cache loading with deduplication (only one load in flight)
    • Resilient saves that update in-memory even if disk write fails

Build:

  • Updated Makefile to download precomputed test data files from sdk-test-data

Tests:

  • PrecomputedConfigurationStoreTest - Unit tests for:
    • Initial empty state
    • Set/get configuration
    • Save and load from cache
    • Disk failure resilience (in-memory still updated)
    • Missing key handling

Decisions

  • Used volatile for configuration field to ensure visibility across threads without locking on reads
  • Separate locks for cache file I/O (cacheLock) and load future management (cacheLoadLock)
  • In-memory config is updated before disk write, so failures don't block the client
  • Cache file name includes API key + subject key hash for isolation

PR Stack

This PR is part of the precomputed client feature, split for easier review:

main
├── precomputed/1-base-cache-file ⬅ (base)
│   └─┬─ 👉 precomputed/3-storage-layer (this PR)
│     │   └── precomputed/4-client
│     │       └── precomputed/5-example-app
│     │
└── precomputed/2-dtos-and-utils ──────┘
         (also required - merge before this PR)

Why this structure: The storage layer depends on both the base cache infrastructure (PR 1) and the DTOs (PR 2). It's isolated from the client implementation, making it easier to review the caching logic independently.

Merge order: PR 1 and PR 2 must be merged first, then this PR's base updated to main.

Add the foundational data structures and utilities for the precomputed
client feature:

- ObfuscationUtils: MD5 hashing for flag key obfuscation
- PrecomputedFlag: DTO for precomputed flag assignments
- PrecomputedBandit: DTO for precomputed bandit assignments
- PrecomputedConfigurationResponse: Wire protocol response parsing
- BanditResult: Result container for bandit action lookups
- MissingSubjectKeyException: Validation exception

Includes comprehensive unit tests for serialization round-trips and
MD5 hash consistency.
Base automatically changed from precomputed/1-base-cache-file to main January 30, 2026 02:13
@leoromanovsky leoromanovsky marked this pull request as ready for review January 30, 2026 02:13
Copy link

@sameerank sameerank left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty-vs-null behavior returned by loadConfigFromCache is the main one that was confusing to me. The rest are nits. Looks good overall!

return CompletableFuture.completedFuture(null);
}
return cacheLoadFuture =
CompletableFuture.supplyAsync(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to fix here, but just noting that there's a race condition due to a sync check and async read. You've handled it by ensuring that exceptions are caught so it's not dangerous.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea absolutely thanks for mentioning; I'm being kind of lazy and copying the same pattern as exists in the local-eval configuration store.

Replace Integer.toHexString() with a pre-computed hex character lookup
table for byte-to-hex conversion. This avoids creating intermediate
String objects for each byte, reducing allocations.

Mirrors optimization from iOS SDK PR #91/#93.
@leoromanovsky leoromanovsky force-pushed the precomputed/3-storage-layer branch from 8f4e45c to e80e6f9 Compare February 2, 2026 20:10
Add md5HexPrefix() method that only converts the bytes needed for a
given prefix length, avoiding unnecessary work when only a prefix is
required (e.g., cache file naming uses first 8 chars).

Includes unrolled loop for the common 4-byte (8 hex char) case to
help compiler optimization, following iOS SDK PR #93 approach.
Extract common file caching functionality from ConfigCacheFile into
a new BaseCacheFile base class. This enables reuse for the upcoming
precomputed configuration cache without code duplication.

- Add BaseCacheFile with common read/write/delete operations
- Refactor ConfigCacheFile to extend BaseCacheFile
- No functional changes to existing behavior
Add the storage layer for precomputed flag configurations:

- PrecomputedCacheFile: Disk cache file extending BaseCacheFile
- PrecomputedConfigurationStore: In-memory + disk storage with
  async save/load operations and proper thread synchronization
- Updates in-memory config even if disk write fails for resilience

Also adds test data files to Makefile for integration testing.

Includes unit tests for cache operations and failure scenarios.
@leoromanovsky leoromanovsky force-pushed the precomputed/3-storage-layer branch from e80e6f9 to dd5309f Compare February 2, 2026 20:13
- Return null consistently in loadConfigFromCache for both file-not-found
  and read-error cases
- Use static EMPTY singleton in PrecomputedConfigurationResponse.empty()
- Use Collections.singletonMap() in getEnvironment() for memory efficiency
@leoromanovsky leoromanovsky enabled auto-merge (squash) February 3, 2026 02:35
@leoromanovsky leoromanovsky merged commit e6df5f1 into main Feb 3, 2026
4 checks passed
@leoromanovsky leoromanovsky deleted the precomputed/3-storage-layer branch February 3, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants