Skip to content

Commit 876c950

Browse files
committed
fix: decouple validation and constraint adding for db
Without the not valid, the migration tries to create the constraint and validate it at the same time which can lock the entire table. We decouple the validation and the constraint creation to avoid transaction timeouts
1 parent f11c046 commit 876c950

3 files changed

Lines changed: 13 additions & 3 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub
9898
- Fail early, fail loud. Fail fast with an error instead of silently continuing.
9999
- Do NOT use `as`/`any`/type casts or anything else like that to bypass the type system unless you specifically asked the user about it. Most of the time a place where you would use type casts is not one where you actually need them. Avoid wherever possible.
100100
- When writing database migration files, assume that we have >1,000,000 rows in every table (unless otherwise specified). This means you may have to use CONDITIONALLY_REPEAT_MIGRATION_SENTINEL to avoid running the migration and things like concurrent index builds; see the existing migrations for examples.
101+
- Each migration file runs in its own transaction with a relatively short timeout. Split long-running operations into separate migration files to avoid timeouts. For example, when adding CHECK constraints, use `NOT VALID` in one migration, then `VALIDATE CONSTRAINT` in a separate migration file.
101102
- **When building frontend code, always carefully deal with loading and error states.** Be very explicit with these; some components make this easy, eg. the button onClick already takes an async callback for loading state, but make sure this is done everywhere, and make sure errors are NEVER just silently swallowed.
102103

103104
### Code-related

apps/backend/prisma/migrations/20260210000000_deferred_email_retry/migration.sql

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ ALTER TABLE "EmailOutbox"
99

1010
-- Constraint: nextSendRetryAt can only be set after at least one failed attempt
1111
-- (if sendRetries is 0, no attempt has failed, so there's nothing to retry)
12+
-- Use NOT VALID to avoid holding ACCESS EXCLUSIVE lock during full-table validation.
13+
-- Validation happens in a separate migration to avoid transaction timeout.
1214
ALTER TABLE "EmailOutbox"
1315
ADD CONSTRAINT "EmailOutbox_nextSendRetryAt_requires_failure"
14-
CHECK ("nextSendRetryAt" IS NULL OR "sendRetries" > 0);
16+
CHECK ("nextSendRetryAt" IS NULL OR "sendRetries" > 0) NOT VALID;
1517

1618
-- Constraint: sendAttemptErrors can only be set after at least one failed attempt
1719
ALTER TABLE "EmailOutbox"
1820
ADD CONSTRAINT "EmailOutbox_sendAttemptErrors_requires_failure"
19-
CHECK ("sendAttemptErrors" IS NULL OR "sendRetries" > 0);
21+
CHECK ("sendAttemptErrors" IS NULL OR "sendRetries" > 0) NOT VALID;
2022

2123
-- Constraint: nextSendRetryAt must be null when email has finished sending
2224
-- (if finishedSendingAt is set, there's nothing more to retry)
2325
ALTER TABLE "EmailOutbox"
2426
ADD CONSTRAINT "EmailOutbox_no_retry_after_finished"
25-
CHECK ("finishedSendingAt" IS NULL OR "nextSendRetryAt" IS NULL);
27+
CHECK ("finishedSendingAt" IS NULL OR "nextSendRetryAt" IS NULL) NOT VALID;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Validate the deferred retry constraints added in the previous migration.
2+
-- This runs in a separate transaction to avoid timeout, and only takes
3+
-- SHARE UPDATE EXCLUSIVE lock (allows concurrent reads/writes).
4+
5+
ALTER TABLE "EmailOutbox" VALIDATE CONSTRAINT "EmailOutbox_nextSendRetryAt_requires_failure";
6+
ALTER TABLE "EmailOutbox" VALIDATE CONSTRAINT "EmailOutbox_sendAttemptErrors_requires_failure";
7+
ALTER TABLE "EmailOutbox" VALIDATE CONSTRAINT "EmailOutbox_no_retry_after_finished";

0 commit comments

Comments
 (0)