Analyze the performance and impact of PostgreSQL migrations before running them in production. Migration Lab parses Flyway-style SQL migrations, predicts operation costs, and measures execution, locking, and retry behavior against PostgreSQL containers seeded with generated data.
- Static analysis: inspect SQL without a database, flag risky operations, and predict whether a change updates metadata, scans data, or rewrites a table.
- Runtime analysis: measure a migration against a seeded database, observe locks, compare observed costs with predictions, and report what a cancelled attempt leaves behind.
- Timeline: explore tables and migrations in an interactive grid, including renames and partitions.
- HTML reports: export the timeline and static analysis as a self-contained file.
Migration Lab is an early-stage project. SQL support is partial, and APIs and output formats may change. See supported operations for the current scope.
Building locally requires Go 1.25 or later, a C compiler with CGO enabled, Node.js 22 or later with npm, and just. Docker is needed for execution and runtime analysis.
From the repository root:
npm ci --prefix frontend
just build-cliThis builds the frontend, embeds its assets, and produces build/migration-lab. Build the CLI before
running the full test suite on a fresh checkout: the report package requires those generated assets.
To build the frontend, backend server, and CLI together, run just build. The outputs are
frontend/dist/, build/server, and build/migration-lab.
Analyze the included example migrations or a SQL string:
./build/migration-lab backend/testdata/migrations
./build/migration-lab "CREATE TABLE users (id INT);"The default output is JSON containing analysisResult. Static analysis runs without Docker.
Pass your own directory to analyze its *.sql files:
./build/migration-lab /absolute/path/to/migrationsFiles are ordered by Flyway version, compared numerically by segment. For example,
V20260410_2__change.sql comes before V20260410_10__change.sql.
With Docker running, execute a migration sequence in a disposable PostgreSQL container:
./build/migration-lab --run backend/testdata/migrationsTo measure the last migration, first apply its predecessors and seed the affected tables:
./build/migration-lab --runtime backend/testdata/rewrite
./build/migration-lab --runtime --rows 50000 --deadline-ms 30000 backend/testdata/rewrite
# Select a migration by filename, applying only its predecessors first
./build/migration-lab --runtime --migration V2__add_email.sql backend/testdata/migrationsRuntime analysis uses PostgreSQL 16, defaults to 1,000,000 generated rows per touched table, and gives
the target migration a shared 5,000 ms execution deadline. Setup and seeding happen before that deadline.
The CLI adds runtimeResult to the JSON and exits non-zero unless its verdict is COMPLETED.
Measurements depend on the generated data, PostgreSQL version, and local resources. Read the runtime analysis guide for seeding limitations, observed performance classes, and retry verdicts.
./build/migration-lab --report=report.html backend/testdata/migrationsOpen report.html directly in a browser. It includes the frontend assets, migration SQL, and static
analysis, so the timeline works offline. Runtime measurements are available through the CLI and live
web application; they are not included in the HTML report. Review the embedded SQL before sharing a report.
Use the reusable runtime analysis workflow from another
repository with one input: migrations-directory. It measures new migrations in pull requests, or
the latest migration on a manual run, using a GitHub-hosted Ubuntu runner and disposable PostgreSQL.
Same-repository PRs receive one bot summary comment, updated on subsequent runs. Full results and
logs remain downloadable artifacts. The caller grants pull-requests: write; no external server
is needed.
See GitHub setup and debugging for the caller workflow and current limits.
The development environment requires Docker with the Compose plugin and just. Go and Node.js run
inside the containers for this setup.
Use the web application only in a trusted local environment. The API has no authentication, and the supplied configuration publishes ports 3000 and 8080 on all host interfaces. The backend can read mounted migration directories and uses the Docker socket to start containers. Restrict access to those ports and run only trusted migrations.
Start with the bundled sample files mounted as the migration root:
MIGRATIONS_ROOT="$PWD/backend/testdata" just dev-runOpen localhost:3000/?migrationId=ecommerce.
Use the dataset selector to explore other examples, including simple-partition and performance-class.
Tables form the rows and migrations form the columns. Drag to pan, scroll to zoom, and use the left and
right arrow keys to move between cells. Hover a cell for its focus-in and runtime buttons. Enter
focuses the selected cell; Escape backs out. The focus panel shows the SQL and warnings, and runtime
measures that cell's migration.
Choose a narrow directory to mount read-only:
MIGRATIONS_ROOT=/absolute/path/to/migrations just dev-runThen open:
http://localhost:3000/?migrationsPath=/absolute/path/to/migrations
The path must be accessible inside the backend container. migrationsPath takes precedence over
migrationId. Without an explicit MIGRATIONS_ROOT, Compose mounts ~/dev; keep the variable set when
working with a narrower directory. It can also be set in a local .env file.
Stop the services with:
just dev-stopThe backend is Go with Echo and pg_query_go; the frontend is React, TypeScript, Zustand, and
active-grid.
After installing dependencies and building the CLI as described above, run:
just testThis generates API contract fixtures, runs the Go tests, typechecks the frontend, and runs the Vitest
tests. Docker is required for container-backed tests.
The Go tests include the GitHub automation tests, which also require Git. Run those separately with
just test-automation.
Use just dev-run to build and start the development environment in the background, waiting until
the services are healthy. The frontend listens on port 3000 and proxies API requests to port 8080.
Frontend edits reload automatically; use just dev-apply for backend changes, retaining your
MIGRATIONS_ROOT setting. That recipe also requires the local Go toolchain. Stop the environment
with just dev-stop, or use just dev-stop clear to also remove its volumes.
- Supported SQL operations
- Static analysis warnings
- Predicted performance classes
- Runtime analysis and findings
- GitHub Actions setup and debugging
- Monitoring
Apache License 2.0 © 2026 Eugene Levenetc.