From a15cdefb45d435d50093b7344233c146baf6ba57 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 27 Aug 2026 16:39:02 +0200 Subject: [PATCH] db: index `blocks(height)` references Postgres doesn't index the referencing side of a foreign key, so every row deleted from `blocks` scans `transactions`, `channeltxs` and `outputs` in full. Postgres was taking 100% CPU for multiple minutes, causing startup timeouts in systemd. 247aa13a5 and 50a8e32e3 added indices for `outputs` and `utxoset` in 2018. `channeltxs` (28feb2eb7) and `transactions` (0f89653ce) arrived weeks later without one. `outputs` is only half-covered: `output_height_idx` is (`confirmation_height`, `spend_height`), which doesn't serve a lookup on `spend_height` alone. Changelog-Fixed: db: much faster startup on postgres nodes with large `transactions` or `channeltxs` tables. --- wallet/migrations.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wallet/migrations.c b/wallet/migrations.c index 66dd26ca50dc..b4c8d0ea109e 100644 --- a/wallet/migrations.c +++ b/wallet/migrations.c @@ -1186,6 +1186,17 @@ static const struct db_migration dbmigrations[] = { * after the failure was recorded (issue #9341). */ {SQL("ALTER TABLE payments ADD failmsg BLOB;"), NULL, SQL("ALTER TABLE payments DROP COLUMN failmsg"), NULL}, + /* Postgres does not index the referencing side of a foreign key, so + * removing a row from `blocks` scanned each of these tables in full. */ + {SQL("CREATE INDEX transactions_blockheight_idx" + " ON transactions (blockheight)"), NULL, + SQL("DROP INDEX transactions_blockheight_idx"), NULL}, + {SQL("CREATE INDEX channeltxs_blockheight_idx" + " ON channeltxs (blockheight)"), NULL, + SQL("DROP INDEX channeltxs_blockheight_idx"), NULL}, + {SQL("CREATE INDEX outputs_spend_height_idx" + " ON outputs (spend_height)"), NULL, + SQL("DROP INDEX outputs_spend_height_idx"), NULL}, /* ^v26.09 */ };