perf(db): index every acting foreign key and drop prefix indexes

Twenty-one foreign keys with ON DELETE CASCADE or SET NULL had no index
leading with their column, so deleting a parent scanned the child table
once per row; medication_intake_events.medication_id was the one an
account delete paid for. Seventeen small-table indexes and the measurements
(user_id, type, measured_at) index repeated the leading columns of another
index and only cost writes.

Migration 0415 adds and drops the small-table ones in one DO block under a
bounded lock wait; 0416 drops the measurements index concurrently, in a
file of its own. An integration test holds the schema to both rules from
the catalog, each with a counter-check, and compares the plans of the
user-and-type query shapes on a seeded table with and without the dropped
index. The lock-timeout guard now also requires a CONCURRENTLY statement
to stand alone in its file.
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 13:07:24 +02:00
1 parent 41de0ca126
commit 03aec3ec12
6 files changed
+572 -21

No files matched your search

@@ -19,8 +19,8 @@
* - All-time scan rather than a rolling window. The
* `personal_records_user_metric_idx` index makes
* `ORDER BY value LIMIT 1` constant-time; reading the worst-case
* ~30k measurement rows per metric is ~50 ms with the typed
* `measurements_user_id_type_measured_at_idx` index.
* ~30k measurement rows per metric is ~50 ms over the
* `(user_id, type, measured_at, …)` unique index.
* - Tie handling: an equal-value reading writes a row but the caller
* suppresses the push notification (the user matched, not beat
* their best). The notification suppression lives on the job