diff --git a/doc/DATABASE.md b/doc/DATABASE.md index d4dffb37ce..a979cefcaa 100644 --- a/doc/DATABASE.md +++ b/doc/DATABASE.md @@ -143,6 +143,19 @@ The database mode is controlled by `DATABASE_URL`: Your Drizzle schema (`packages/db/src/schema/`) stays the same regardless of mode. +## Migration authoring checklist + +The 0126 issue comment attribution backfill showed the failure mode this checklist is meant to prevent: each batch looked for the next rows with an unindexed predicate, so PostgreSQL repeatedly scanned the same table and the migration became O(n²) as the table grew. + +When authoring migrations or one-time backfills: + +- Create the supporting index for the batch predicate before the backfill loop runs. +- Bound batches by an indexed key, such as an id range or keyset pagination cursor. Do not use `OFFSET` pagination or a query shape that re-scans already-visited rows each batch. +- Avoid unbounded full-table `UPDATE` or `DELETE` statements. Add a selective predicate and process rows in bounded batches when table size can be large. +- Use `CREATE INDEX CONCURRENTLY` for large existing tables when the migration can run outside a transaction and must avoid long write locks. +- Split schema changes, index creation, and data backfill into separate phases so each step has clear locking and rollback behavior. +- Treat the `check:migrations` CI gate as the enforcement backstop for these rules. If it flags a migration, rewrite the migration or add a suppression comment with the indexed predicate, batch bound, and reason the remaining scan is safe. + ## Resource membership tables Paperclip stores current-user sidebar membership state in: