fix(cache): one versioned cache model with ETags and stored gzip - #50
Merged
Merged
Conversation
Replace the five invalidation schemes with versioned namespaces:
CacheManager.respond() caches rendered responses under
{ns}[:{scope}]:v{version}:{endpoint}:{digest}, and invalidate() bumps a
TTL-less version counter. No more delete_pattern/SCAN, no raw request
input in keys, and the endpoint name keeps planet retrieve and multiple
apart (they collided before).
- entries are stored as (weak ETag, gzip level 9 bytes), sent as is to
gzip clients and decompressed otherwise
- Cache-Control: public, max-age=60 or private, no-cache;
ConditionalGetMiddleware answers 304; responses without a header
default to private, no-cache
- CX webhook updates, snapshot runs and all importers bump their namespace
- planets multiple/search/<term> and planet insights are no longer cached
- planet entries live 1 h (active COGC program is time-dependent)
- cache_ns/cache_hit land in the request_finished log line
- dashboard: "Keyspace hit rate (all Redis)"
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | -53 |
| Duplication | 0 |
🟢 Coverage 100.00% diff coverage · -0.01% coverage variation
Metric Results Coverage variation ✅ -0.01% coverage variation (-1.00%) Diff coverage ✅ 100.00% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (156ba09) 3855 3623 93.98% Head commit (5282b92) 3746 (-109) 3520 (-103) 93.97% (-0.01%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#50) 164 164 100.00% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
…cope Bandit B101: asserts vanish under python -O, and a missing scope would then share one user's private entry with everyone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
Replaces the five cache invalidation schemes with a single versioned-namespace model, and makes HTTP caching honest.
CacheManager.respond(request, NS, endpoint, *parts, build=…, scope=…)stores rendered responses under{ns}[:{scope}]:v{version}:{endpoint}:{digest}. To invalidate,CacheManager.invalidate(NS, scope)bumps a version counter that has no TTL.delete_pattern/SCAN is gone.planets/multipleno longer collide, because the endpoint name is in the key.ETag, andIf-None-Matchgets a304.public, max-age=60; they used to send up to 86400. Private responses sendprivate, no-cache.Cache-Controldefaults toprivate, no-cache.planets/multiple,planets/search,planets/<term>andplanet_insightsare no longer cached. They were cheap cold and had low hit rates.cache_nsandcache_hitare added to therequest_finishedlog line. The dashboard label now reads "Keyspace hit rate (all Redis)".Spec:
specs/2026-09-27-cache-structure.md(workspace repo).Response bodies and the OpenAPI schema are unchanged; the schema is byte-identical to
main. Only headers change, so this is backward compatible.Deploy order
Deploy this PR first. The paired frontend PR PRUNplanner/frontend#505 must only ship once this is live.
After deploy (optional):
redis-cli --scan --pattern ':1:PLANNING:*:version' | xargs redis-cli delused_memoryagainst the ~50 MB baseline.Old cache keys expire within their TTL (at most 1 d), so no flush is needed.
Perf
Run with
perf/run.sh --mode quickat small scale, both sides using a gzip-accepting bench client.Memory (
MEMORY USAGEin Redis):Warm latency (median, main → this PR):
data.planetdata.planetis the one regression: the extra version read costs ~0.5 ms, and its 724 B body is too small for gzip to win that back.Cold builds are slower because of gzip level 9: exchanges +13 ms, planets +235 ms.
Not changed: Redis
maxmemoryflags indocker-compose.yml. Production sets its own.Test plan
uv run ruff check,ruff format --check,ty checkuv run pytest(316 passed). New tests:cache_ns/cache_hit.curlexchanges twice withIf-None-Matchand check for a304.🤖 Generated with Claude Code