feat: add HTTP ETag-based caching for configuration fetches #172
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