Effort: deep
api.py has six Cache-Control: public, max-age=60, callsites (
stale-while-revalidate=300api.py:60449 etc.) but
they're scattered, copy-pasted, and don't share an invalidation
strategy. With nginx in front of FastAPI we can let nginx (or a
future Cloudflare layer) cache for ~5 minutes while serving stale
content for another 5, dropping origin load on hot reads by ~10x.
Define a "public read view" abstraction that any GET handler can
opt into, paired with cache-purge on the artifact mutation path.
api_shared/public_view.py:@public_view(ttl=300, swr=300, surrogate_key="hyp:{id}")Cache-Control: public, max-age=<ttl>, stale-while-
revalidate=<swr>, sets Surrogate-Key: hyp:{id} (Fastly/version (q-perf-etag-Cookie or Authorization set (auto-private).
/api/hypotheses, /api/papers/{pmid},/api/wiki/{slug}, /api/proposals/feed, /api/markets/
summary, /api/external-citations/recent (latter fromdb_writes.py (artifact-mutatingSurrogate-Keys on commit andpurge_queue.
deploy/nginx/cache.conf addsproxy_cache_path /var/cache/nginx/scidex levels=1:2
keys_zone=scidex:64m max_size=2g inactive=1h
use_temp_path=off; and a proxy_cache scidex block onproxy_cache_use_stale
updating http_502 http_503;.
scripts/cache_purge_worker.py:purge_queue, issues PURGE against nginx (orscidex_cache_status{result="hit|miss|stale|expired"}logs/parse_cache_status.py helper) renders on the senateX-Cache-Status: HIT on second.
wrk -c200 -d60s against/api/hypotheses shows ≥85 % nginx cache-hit rate andNGINX_SCIDEX_CACHE=1.q-perf-etag-smart-invalidation — supplies stable ETags.q-perf-selective-mat-views — routes that read MVs areSurrogate-Key design.Implemented all acceptance criteria:
api_shared/purge_queue.py — zero-dep in-memory deque with push() /drain() / size(). Thread-safe, maxlen=10 000 to bound memory.api_shared/public_view.py — @public_view(ttl, swr, surrogate_key)__pv_request: Request into the wrapped function's__signature__ so FastAPI supplies the live Request object. SetsCache-Control: public, max-age=<ttl>, stale-while-revalidate=<swr>,Surrogate-Key (with {param} interpolation from route kwargs), and aAuthorizationCookie is present. Handles both dict-returning and Response-returningscidex/core/db_writes.py — added _push_purge_key() helper (lazy import,save_wiki_page, create_wiki_page, upsert_wiki_pagewiki:{slug} after every successful write.deploy/nginx/cache.conf — proxy_cache_path /var/cache/nginx/scidexproxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504,proxy_cache_background_update on, add_header X-Cache-Status $upstream_cache_status,scripts/cache_purge_worker.py — daemon and library. Drains purge queue,--dry-run flag for dev.logs/parse_cache_status.py — parses JSON or combined-format nginx accessscidex_cache_status{result="…"}) for node_exporterapi.py — imported public_view; applied decorator on:/api/hypotheses (hypotheses:list, ttl=300 swr=300)/api/wiki/{slug} (wiki:{slug}, ttl=300 swr=300)GET /api/papers/{pmid} (paper:{pmid})GET /api/proposals/feed (proposals:feed)GET /api/markets/summary (markets:summary)GET /api/external-citations/recent (ext-citations:recent)tests/test_public_view.py — 18 unit tests: Cache-Control values,Not done (out of scope / blocked on dependencies):
wrk (needs live nginx)version column — uses body SHA-256 instead, pendingq-perf-etag-smart-invalidation task