Ecto ↔ PostgreSQL: Ensuring Safe Rollback of Schema‑Change Migrations
0 reputation · 30 Jul 2024, 23:18 UTC
0 reputation · 30 Jul 2024, 23:18 UTC
Ecto runs each migration inside a single transaction, but the generated down callback does not automatically undo data modifications that occur before the schema alteration. Certain schema operations—such as changing a column type, dropping a column, or adding a non‑null constraint—require explicit inverse logic in down to be reversible, and the mix task does not verify that the supplied down matches the exact inverse of up for those operations.
Because Mix and Repo configurations lack an automatic compatibility check, developers must manually test rollback scenarios, which introduces uncertainty about whether a migration can be safely reverted in production.
Should Ecto provide a built‑in mechanism to validate that a down function is the exact inverse of the up function for supported schema changes? Should the mix ecto.migrate task emit a warning when a migration lacks a down for irreversible changes? Should Repo offer an opt‑in strict mode that aborts migration if the down cannot be verified as safe?
33775 reputation · 31 Jul 2024, 10:45 UTC
Ecto cannot guarantee that a migration's down function restores the database to its pre-migration state. It does not include a built-in mechanism to validate that down is the exact inverse of up. The mix ecto.migrate task does not emit warnings when a migration lacks a down for irreversible changes, and Repo offers no opt-in strict mode that aborts a migration when rollback safety cannot be confirmed. Safety depends on developer-written logic and manual testing.
For PostgreSQL, Ecto wraps each migration in a single transaction. This ensures atomicity: if any statement fails, the entire migration rolls back and the schema is unchanged. It does not ensure semantic correctness of the down callback. A flawed down can still commit an unsafe state.
The change macro can automatically reverse only certain operations, such as creating or dropping tables and indexes. Destructive or data-dependent changes—dropping a column, altering a column type, adding a NOT NULL constraint without a default, or executing raw SQL—require explicit down logic. Because dropped columns or truncated data cannot be restored by schema-only logic, no automatic inverse exists for those cases. The set of automatically reversible operations may vary across Ecto versions, so verify against your installed version.
The absence of a general-purpose verifier likely stems from the fact that many schema changes are lossy or context-dependent. A static checker could not reliably distinguish a correct inverse from an incorrect one without false positives or excessive restrictions. A warning system would also need to classify reversible versus irreversible operations across all supported adapters and Ecto versions, a mapping that is not currently encoded in the migration runner.
mix ecto.migrate followed by mix ecto.rollback --step 1.mix ecto.dump or psql \d before and after the round trip.up operation has an explicit, tested down counterpart.change operations are automatically reversible.These steps are necessary because Ecto executes down as ordinary code without verifying that it undoes the prior state.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 31 Jul 2024, 02:24 UTC
Ecto wraps each migration in a single transaction only when the migration does not call disable_ddl_transaction/0. If you disable the transaction (e.g., to run CREATE INDEX CONCURRENTLY or VACUUM), the migration steps are committed immediately, so a later down cannot rely on automatic rollback of those statements. For PostgreSQL, most DDL like ADD COLUMN or ALTER COLUMN TYPE … USING is transactional and will be undone if the transaction aborts, but any work outside the transaction must be manually reversed in down. Testing the pair on a staging copy remains the safest way to verify reversibility.