Add cheap, stable content-page cache token #47
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Jinemi/thermograph#47
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/content-token"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Content SEO pages (
/climate/<city>[/month|/records]) key their derived-payload cache validity onhistory_token(history) = PAYLOAD_VER:hist_end, computed from the fully-loaded ~45-year archive. Two costs fall out of that:hist_end(an intra-day timestamp change), so the entire content cache for a cell invalidates ~1-2x/day, forcing expensive recomputes.Change (Fix 1 of 3)
Adds a new token that content routes and the warm job (other agents' scope) will use instead of
history_token:payloads.CONTENT_VER = "c1"— a separate content-shape version, so a content-only change need not orphan every other cache kind.payloads.content_token(cell_id) -> strreturningf"{PAYLOAD_VER}:{CONTENT_VER}:{max_date or 'none'}".climate_store.history_max_date(cell_id) -> str | None— indexedMAX(date)over the(cell_id, date)primary key (confirmed in migration0002_climate_hypertables), never a full-history load.climate.history_max_date(cell_id) -> str | None— backend dispatcher: Postgres path delegates to the store; the dev/parquet path does a single-columnscan_parquet(...).select(col("date").max()), not a full frame load.The token keys on the archive's newest date, so it survives intra-day top-ups and turns over only when the last archived day genuinely advances (~1x/day) — keeping content pages ≤1 day stale, acceptable for SEO. Fail-soft throughout: a store/DB error buckets to
'none'rather than raising.Scope kept to
backend/api/payloads.py,backend/data/climate_store.py,backend/data/climate.py(+ tests).content_routes.py/warm_cities.py/scheduler.pyuntouched — owned by the other fixes.Tests
New unit tests: parquet + gated-Postgres
history_max_dateround-trips (including cell isolation and tail-topup advance), andcontent_tokenstability (same max date -> identical token across simulated top-ups; changed max date -> new token) plus fail-soft bucketing to'none'. Suite green (the 2 unrelatedtest_lake_appfailures are a missingduckdbin the dev venv, pre-existing).Content SEO pages keyed derived-payload cache validity on history_token = PAYLOAD_VER:hist_end, computed from the fully-loaded ~45-year archive. Every hourly tail top-up advanced hist_end and invalidated the whole content cache for a cell, and computing the token at all required loading the full history first — so even cache hits paid the full load. Add content_token(cell_id) = PAYLOAD_VER:CONTENT_VER:max_date, keyed on the archive's newest DATE read cheaply without loading history: climate_store.history_max_date does an indexed MAX(date) over the (cell_id, date) PK on Postgres; climate.history_max_date dispatches to it or to a single-column scan of the cached parquet on the dev backend. The token survives intra-day top-ups and turns over only when the last archived day advances (~1x/day), keeping content pages <=1 day stale. Fail-soft: a store/DB error buckets to 'none' rather than raising. CONTENT_VER ("c1") is a separate content-shape version so a content-only change need not orphan every other kind's cache.