Cache platform settings in Redis#20
Merged
Merged
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
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.
Cache platform settings in Redis (read-heavy, write-invalidate)
Summary
Platform settings are read on essentially every UUID-scoped request (the game-language fallback, the gameboard page's ~8 setting reads, the polled JSON feeds), and each read was a live
SELECT … WHERE name=? AND uuid=?with no caching. Settings change only via admin writes. This adds a two-layer cache-aside toSettingsManagerso reads are served from memory and writes invalidate, finally putting the already-wired-but-unused Redis connection to work.Why
SettingsManager.Getran a DB query per call; the gameboard render alone issued ~8 of them, and the JSON feeds several more — all for data that rarely changes.HandlersMap.RedisCachewas connected but never used for data (only the SCS session store used Redis).What changes
pkg/settings/cache.go: a per-UUID, two-layer cache-aside.sync.Mapholding the full settings set per UUID, 2s TTL — coalesces the burst of reads within a single request.mapctf:settings:<uuid>, 60s TTL — shared across instances, survives request bursts.singleflightper UUID key so a cache miss under concurrent requests does one DB load, not a thundering herd.SetCache(client): enables caching;nilenables L1-only (network-free). Cache errors never fail a read — any miss/error falls back to the DB.pkg/settings/settings.go:Get/GetAllare now served from the cache;getAllFromDB/loadFromDBare the uncached DB reads used to populate it.Getpreservesgorm.ErrRecordNotFoundsemantics, so existing callers (includingupsertSetting's existence check) are unaffected.Save,Create, andChangecallinvalidate(uuid)(drops L1 + Redis). Every settings write path (adminSetX, reset-to-defaults, import) goes throughupsertSetting→Save/Create, so a changed setting is visible on the next read.cmd/map/main.go:settingsMgr.SetCache(redis.Client)after manager creation (and beforeInitialization, which seeds viaCreate→ invalidate, a no-op on the empty cache).go.mod:golang.org/x/syncpromoted to a direct require (forsingleflight).pkg/settings/settings_test.go:TestSettingsCacheServesReadsAndInvalidatesOnWrite— proves a cached read does not reflect a DB write that bypasses the manager, that explicitinvalidaterefreshes, and that a manager write invalidates so the new value is visible immediately. Uses L1-only to stay network-free; the Redis L2 path uses the same load/invalidate logic.How it works (resolution)
Get(name, uuid)→loadCached(uuid):singleflight→ L2 RedisGET→ on hit, populate L1 and return.getAllFromDB→ populate L2 (if enabled) + L1 → return.Save/Create/Change→invalidate(uuid)→ delete L1 entry +DELRedis key.Behavior preserved
Getstill returnsgorm.ErrRecordNotFoundfor missing settings.GetAllstill returns the ordered slice.SetCachenever called, e.g. existing tests), every read goes straight to the DB — no behavior change, no staleness.Getresult is safe: map access returns a copy, soupsertSetting's in-place mutation of the returned setting cannot corrupt the cache.Performance
Getcalls in one request → 1 DB/Redis load + 7 in-memory hits.singleflight.Risks / edge cases
invalidateis called. All real write paths go through the manager, so this only matters for direct-DB maintenance.[]PlatformSetting(includinggorm.Modelfields) is JSON-marshaled to Redis; callers only read value fields, so cached IDs/timestamps are not relied upon beyondSave's use of the ID (which comes from the returned copy).Testing
TestSettingsCacheServesReadsAndInvalidatesOnWrite.pkg/settingsandcmd/map/handlerssuites still pass (handlers run with caching disabled in tests, so behavior is unchanged).go vet,gofmtclean.Rollout
Rebuild/restart the service so
SetCache(redis.Client)takes effect. No schema change, no config change. The cache is enabled automatically when Redis is configured (as it already is for sessions).