From 213b3818e7b0e13fa5bba1a928df55a7ac2fa1a1 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Mon, 20 Jul 2026 17:07:52 -0700 Subject: [PATCH] Add a Postgres advisory-lock leader election for multi-host deploys (#229) 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. --- deploy/thermograph.env.example | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/deploy/thermograph.env.example b/deploy/thermograph.env.example index d1320c0..695ec06 100644 --- a/deploy/thermograph.env.example +++ b/deploy/thermograph.env.example @@ -49,6 +49,14 @@ THERMOGRAPH_COOKIE_SECURE=1 # not once per worker. ~200 MB RAM per worker. WORKERS=4 +# THERMOGRAPH_SINGLETON_LOCK arbitrates workers on ONE host. Under multi-host Swarm, +# each host would independently elect its own leader — multiplying the Open-Meteo +# quota use N-fold again. Set THERMOGRAPH_SINGLETON_PG=1 (with THERMOGRAPH_DATABASE_URL +# pointing at Postgres) to switch to a cluster-wide Postgres advisory lock instead, so +# exactly one host — not one per host — runs the notifier. Leave unset on a single-host +# deploy (today's default); the flock above is sufficient there. +#THERMOGRAPH_SINGLETON_PG=1 + # Base path the app is served under. # / -> app at the domain root (Thermograph owns the whole domain — # this is what the Caddyfile expects: thermograph.org proxies "/")