Migrations Basics
Why migrations are a production discipline
Migrations are where many teams accidentally introduce downtime. In development, you can drop and recreate tables. In production, you need deterministic, reversible changes that coordinate with application releases. The goal is not to become a database expert. The goal is to adopt a boring workflow that prevents risky surprises during deploys.
Production goals at this level
- Repeatable: the same migration set produces the same schema on any environment.
- Reviewable: migrations are code reviewed like application changes.
- Automatable: migrations can run in CI and deployment pipelines reliably.
- Safe sequencing: schema changes and application changes roll out without breaking live traffic.
Core rules
- Never edit applied migrations. If a migration has shipped, create a new migration to fix or adjust.
- One migration per change. Keep each migration small and focused.
- Test migrations in CI. Apply migrations to an empty database and verify the app can start.
- Do not rely on manual steps. If humans must run migrations by hand, it will eventually fail at 3 AM.
Expand then contract (minimal safe deploy pattern)
For production safety, prefer a two-step pattern:
- Expand: add new columns or tables in a backwards-compatible way. Keep old columns intact. Deploy the migration first.
- Deploy app: update code to write both old and new fields, or read from the new field with fallback.
- Contract: remove old columns later, only after confirming traffic no longer depends on them.
This avoids the most common outage: deploying code that expects a column before the migration adds it, or removing a column while old code still reads it.
Using SQLx migrations
SQLx supports migrations as files stored in your repository. This aligns well with production: migrations are versioned, reviewed, and applied by automation.
Create migrations
Typical workflow:
# Create a new migration folder with up and down scripts sqlx migrate add add_users_table # Apply migrations (local or CI) sqlx migrate run # Check migration status sqlx migrate info
Example migration: add a users table
A minimal schema example for MySQL. Keep it simple and explicit.
-- migrations/2026xxxxxxxx_add_users_table/up.sql CREATE TABLE users ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY users_email_uq (email) );
If you use down migrations, keep them honest. In production you may avoid destructive down migrations, but for learning and CI they help validate reversibility.
-- migrations/2026xxxxxxxx_add_users_table/down.sql DROP TABLE users;
Where to run migrations
A common mistake is running migrations automatically inside every service instance at startup. That can work for simple setups, but it can also create dangerous race conditions during rollouts and surprise load on the database.
A safer minimal approach:
- Run migrations as a separate deployment step (CI job, release pipeline, or one-off job) before routing traffic to the new version.
- Keep the application compatible with both old and new schema during the transition.
CI verification pattern
At this level, aim for one CI check that catches most mistakes:
- Start a clean database container.
- Run sqlx migrate run.
- Run a small smoke test that checks basic queries.
# Example steps (conceptual) sqlx migrate run cargo test
Migration safety tips for production
- Prefer additive changes (new columns, new tables) over breaking changes.
- Avoid long locks by keeping migrations small. Large table rewrites are where downtime hides.
- Index changes are not free. Adding an index can be expensive on large tables; plan it and test timing in staging.
- Use explicit naming for indexes and constraints so operations can reason about them.
Minimal operational checklist
- Migration files are committed with the code change.
- Migrations run in CI against a clean database.
- Deployment pipeline has a clear migrate step before traffic shift.
- Application code is backwards compatible during transitions.
What comes next
After migrations, transactions are the next core production concept: where to place them, how to keep them small, and how to avoid holding locks longer than necessary.