-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add HTTP ETag-based caching for configuration fetches #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
typotter
wants to merge
8
commits into
main
Choose a base branch
from
typo/etag
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+854
−332
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Create public interfaces (I* prefix) for all DTOs in cloud.eppo.api package: - IFlagConfig, IFlagConfigResponse, IAllocation, ISplit, IShard, IShardRange - ITargetingRule, ITargetingCondition, IVariation - IBanditParameters, IBanditParametersResponse, IBanditReference - IBanditModelData, IBanditCoefficients, IBanditFlagVariation - IBanditAttributeCoefficients, IBanditNumericAttributeCoefficients, IBanditCategoricalAttributeCoefficients - IEppoValue Update DTOs to implement interfaces: - All DTOs in cloud.eppo.ufc.dto now implement their corresponding interfaces - EppoValue implements IEppoValue - ShardRange implements IShardRange Update consumers to use interface types: - Configuration stores and returns interface types (Map<String, ? extends IFlagConfig>) - FlagEvaluator, RuleEvaluator, BanditEvaluator use interface types - BaseEppoClient uses IFlagConfig, IBanditParameters This enables downstream SDKs (Android, KMP) to provide custom DTO implementations while maintaining type safety and compatibility with the common SDK.
…ents Remove EppoValue overload methods - use IEppoValue consistently. Breaking change acceptable since targeting feature/v4.
…interface Delete internal BanditAttributeCoefficients interface - use IBanditAttributeCoefficients everywhere. Concrete classes now only implement the public interface. Breaking change acceptable since targeting feature/v4.
- FlagEvaluationResult now uses IVariation throughout - Remove concrete Variation from public API - Remove wildcard imports of cloud.eppo.ufc.dto - Only import specific types needed (VariationType enum) - Breaking change: getVariation() returns IVariation instead of Variation
Implement HTTP caching using ETags to reduce bandwidth and processing overhead. Changes: - Add flagsETag field to Configuration for storing current eTag - Create EppoHttpResponse wrapper class (status code, body, eTag) - Update EppoHttpClient to accept If-None-Match header and return EppoHttpResponse - Update ConfigurationRequestor to handle 304 Not Modified responses - Early exit on 304 - skip JSON parsing, bandit fetches, and callbacks Benefits: - ~80% bandwidth reduction (assuming 80% cache hit rate on polling) - Reduced CPU usage - skip parsing and processing when config unchanged - Fewer unnecessary bandit parameter fetches - Reduced callback notifications Implementation: - Get previous eTag from Configuration.getFlagsETag() - Include If-None-Match header in HTTP request if eTag exists - Server returns 304 if config hasn't changed - ConfigurationRequestor early returns (no processing) on 304 - Server returns 200 with new eTag if config changed - New eTag stored in Configuration for next request Tests: - Updated all test mocks to return EppoHttpResponse - Added null safety for empty configurations - All 249 tests passing
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
ETag-Based Caching Implementation for Configuration Fetches
Eppo Internal:
🎟️ Fixes [issue number if applicable]
📜 Design Doc: ETAG_IMPLEMENTATION_PLAN.md
Motivation and Context
The SDK currently fetches full configuration data from the server on every poll cycle (default: every 30 seconds), even when the configuration hasn't changed. This results in:
For applications with frequent polling or many SDK instances, this overhead compounds significantly. HTTP ETags provide a standard mechanism for conditional requests that can eliminate this waste when configuration data hasn't changed.
Description
This PR implements HTTP ETag-based caching for configuration fetches using conditional requests (If-None-Match header / 304 Not Modified responses).
Architecture
ETag Storage:
flagsETagfield toConfigurationclass (stored atomically alongside configuration data)ConfigurationStoreemptyConfig()HTTP Layer Changes:
EppoHttpResponseclass to wrap HTTP responses (status code, body, ETag header)EppoHttpClientto:ifNoneMatchparameter for conditional requestsETagheader from responsesEppoHttpResponseinstead of rawbyte[]Request Flow:
If-None-Match: {eTag}header in request (if eTag available)Optimization Details:
Performance Impact
Bandwidth Savings (304 response):
CPU Savings (304 response):
Latency Improvements:
Key Design Decisions
Always Enabled: ETag caching is always on (no configuration toggle). This provides immediate benefits without added complexity.
In-Memory Only: ETag stored only in memory (not persisted to disk). First fetch after SDK restart will always be full.
Minimal Logging: Follows existing codebase style with terse logging. No verbose debug logs for routine 304 responses.
Flags-Only ETag: Only the flags endpoint uses conditional requests. Bandits optimization is achieved implicitly (flags unchanged → bandits skipped).
Files Modified
Production Code:
src/main/java/cloud/eppo/EppoHttpResponse.java- NEW - HTTP response wrappersrc/main/java/cloud/eppo/api/Configuration.java- AddedflagsETagfield and methodssrc/main/java/cloud/eppo/EppoHttpClient.java- ReturnsEppoHttpResponse, handles ETagssrc/main/java/cloud/eppo/ConfigurationRequestor.java- Handles 304 responses with early returnTest Code:
src/test/java/cloud/eppo/helpers/TestUtils.java- Updated mocks for new signaturessrc/test/java/cloud/eppo/ConfigurationRequestorTest.java- Updated forEppoHttpResponsesrc/test/java/cloud/eppo/api/ConfigurationBuilderTest.java- Updated constructor callssrc/test/java/cloud/eppo/BaseEppoClientTest.java- Updated mocksBreaking Changes
Public API: ✅ None - All public SDK APIs remain unchanged
Internal APIs:⚠️ Changed (acceptable for internal classes)
EppoHttpClient.get()andgetAsync()now returnEppoHttpResponseinstead ofbyte[]Configurationconstructor signature changed (addedflagsETagparameter)How has this been documented?
Code Documentation:
EppoHttpResponseclass methodsWhy No User-Facing Documentation:
Internal Documentation:
How has this been tested?
Automated Testing
✅ 161/161 tests passing (100% pass rate)
Test Coverage:
Unit Tests:
emptyConfig()has null eTag (automatic clearing)EppoHttpResponsecorrectly detects 304 statusequals()andhashCode()include eTag fieldIntegration Tests:
If-None-Matchheader when eTag providedETagheader from responsesError Handling:
Backward Compatibility:
Manual Testing Checklist
Build Verification
Thread Safety
volatileConfiguration storage)flagsETagfield (no synchronization needed for reads)synchronizedblocks protect concurrent updatesAdditional Notes