Database Migrations Guide¶
This document explains how to create, apply, and roll back database migrations for Wait Time Canada.
[!NOTE] The canonical migration history and schema reference lives in See
backend/migrations/README.md. This guide covers the process — when and how to create migrations.
When to Create a Migration¶
Create a migration whenever you need to:
- Add, rename, or drop a table or column
- Create or modify indexes
- Add or change CHECK constraints
- Create or modify PostgreSQL functions or enums
- Seed reference data that must exist before the application starts
Never modify the production schema manually. All schema changes must go through a migration file.
Workflow¶
1. Determine the next migration number¶
ls backend/migrations/*.sql | tail -1
# e.g. backend/migrations/017_add_scraper_alert_state.sql → next is 018
2. Create the migration file¶
3. Write the migration¶
Use idempotent SQL so migrations are safe to re-run:
-- 018_your_descriptive_name.sql
-- Purpose: Brief description
-- Depends on: 012_optimize_indexes.sql
-- Idempotent column addition
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'your_table' AND column_name = 'your_column'
) THEN
ALTER TABLE your_table ADD COLUMN your_column TEXT;
END IF;
END
$$;
-- Rollback:
-- ALTER TABLE your_table DROP COLUMN IF EXISTS your_column;
4. Test locally¶
Verify idempotency by running a second time — it should succeed without errors.
5. Document the migration¶
Add an entry to backend/migrations/README.md under the appropriate milestone section.
6. Update related docs¶
| If you changed… | Also update… |
|---|---|
| API response shape | docs/API.md |
| Service layer | Docstrings in backend/src/waittime/services/ |
| Major design choice | New ADR in docs/adr/ |
Applying Migrations¶
Development / staging¶
Production (Neon PostgreSQL)¶
-
Backup first:
-
Run migrations:
-
Verify:
Rolling Back¶
Migrations do not auto-rollback. Each migration file includes rollback SQL in comments.
Steps:
- Identify the migration to undo (e.g.,
018_your_descriptive_name.sql). - Extract the rollback SQL from the file's comments.
- Execute it manually:
- Rename the migration file to
.sql.skipto prevent re-application:
[!CAUTION] Always rollback in reverse order (newest migration first). Data dropped by a rollback is permanently lost — ensure you have a backup.
CI/CD Integration¶
Migrations are validated in two workflows:
| Workflow | What it checks |
|---|---|
scraper-ci.yml | Runs run_migrations.py + full pytest suite |
production-readiness.yml | Validates file naming convention and SQL syntax |
The database-migrate.yml workflow can be triggered manually to apply migrations to production via GitHub Actions.
Current Schema Summary¶
| Table | Rows | Purpose |
|---|---|---|
sources | 4 | Provincial data source metadata |
hospitals | 380+ | Facility metadata with geo-coordinates |
measurements | rolling 30-day window | Wait time audit log with ontology tags |
measurement_aggregates | permanent | Statistical summaries (hourly → monthly) |
scraper_status | 4 | Heartbeat monitoring per source |
scraper_alert_state | 4 | Current alert/incident state per source |
data_quality_snapshots | daily | Scraper reliability metrics |
methodology_change_events | historical | Drift detection audit log |
regions | 15 | Health region metadata |
hospital_regions | 380+ | Hospital-to-region mapping |
For the full schema, see backend/migrations/README.md.