What Happens After a Database Migration Runs Successfully

A database migration finishes without errors. The pipeline shows green. The team breathes a sigh of relief. But an hour later, users start reporting that pages are loading slowly. Some queries are timing out. A few API calls return 500 errors. The migration succeeded technically, but something went wrong underneath.

This scenario is more common than most teams expect. A migration that completes without throwing an exception is not the same as a migration that left the system in a healthy state. The difference between those two outcomes is what post-migration verification is meant to catch.

Why Success Codes Are Not Enough

Most migration tools return a zero exit code when they finish without errors. That tells you the SQL executed and the tool's internal tracking table was updated. It does not tell you whether the new schema works well with the application, whether query performance changed, or whether the migration left locks behind.

A migration can succeed technically and still cause real damage. Adding a column with a default value might lock a large table for minutes. Changing a column type might force the database to rewrite rows, which slows down concurrent queries. Adding an index might help one query but break the execution plan for another. None of these problems show up in the migration tool's exit code.

Post-migration verification is the practice of checking the actual state of the database and the application after a migration runs. It turns a blind deployment into an informed one.

Check Migration Status Properly

The first thing to verify is whether the migration actually completed or stopped partway through. Some tools apply migrations in batches. If a migration fails on the third batch, the first two batches have already changed the database. The exit code might be non-zero, but the damage is already partial.

Look at the migration tool's tracking table or its detailed logs. Find out exactly which statements were applied and where the process stopped. This information tells you whether the database is in a consistent state or whether manual cleanup is needed before retrying.

Do not rely solely on the exit code. Some migrations produce warnings that are not fatal but indicate potential problems, such as deprecated syntax or implicit type conversions. Log those warnings and include them in the pipeline report.

Compare Query Latency Before and After

Schema changes can alter how the database executes queries. A column that was added to a table might cause the query planner to choose a different index or a full table scan. A data type change might disable index usage for certain comparisons.

The pipeline should run a set of representative queries against the database before and after the migration. Compare the latency for each query. If any query shows a significant increase, that is a signal that the migration changed the execution plan in a way that hurts performance.

Focus on the queries that the application uses most frequently or that are known to be performance-sensitive. Do not run heavy analytical queries for this check. Keep the verification queries lightweight so they do not add load to the database during the deployment window.

Check for Locks That Did Not Release

Migrations that modify schema often need to acquire locks on tables or rows. Most locks are released when the migration finishes, but not always. A long-running transaction, a connection that was not closed properly, or a migration that timed out can leave locks in place.

After the migration completes, check the database for active locks. If locks are still held, the application will experience timeouts or queue buildup when it tries to access the affected tables. The pipeline should also log how long locks were held during the migration. If a lock was held for more than a few seconds on a production table, that is worth investigating even if it was eventually released.

Run this query to see if any locks are still held on the table you migrated:

SELECT
    pg_locks.pid,
    pg_locks.mode,
    pg_locks.granted,
    pg_class.relname,
    pg_stat_activity.query,
    pg_stat_activity.state,
    pg_stat_activity.wait_event_type || ': ' || pg_stat_activity.wait_event AS wait
FROM pg_locks
JOIN pg_class ON pg_locks.relation = pg_class.oid
JOIN pg_stat_activity ON pg_locks.pid = pg_stat_activity.pid
WHERE pg_class.relname = 'your_table_name'
  AND pg_locks.granted = true;

If any rows are returned, the migration left locks behind. Investigate the query and state columns to understand why.

Verify Row Counts for Data-Changing Migrations

Some migrations do more than change schema. They populate new columns with default values, move data between tables, or clean up duplicates. These operations can silently miss rows if the migration logic has edge cases or if the data does not match the expected format.

After such migrations, compare the actual row count with the expected row count. For example, if the migration was supposed to fill a new column for all existing rows, check that the number of rows with a non-null value in that column matches the total row count. If there is a mismatch, the migration did not apply to all rows.

Run these checks with simple count queries. Avoid joins or aggregations that could put unnecessary load on the database.

Watch the Application Logs for Database Errors

The most important verification step is checking whether the application can still work with the database after the migration. The application code that is currently running was written for the old schema. If the migration changed the schema in a way that breaks the running code, errors will appear in the application logs.

Look for errors that mention missing columns, type mismatches, or failed queries. These errors mean the application and the database are out of sync. The team needs to decide quickly whether to roll back the migration or deploy a code fix that matches the new schema.

Do not wait for users to report these errors. The pipeline should pull application logs from the monitoring system and scan them for database-related errors automatically.

A Practical Post-Migration Checklist

If you are setting up post-migration verification for the first time, start with these checks in your pipeline:

The following flowchart illustrates the recommended sequence of verification steps:

flowchart TD A[Migration completes successfully] --> B[Check migration status] B --> C[Compare query latency] C --> D[Check for locks] D --> E[Verify row counts] E --> F[Watch application logs] F --> G{All checks pass?} G -- Yes --> H[Safe to keep] G -- No --> I[Alert team]
  • Migration status: Did it complete fully, and where did it stop if it failed?
  • Query latency: Are the top five critical queries still within acceptable range?
  • Locks: Are there any active locks remaining after the migration?
  • Row counts: Do the numbers match expectations for data-changing migrations?
  • Application errors: Are there any new database-related errors in the logs?

Run these checks automatically after every migration. Send the results to the team as a report. If all checks pass, the migration is safe to keep. If any check fails, the team has enough information to decide the next step.

The Takeaway

A green migration status is not a guarantee of safety. The real test is whether the database and the application still work well together after the change. Post-migration verification bridges the gap between "the migration ran" and "the system is healthy." Without it, you are deploying blind and hoping for the best.