Thermograph monorepo: graded-climate API + SSR frontend + infra, domain-specific containerized deploys
Find a file
Emi Griffith 1b4a607945 Split web/worker duties with THERMOGRAPH_ROLE (#230)
* Add a Postgres advisory-lock leader election for multi-host deploys

The subscription notifier elects one leader via a host-local flock
(THERMOGRAPH_SINGLETON_LOCK) so multiple uvicorn workers on one host don't
each run it. Under multi-host Swarm that guard is insufficient: each host
would independently elect its own leader, multiplying Open-Meteo quota use
N-fold again.

Add claim_pg(key) alongside the existing claim(lock_path): a cluster-wide
Postgres advisory lock, visible to every host talking to the same database.
The holding connection is dedicated and kept for the process lifetime
(advisory locks are session-scoped); a dead connection is dropped and
re-election retried on the next call.

claim_leader() dispatches between the two mechanisms from env:
THERMOGRAPH_SINGLETON_PG (+ Postgres) -> claim_pg; else
THERMOGRAPH_SINGLETON_LOCK -> claim; else always leader, unchanged. Wired in
at web/app.py in place of the direct claim() call. Off by default, so
today's single-host behavior is unaffected.

* Split web/worker duties with THERMOGRAPH_ROLE

Background work (the subscription notifier) is welded to the same process
that serves requests, so scaling the web tier to N replicas would also scale
notifier instances unless something restricts it further than leader
election alone.

Add THERMOGRAPH_ROLE (web|worker|all, default all - unchanged single-process
behavior). Every replica runs the same image; ROLE only gates whether a
process is allowed to own the notifier at all, layered on top of the
existing leader election: web replicas never start it even if they'd win
leader election, worker replicas start it if they win. The decision is
pulled into _should_run_notifier() so it's unit-testable without booting the
full app (DB init, places index, neighbor warmer).

Add a minimal /healthz liveness route (no DB/upstream I/O, not under BASE)
so a worker replica - which serves no real traffic - still has something
Swarm can health-check.

* Retrigger CI (no prior check run was ever recorded for this PR)
2026-07-21 01:23:24 +00:00
accounts Containerize the app and move the databases to PostgreSQL 18 (#220) 2026-07-20 06:28:23 +00:00
alembic Move the climate record from parquet to TimescaleDB hypertables (#227) 2026-07-20 20:15:55 +00:00
core Add a Postgres advisory-lock leader election for multi-host deploys (#229) 2026-07-21 00:07:52 +00:00
data Move the climate record from parquet to TimescaleDB hypertables (#227) 2026-07-20 20:15:55 +00:00
deploy Add entrypoint one-shot migrate mode and a Swarm secrets shim (#231) 2026-07-21 00:18:05 +00:00
notifications Split the backend into domain packages (#217) 2026-07-20 05:31:03 +00:00
templates Fix the footer leaking "wrapper. #}" from a broken Jinja comment (#211) 2026-07-20 04:14:56 +00:00
tests Extract the glossary and static-page SEO copy into a schema-validated content/ tree (#241) 2026-07-21 01:21:11 +00:00
web Extract the glossary and static-page SEO copy into a schema-validated content/ tree (#241) 2026-07-21 01:21:11 +00:00
alembic.ini Containerize the app and move the databases to PostgreSQL 18 (#220) 2026-07-20 06:28:23 +00:00
app.py Split the backend into domain packages (#217) 2026-07-20 05:31:03 +00:00
cities.json SEO: values filter — trim cities in anti-LGBTQ countries, keep notable hubs (#102) 2026-07-16 02:20:41 +00:00
cities_flavor.json SEO: values filter — trim cities in anti-LGBTQ countries, keep notable hubs (#102) 2026-07-16 02:20:41 +00:00
gen_cities.py Split the backend into domain packages (#217) 2026-07-20 05:31:03 +00:00
gen_flavor.py Split the backend into domain packages (#217) 2026-07-20 05:31:03 +00:00
indexnow.py Split the backend into domain packages (#217) 2026-07-20 05:31:03 +00:00
migrate.py Split the backend into domain packages (#217) 2026-07-20 05:31:03 +00:00
migrate_accounts_to_pg.py Containerize the app and move the databases to PostgreSQL 18 (#220) 2026-07-20 06:28:23 +00:00
migrate_cache_to_pg.py Move the climate record from parquet to TimescaleDB hypertables (#227) 2026-07-20 20:15:55 +00:00
paths.py Extract the glossary and static-page SEO copy into a schema-validated content/ tree (#241) 2026-07-21 01:21:11 +00:00
requirements-dev.txt Add backend test suite; gate direct pushes; serialize LAN deploys (#41) 2026-07-11 19:37:49 +00:00
requirements.txt Extract the glossary and static-page SEO copy into a schema-validated content/ tree (#241) 2026-07-21 01:21:11 +00:00
warm_cities.py Split the backend into domain packages (#217) 2026-07-20 05:31:03 +00:00