fix(restore): let a large restore outlast the request statement timeout

A restore of an account with 1.9 million readings, most of them deleted
by the nightly consolidation and still awaiting the purge, was cancelled
in its clearing step: `DELETE FROM measurements` ran past the 60 s
statement timeout every pooled connection carries, and the whole restore
rolled back with a message that named no cause (#1031).

Most of that statement was not the delete. Two columns point into
`measurements` with ON DELETE SET NULL and had no index, so Postgres
scanned `personal_records` and `ecg_recordings` once per deleted reading:
38 of 40 s on a seeded account with 300 personal records. Migration 0362
indexes both, which also speeds up the tombstone purge and account
deletion.

The restore transaction now sets its own statement and idle-in-transaction
limits to the transaction budget with SET LOCAL, and the budget counts
the rows the account holds as well as the rows the file carries. A
rolled-back restore names what the database refused it over (timeout,
lock, constraint, storage, connection, other) on the job row, the
console, the audit row and the wide event.
This commit is contained in:
Marc-André Bombeck committed 2026-09-27 11:24:41 +02:00
1 parent 69ddcc28e7
commit 93b6c086b4
19 files changed
+763 -18

No files matched your search

+14 -3
View File
@@ -73187,13 +73187,24 @@ components:
type: array
items:
type: string
cause:
type: string
enum:
- timeout
- lock
- constraint
- storage
- connection
- other
required:
- code
- message
additionalProperties: false
description: Why the restore did not happen, in a sentence an operator can act on. `sections` names the missing sections
for `backup.section.missing`; `keyIds` names the encryption key ids the file needs and this server cannot open
it with, for `backup.key.missing`.
description: "Why the restore did not happen, in a sentence an operator can act on. `sections` names the missing
sections for `backup.section.missing`; `keyIds` names the encryption key ids the file needs and this server
cannot open it with, for `backup.key.missing`; `cause` says what the database refused a `transaction_failed`
restore over: it ran out of time, waited on a lock, rejected a record, ran out of disk space or memory, lost the
connection, or something else."
AdminBackupRestoreJobResponse:
type: object
properties: