Sync ORM index metadata with DB, add partial unique index for dashboard dedup rows #57
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#57
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Six live indexes exist only in migrations, not models (sent_alerts
location_id/nws_alert_id/expires, locations nws_state/enabled, sent_forecasts
sent_date) — autogenerate would propose dropping them. Add
index=Truetomatch (no schema change). The sent_alerts unique constraint includes nullable
channel_id, so dashboard rows have no DB-enforced dedup (Postgres NULL
semantics); the SELECT-then-INSERT check (alert_processor.py:584-591) is
unprotected. Add
CREATE UNIQUE INDEX ... ON sent_alerts (nws_alert_id, location_id) WHERE channel_id IS NULLand a composite(location_id, channel_id)index for the dedup hot path. Optionally drop the redundantstandalone indexes duplicating unique-constraint prefixes, and add an env.py
comment noting the ALTER TYPE ADD VALUE single-transaction pitfall (0007) for
future migration authors.
Acceptance criteria:
alembic revision --autogenerateon a clean head produces an empty diffFiled from the 2026-07-17 codebase audit (
docs/.internal/report-2026-07-17.md), finding F-18.Done in #107 (merged). Verified against a live-schema Postgres on the dev server:
alembic revision --autogeneratenow yields a genuinely empty diff, migration 0024 applies onupgrade head, bulk suite 658 passed, postgres tier 4 passed.Scope turned out broader than the six enumerated indexes — autogenerate flagged mismatches on six further tables, all reconciled (model-metadata-only, no schema change):
ix_notification_deliveries_due).uq_*constraint + non-uniqueix_*index the DB actually has).uq_sent_alerts_dashboard_dedup (nws_alert_id, location_id) WHERE channel_id IS NULL+(location_id, channel_id)hot-path index; new postgres-tier test asserts it rejects a duplicate dashboard row.alembic/script.py.makowas never committed, soalembic revisionfailed on any fresh clone — that's what blocked the empty-diff check; added the standard template. env.py now notes the ALTER TYPE ADD VALUE single-transaction pitfall.Follow-up noted (not blocking): the SELECT-then-insert dashboard dedup is race-safe today under the scheduler's
max_instances=1, so the new index is defensive; if WeatherBot ever runs multiple app instances or a concurrent injector, add a savepoint-basedIntegrityErrorhandler around the insert so a race degrades gracefully instead of raising.⚠️ Deploy follow-up (fixed in #109): the initial #107 deploy to dev failed and rolled back — migration 0024's partial UNIQUE index couldn't be built because the dev DB already held 18 groups of duplicate
channel_id=NULLSPC dashboard rows (up to 147 copies each). Root cause:poll_spcandpoll_spc_dashboard(two independent 5-min jobs) race the same SELECT-then-INSERT for a dashboard record. The original PR's empty-throwaway-DB test didn't surface this.#109 fixes it: migration 0024 now de-duplicates (keep newest per group) before creating the index, and both NULL-dashboard insert sites are now savepoint-guarded so a lost race is handled as "already exists" instead of poisoning the poll transaction. Verified end-to-end: deployed to dev (
v1.3.0-5-g98fbfd8), DB at 0024, 0 duplicate groups, healthy. This also validates the empty-diff/index work from this issue against real data.