POSTGRES MIGRATION SAFETY, WITH PRODUCTION CONTEXT

Know when valid SQL is risky SQL.

dbpulse checks every migration against recent production scale and write traffic—then posts one specific, actionable report before merge.

See the PR Report
Read-only stats. No table contents. No database credentials in GitHub.
dbpulse migration checkCompleted in 8s
1 finding
HIGH CONFIDENCE · DESTRUCTIVE / LOCK RISK

Non-concurrent index on public.orders

CREATE INDEX idx_orders_status ON orders (status);
Estimated rows48.2M
Table size86 GB
Write rate312 / min
Snapshot age11 min
Why this is risky

A standard index build can block writes on this large, actively written table.

Safer next stepUse CREATE INDEX CONCURRENTLY outside a transaction, then verify the index is valid.

Stats are estimates from read-only system views. Workload can change after the snapshot.

Static SQL checks stop at syntax.

Production incidents at Handshake, GoCardless, and GitLab show the missing signal: scale & live traffic.

HOW IT WORKS

Context arrives before the migration does.

A small, read-only collector turns system-view counters into a recent snapshot. The Action joins that snapshot to changed DDL.

01

Sample Postgres

The Go collector reads row estimates, relation sizes, and write counters from system views—never user rows.

pg_class · pg_stat_user_tables
02

Inspect the Diff

The repository Action finds changed migration files and parses the 5 supported DDL patterns.

pull_request · contents: read
03

Explain the Risk

One PR-ready report names the operation, table, live evidence, limits, and a safer next step.

pull-requests: write
THE AHA MOMENT

The table changes everything.

ALTER TABLE orders ADD COLUMN is valid SQL. It is a different decision when orders has 48.2 million rows and hundreds of writes each minute.

  • Specific operation & affected table
  • Relevant stats with snapshot age
  • Confidence, limits & safer next step
dbpulse migration checkCompleted in 8s
1 finding
HIGH CONFIDENCE · DESTRUCTIVE / LOCK RISK

Non-concurrent index on public.orders

CREATE INDEX idx_orders_status ON orders (status);
Estimated rows48.2M
Table size86 GB
Write rate312 / min
Snapshot age11 min
Why this is risky

A standard index build can block writes on this large, actively written table.

Safer next stepUse CREATE INDEX CONCURRENTLY outside a transaction, then verify the index is valid.

Stats are estimates from read-only system views. Workload can change after the snapshot.

V1 RULESET

5 high-signal Postgres checks. No mystery score.

Each rule maps supported DDL to the live facts that make it operationally important.

OperationRuleAffected TableLive Context
01ADD COLUMN … DEFAULT gen_random_uuid()Volatile defaultpublic.orders48.2M rows312 writes/min
02CREATE INDEX idx_orders_statusNon-concurrent indexpublic.orders86 GB table312 writes/min
03ALTER COLUMN total TYPE numericColumn type changepublic.orders48.2M rowsHigh rewrite risk
04ADD CONSTRAINT … CHECKValidated constraintpublic.orders48.2M rowsFull scan likely
05DROP COLUMN legacy_stateDestructive droppublic.ordersRead dependencies unknownReview required

dbpulse reports only supported operations it can identify confidently. It does not estimate lock duration or promise zero downtime.

SECURITY BY DEFAULT

Production context without production data.

Least Privilege

Collector SQL uses read-only system views. Grant no access to user table contents.

Scoped Credentials

Ingestion tokens are scoped to one organization and one database, stored only as hashes, and revocable.

Honest Analysis

Reports include snapshot age and parser limits. No unsupported lock-duration guarantees.

REPOSITORY SETUP

One workflow. Two permissions.

Use the Action through repository configuration—no GitHub App installation required.

Required workflow permissionscontents: read reads the diff. pull-requests: write posts or updates the report.
migration-safety.yml
name: dbpulse migration check
on:
  pull_request:
    paths: ["db/migrations/**/*.sql"]
permissions:
  contents: read
  pull-requests: write
jobs:
  dbpulse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: your-org/dbpulse-action@v1
        with:
          snapshot-path: .dbpulse/production.json

Bring production context to code review.

Start with one Postgres primary and one repository.