SchemaRisk
One bad migration away from production downtime.
SchemaRisk catches dangerous PostgreSQL migrations before they hit production.
Quick Start • Demo • CI Integration • Docs
The Problem
(email);
This runs silently in seconds locally. On production with 10M rows? Table locked for 8+ minutes. API down.
users ALTER COLUMN status TYPE VARCHAR(50);
Looks harmless. Actually: Full table rewrite. Lock every row. Downtime.
orders ADD COLUMN shipped BOOLEAN NOT NULL;
Works on empty tables. Production? Instant failure. Transaction rollback.
These are real incidents. Every week, teams deploy migrations that silently break production.
The Solution
┌─────────────────────────────────────────────────────────────────────────────┐
│ SchemaRisk Analysis │
├─────────────────────────────────────────────────────────────────────────────┤
│ File: migrations/001_add_index.sql │
│ Risk: HIGH (score: 70) │
├─────────────────────────────────────────────────────────────────────────────┤
│ ⚠ WARNING │
│ CREATE INDEX on 'users' without CONCURRENTLY will hold a SHARE lock │
│ for the duration of the index build │
├─────────────────────────────────────────────────────────────────────────────┤
│ ✓ SAFE ALTERNATIVE │
│ CREATE INDEX CONCURRENTLY idx_email ON users(email); │
└─────────────────────────────────────────────────────────────────────────────┘
30 seconds to install. 1 command to prevent downtime.
See It in Action
# Built-in demo — see SchemaRisk catch real problems
Output:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SchemaRisk Demo - Real-World Migration Analysis
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Analyzing dangerous migration...
┌─────────────────────────────────────────────────────────────┐
│ ⛔ CRITICAL RISK DETECTED │
├─────────────────────────────────────────────────────────────┤
│ Operation: ALTER COLUMN TYPE on `users.email` │
│ Impact: Full table rewrite (~5M rows) │
│ Lock: ACCESS EXCLUSIVE (blocks all queries) │
│ Duration: 8-15 minutes estimated │
├─────────────────────────────────────────────────────────────┤
│ ✓ Zero-Downtime Alternative: │
│ │
│ -- Step 1: Add shadow column │
│ ALTER TABLE users ADD COLUMN email_v2 VARCHAR(255); │
│ │
│ -- Step 2: Backfill in batches │
│ UPDATE users SET email_v2 = email WHERE email_v2 IS NULL │
│ LIMIT 10000; │
│ │
│ -- Step 3: Atomic swap │
│ ALTER TABLE users RENAME COLUMN email TO email_old; │
│ ALTER TABLE users RENAME COLUMN email_v2 TO email; │
│ │
│ -- Step 4: Drop old column │
│ ALTER TABLE users DROP COLUMN email_old; │
└─────────────────────────────────────────────────────────────┘
→ This migration would have caused 15 minutes of downtime.
→ SchemaRisk gives you the safe path instead.
Quick Start
Install
# From crates.io (recommended)
# From source
&&
Analyze Your Migrations
# Single file
# All migrations
# Auto-discover and analyze everything
Get Safe Alternatives
# Preview what the safe version looks like
# Generate fixed migration file
CI Integration
Block dangerous migrations at PR time. Add one file and every PR gets automatic risk reports.
GitHub Actions (2 minutes)
Create .github/workflows/schema-risk.yml:
name: Migration Safety Check
on:
pull_request:
paths:
- 'db/migrations/**'
- 'migrations/**'
- 'prisma/migrations/**'
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install SchemaRisk
run: cargo install schema-risk
- name: Analyze migrations
run: |
schema-risk ci-report "migrations/*.sql" \
--format github-comment \
--fail-on high \
--pg-version 14
Result: Every PR with SQL changes gets a comment like:
⚠️ HIGH RISK — This migration may cause production issues.
File Risk Score Lock Est. Duration 001_add_index.sqlHIGH 70 SHARE~90s Safe Alternative:
(email);
What SchemaRisk Catches
| Operation | Risk | Why It's Dangerous |
|---|---|---|
CREATE INDEX (no CONCURRENTLY) |
🔴 HIGH | Blocks all writes during build |
ALTER COLUMN TYPE |
🔴 HIGH | Full table rewrite, exclusive lock |
ADD COLUMN NOT NULL (no default) |
🔴 HIGH | Fails on existing rows |
DROP TABLE |
⛔ CRITICAL | Irreversible data loss |
DROP COLUMN |
🔴 HIGH | Breaks app code still reading it |
RENAME COLUMN/TABLE |
🔴 HIGH | Breaks all downstream queries |
SET NOT NULL |
🟡 MEDIUM | Full table scan to validate |
ADD COLUMN DEFAULT (PG < 11) |
🔴 HIGH | Table rewrite (metadata-only on PG11+) |
TRUNCATE |
⛔ CRITICAL | Immediate data destruction |
ON DELETE CASCADE |
🟡 MEDIUM | Silent cascading deletes |
Commands
analyze — Risk Assessment
fix — Safe Migration Generator
guard — Interactive Confirmation Gate
# → Blocks execution until you confirm
# Safe pattern for scripts:
&&
doctor — Zero-Config Analysis
demo — See It In Action
ci-report — PR Comments
discover — Find Migrations
Configuration
Generate a config file:
Example schema-risk.yml:
version: 2
thresholds:
fail_on: high # Exit non-zero on HIGH or CRITICAL
guard_on: medium # Require confirmation on MEDIUM+
rules:
disabled: # Rule IDs to skip: [R01, R02]
table_overrides:
sessions:
ignored: true # Skip analysis for ephemeral tables
guard:
require_typed_confirmation: true # "yes I am sure" for CRITICAL
audit_log: ".schemarisk-audit.json"
block_agents: true
block_ci: false
PostgreSQL Version Awareness
SchemaRisk knows PostgreSQL internals. Same SQL, different behavior:
| Operation | PG 10 | PG 11+ |
|---|---|---|
ADD COLUMN DEFAULT |
Full table rewrite | Metadata-only ✓ |
SET NOT NULL |
Long exclusive lock | CHECK constraint workaround available |
# Score accurately for your PG version
Real Production Scenarios
Test these to validate SchemaRisk before rolling out to your team:
# 1. Safe migration (should pass cleanly)
# 2. Risky operations (should flag with alternatives)
# 3. Critical destructive ops (should block)
# 4. Full fix generation
# 5. CI output format
Exit Codes
| Code | Meaning |
|---|---|
0 |
Safe / below threshold |
1 |
Risk meets or exceeds --fail-on threshold |
2 |
Parse or I/O error |
3 |
Guard runtime error |
4 |
Blocked by guard |
FAQ
Q: Does this work with my ORM? A: Yes. SchemaRisk analyzes raw SQL. Works with Prisma, Rails, Django, Diesel, or any tool that generates SQL migrations.
Q: How accurate is the lock duration estimate?
A: It's a heuristic based on table size. For precise estimates, use --table-rows users:5000000 or connect to your database with --db-url.
Q: Can I run this in my CI pipeline?
A: Yes. That's the primary use case. Use --fail-on high to block PRs with dangerous migrations.
Q: What about MySQL/SQLite? A: Currently PostgreSQL only. The locking and DDL behavior is Postgres-specific.
Contributing
License
MIT
Stop dangerous migrations before they reach production.