From ecf879fd1b6e147db2c25ccade9709ebe7a3f0b8 Mon Sep 17 00:00:00 2001 From: jay Date: Fri, 12 Jun 2026 09:40:57 -0400 Subject: [PATCH] Perf: parallelize admin loads + edge-cache /api/brief MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two concrete latency wins found by measuring (server compute is 2-17ms; the time is in the path, not the box): - Admin panel fired its 6 API calls SEQUENTIALLY (await chain) — so it paid the uncached origin round-trip six times back-to-back. Now one Promise.all batch. This is the admin lag. - /api/brief (the home "Gathering the good news…" content) wasn't edge-cached, so a distant anonymous visitor triggered a Cloudflare→residential-origin pull. Same global/shareable boundary as /api/feed: public s-maxage=45 when no prefs/exclude, else private,no-store. (Needs /api/brief added to the CF cache rule path list to take effect at the edge.) Tests: test_brief_cache_boundary. 228 pytest + 11 vitest. Co-Authored-By: Claude Opus 4.8 --- frontend/src/routes/admin/+page.svelte | 22 ++++++++++++++++------ goodnews/api.py | 6 ++++++ tests/test_api.py | 7 +++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte index a2acab4..61f45c5 100644 --- a/frontend/src/routes/admin/+page.svelte +++ b/frontend/src/routes/admin/+page.svelte @@ -28,12 +28,22 @@ return; } try { - await loadStats(); - feedback = await getJSON('/api/admin/feedback'); - candidates = await getJSON('/api/admin/candidates'); - wpPool = await getJSON('/api/admin/word/pool'); - clientErrors = await getJSON('/api/admin/client-errors'); - wsThemes = await getJSON('/api/admin/wordsearch/themes'); + // Load all panels in PARALLEL — these were sequential awaits, so the page + // paid the (uncached, origin) round-trip six times back-to-back. One batch + // instead of a chain. + const [, fb, cand, wp, ce, ws] = await Promise.all([ + loadStats(), + getJSON('/api/admin/feedback'), + getJSON('/api/admin/candidates'), + getJSON('/api/admin/word/pool'), + getJSON('/api/admin/client-errors'), + getJSON('/api/admin/wordsearch/themes'), + ]); + feedback = fb; + candidates = cand; + wpPool = wp; + clientErrors = ce; + wsThemes = ws; } catch { error = "Couldn't load stats."; } diff --git a/goodnews/api.py b/goodnews/api.py index 954edab..959153b 100644 --- a/goodnews/api.py +++ b/goodnews/api.py @@ -1676,11 +1676,17 @@ def create_app() -> FastAPI: @app.get("/api/brief", response_model=BriefResponse) def brief( + response: Response, date: str | None = Query(None), limit: int = Query(10, ge=1, le=50), prefs: str | None = Query(None), exclude: str = Query("", description="comma-separated article ids the reader has dismissed"), ) -> BriefResponse: + # The default highlights are global (date-keyed, no session) → edge-cacheable + # so a new visitor's "Gathering the good news…" resolves from their POP, not + # a pull to the residential origin. Personal filters stay private. + shareable = not prefs and not exclude.strip() + response.headers["Cache-Control"] = _EDGE_FEED if shareable else _PRIVATE fp = prefs_from_json(prefs) now = datetime.now(timezone.utc) excl = {int(x) for x in exclude.split(",") if x.strip().lstrip("-").isdigit()} diff --git a/tests/test_api.py b/tests/test_api.py index f977e3b..ade11db 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -107,3 +107,10 @@ def test_feed_cache_boundary(client): assert client.get("/api/feed", params={"following": "true"}).headers.get("cache-control") == "private, no-store" assert client.get("/api/feed", params={"prefs": json.dumps({"mute_topics": ["science"]})}).headers.get("cache-control") == "private, no-store" assert client.get("/api/feed", params={"exclude": "1,2"}).headers.get("cache-control") == "private, no-store" + + +def test_brief_cache_boundary(client): + # Default highlights are global → public; personal filters → private. + assert "public" in client.get("/api/brief").headers.get("cache-control", "") + assert client.get("/api/brief", params={"prefs": json.dumps({"mute_topics": ["health"]})}).headers.get("cache-control") == "private, no-store" + assert client.get("/api/brief", params={"exclude": "3"}).headers.get("cache-control") == "private, no-store"