fix: give the weekly backup a run window of its own

The `data-backup` queue was registered without send options, so every job
it minted took pg-boss's queue defaults: a fifteen-minute expiration and
two redeliveries. A snapshot that serialises every account's whole record
does not fit in fifteen minutes on a large one, so the pass was killed
part-way through, redelivered twice against the same record, and reached
`failed` with "job timed out" about three quarters of an hour after it
started — having never once completed.

Two hours, on the schedule rather than on the queue: `create_queue()` ends
in ON CONFLICT DO NOTHING, so a queue-level default would apply to a fresh
database and be inert on every instance that already has the queue. The
admin run-now route sends with the same options, or an ad-hoc snapshot
would silently keep the default the cron was rescued from.

The default two retries stay. The old redeliveries were pointless because
the first attempt could not succeed either; a transient failure genuinely
benefits from a second go, and the alternative is losing the week.
This commit is contained in:
Marc-André Bombeck committed 2026-09-03 02:22:07 +02:00
1 parent 214b268b41
commit b5dd9372a7
5 files changed
+127 -8

No files matched your search

+12 -5
View File
@@ -15,11 +15,13 @@ import { auditLog } from "@/lib/auth/audit";
import { annotate } from "@/lib/logging/context";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { checkRateLimit } from "@/lib/rate-limit";
import {
DATA_BACKUP_QUEUE,
DATA_BACKUP_SEND_OPTIONS,
} from "@/lib/jobs/data-backup-policy";
export const dynamic = "force-dynamic";
const DATA_BACKUP_QUEUE = "data-backup";
export const POST = apiHandler(async (request: NextRequest) => {
const { user: admin } = await requireAdmin();
annotate({ action: { name: "admin.backups.run" } });
@@ -57,9 +59,14 @@ export const POST = apiHandler(async (request: NextRequest) => {
throw new HttpError(503, "Background worker is not running");
}
const jobId = await boss.send(DATA_BACKUP_QUEUE, {
triggeredAt: new Date().toISOString(),
});
// Same expiration window as the weekly cron. An ad-hoc snapshot runs the
// identical pass over the identical record, so it must not silently keep the
// 15-minute queue default the cron was rescued from.
const jobId = await boss.send(
DATA_BACKUP_QUEUE,
{ triggeredAt: new Date().toISOString() },
DATA_BACKUP_SEND_OPTIONS,
);
annotate({ meta: { job_id: jobId ?? null } });