FS-997: a cache that reloads when a Redis version key moves - #253
Draft
aaron-wego wants to merge 5 commits into
Draft
aaron-wego wants to merge 5 commits into
aaron-wego wants to merge 5 commits into
Conversation
Task 1 of the breaker-config version gate plan: module skeleton and the no-version-key path. Cache[T] holds one dataset in memory and reloads it every checkEvery when there is no version key to check, or the key is absent in Redis. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehc4hP7q1fM6jxKQrkQbUJ
- add five tests for the version gate: unchanged version avoids reload, changed version reloads, any different version reloads (including backwards), missing version key doesn't fail, and the three refresh outcomes are distinguishable - add exampleVersionKey constant for use across the new tests Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehc4hP7q1fM6jxKQrkQbUJ
- Add errors import for test error creation - Append TestFailedVersionReadKeepsTheCachedData to verify warm cache persists through Redis outage - Append TestFailedDataReadKeepsTheCachedData to verify warm cache persists through data load failure - Append TestFirstLoadFailureIsReturnedToTheCaller to verify first load failure is reported to caller Co-Authored-By: Claude <noreply@anthropic.com>
- Add TestConcurrentGetsCauseOneDataRead exercising the compare-and-swap single-flight path: a blocked second load, four concurrent callers served the cached value, then the refresh completing - Add sync/atomic to the test file's import block, in gofmt order - Add versionedcache/README.md with the Use example, the empty-version-key note, and the two behaviors that surprise people Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehc4hP7q1fM6jxKQrkQbUJ
- Adds NewWithVersionInHashField beside New. New keeps its GET on a whole key; the new constructor HGETs one field, so a writer that publishes versions for many namespaces into one hash needs no adapting on the reader side. - Naming no field keeps the old GET path, and naming a field but no key is treated as naming no version at all, so a caller cannot half-configure itself into reading the wrong place. A failed read now says where it was reading - the key, and the field when there is one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehc4hP7q1fM6jxKQrkQbUJ
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
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.
A new module,
github.com/wego/pkg/versionedcache.Holds a dataset from Redis in memory and reloads it only when a small version key next to that dataset says the data changed. A cycle that finds the version unchanged costs one
GETof a short string instead of reading the whole dataset.This is the Go equivalent of what FS-987 built for the Java services. The first consumer is flight-cerberus (FS-999), which today re-reads its whole breaker config hash every 5 minutes and cannot check more often without multiplying reads on the shared static-data Redis that FS-980 exists to take load off.
Notes for review
Getreturns an error only when it has never loaded. After a first success, a failed read of either the version or the data keeps the previous value andGetreturns it with a nil error; the failure reaches the caller throughOptions.WhenRefreshed. A Redis outage therefore does not empty a warm cache.go-redisis pinned at v9.7.3, the version flight-cerberus already uses, so consuming this module does not force an upgrade. Please keep it there.Options.WhenRefreshedreports what each cycle did and lets the calling service emit its own metric, which is what keeps observability libraries out ofwego/pkg.Optionsstruct rather than functional options: Go cannot infer the type parameter for a standalone generic option, soWhenRefreshed[T](...)would make every call site spell the type out.Testing
go test ./... -race -coverinversionedcache/, againstminiredis— 12 tests, 95.5% coverage. Covers:-raceruns)Release
Not tagged yet. Once this merges,
./auto_versiononmaintagsversionedcache/v0.1.0, which is what FS-999 pins.FS-997
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ehc4hP7q1fM6jxKQrkQbUJ