Field noteNov 14, 20265 min read1 source

TIL: Drop Columns a Release Later

In one minute

A PostgreSQL column removal caused seconds of downtime because the migration waited for an exclusive table lock, while new queries queued behind it. The author’s lesson is to separate stopping use of old columns from dropping them, and to bound lock waits. For teams shipping database changes, release sequencing is part of application availability.

What changed: It emphasizes splitting column removal across releases and using a lock timeout to prevent PostgreSQL lock waits from stalling table queries.

What happened

In a November 12, 2026 post, developer Hacdias describes planning a database migration across releases to avoid downtime. The post calls the approach “expand and contract”: add new things, migrate, and remove old things in different deployments. The author had applied that general pattern but later concluded that the contract phase itself should have been split into two releases.

During the rollout, the team experienced a few seconds of downtime in the contract phase. They had agreed downtime might occur, but the author had not accounted for how PostgreSQL obtains the lock required to drop a column. DROP COLUMN requires exclusive access to the table. Before PostgreSQL can take that lock, the migration must wait for every query already running on the table to finish. While the lock request waits, new queries against that table queue behind it, including SELECT queries. As a result, a quick column removal can still cause a visible stall while waiting for permission to begin.

There was also an application compatibility problem: the team shipped code that stopped using the old columns in the same release as the migration that removed them. That coupled the code update to the database change, and old code would not work after the columns disappeared. The author’s revised sequence is to migrate the data, then deploy code that no longer uses the old columns, and only drop those columns in a later release. For Prisma users, the post suggests that @ignore can be used to stop using a field while it remains in the database.

For the eventual removal, the author recommends setting a lock_timeout. If the migration cannot obtain the lock within that limit, it times out and can be retried rather than allowing its wait to block traffic for an extended period. The post says this phased approach works for SaaS products, where the team controls release and update cadence. For self-hosted or on-premise software, customers may skip versions. The author suggests communicating supported upgrade paths and adding a version-check precondition to a migration, preventing an unsupported jump that could skip an intervening data migration and lose data.

The post also notes a related PostgreSQL technique for adding constraints: NOT VALID checks new rows without first checking all existing rows, allowing the initial lock to be held briefly. A later VALIDATE CONSTRAINT checks existing rows under a lighter lock that, according to the author, does not block reads or writes. This matters particularly for large tables, where holding a table-wide blocking lock during deployment can be costly.

What the archive adds

We have not covered this before; it opens a new thread.

Trend signal

This does not yet fit a pattern New Runtime tracks.

Why now

The post is a retrospective on a migration the author recently worked on, rather than an announcement of a new PostgreSQL feature. Its timing comes from the author recognizing that the contract phase needed another release boundary after encountering the lock behavior in practice.

Why it matters

Database migrations are often treated as one-off operations, but this account shows how lock acquisition and application compatibility interact with deployment sequencing. A migration can be fast once it starts and still disrupt service while waiting; meanwhile, removing a field before every running application version has stopped using it can break deployments or make rollbacks unsafe.

The practical value is in turning compatibility into explicit phases. Teams can preserve old columns while new code rolls out, give data migration jobs time to run, then remove obsolete schema only after application versions no longer depend on it. A timeout makes lock contention fail fast enough to retry, rather than letting one schema operation accumulate waiting queries. Teams shipping software that customers operate themselves also need to account for skipped versions, not assume every customer follows the intended sequence.

These are general database practices, not AI-specific techniques. They matter to AI product teams for the same reason they matter to any service team: model-backed features still depend on application data and reliable deployments. The post does not quantify how often this failure occurs or compare alternative migration tools, so the transferable lesson is the rollout discipline, not a guarantee that these steps eliminate all downtime.

Limits and uncertainty

The post describes one migration and reports a few seconds of downtime, but does not give the table size, query volume, PostgreSQL version, exact SQL, timeout value, or the detailed release timeline. It does not measure the effect of the suggested sequence or compare lock behavior across versions and workloads. The on-premise version-check condition and upgrade-path guidance are proposed safeguards, not reported results from a separate deployment. The techniques should therefore be evaluated against a team’s schema, traffic, and release model.

Skill to build now

For the next column replacement, write down the compatibility contract for each release: which columns exist, which application versions read or write them, and when data migration runs. Keep the old column until deployed code no longer depends on it; schedule removal separately and set a lock timeout so the migration can be retried if it cannot acquire the lock promptly. If customers control upgrades, add a precondition that rejects paths that skip required data migration.

Reader takeaway

A safe column removal is not just a fast SQL statement. Split the work so data migration, application cutover, and schema cleanup happen in compatible releases, then bound the wait for the exclusive lock. That gives a migration a chance to fail and retry instead of turning lock contention into a queue of blocked queries.

Evidence / sources

  1. 1hacdias.com/2026/11/12/dropping-columns
All Field Notes

Next reads

Read next

Open the graph
  1. 01archiveField NotesOpen the latest editorial analysis.
  2. 02sourcesSourcesSee the original sources this archive cites.
Who read this page?Machine requests, hidden until opened

Loading the privacy-safe route aggregate...

Open the JSON contract