Changing a live schema without downtime

Add before you remove, backfill in the background, and never lock a table the running application still depends on. Single-tenant means doing this once per client, not once.

An admin approves every new account by hand. Nothing is created until then. We reply by email; no newsletter, no sequence.

app.salescrew.io/today
The daily working view

The short answer

  • A schema change avoids downtime by being additive first. New columns, tables or indexes are added without touching anything the running application still reads or writes.
  • Destructive changes, like dropping or renaming something in use, wait until the app code no longer needs the old shape. Never in the same step as the addition.
  • Backfilling existing rows into a new column happens in the background, in batches. It never holds a lock on a large table long enough to block normal reads and writes.
  • Single-tenant architecture changes the scale of the problem. A schema change has to roll out safely across every client's separate Postgres project, not once against one shared database.

Why a schema change can take a database offline if done carelessly

A schema change that renames a column, drops a table, or runs a blocking operation on a large table can take an application offline in two ways. The first is a hard failure. The app code still expects a column that a migration just renamed or removed. So every query that touches it starts failing at once. The second is a soft failure. Some schema operations, such as adding a column with a default on a very large table in older Postgres versions, hold a lock that blocks reads and writes for the duration. Nothing errors, but it is an outage all the same.

Both failures are avoidable. The technique is decades old and well understood in database operations. It's worth naming, because teams skip it often enough to cause real outages. Never make a change in one step that the app code and the database schema must both update at the same moment.

The additive-first pattern in practice

A schema change that renames a column is really three changes, not one. First, add the new column beside the old one. Both exist, and the running application keeps working unmodified. Second, deploy application code that writes to both columns and reads from whichever one is populated. During this dual-write period, a background job backfills the new column for rows that only have the old one. Third, once the backfill is complete and the application no longer reads the old column, drop it. That is safe, because nothing depends on it anymore.

The backfill needs its own care. If you update every row in a large table in one transaction, it holds a lock for as long as it runs. That causes the soft downtime described above. Backfilling in small batches, with pauses between them, keeps each lock short enough that normal traffic is never blocked. The backfill takes longer in wall-clock time. For a table anyone is actively using, that is almost always the right trade.

What changes when the database is not shared

All of the above describes migrating one database safely. Single-tenant architecture, one Postgres project per client, adds another dimension. A schema change is not deployed once. It has to reach every client's separate project cleanly. No instance can end up on a schema version the running code doesn't expect. A migration that is safe in principle can still cause an incident if it succeeds on most projects and silently fails, or partly applies, on one. That client's instance is then in a state nothing was tested against.

So a single-tenant product needs migration tooling that tracks the schema version of every client project independently. It applies changes with the same additive-first rule. It also spots any instance that falls out of sync and raises an alert, instead of assuming every rollout worked. That is a much larger operational surface than migrating one shared database. It is part of the single-tenant trade. A database per client buys you isolation and a full export. It also calls for rollout discipline that grows with the number of clients.

Questions

Why not only take the database offline briefly for a schema change?
For a product people are actively using, any downtime window has a real cost, and it does not scale. A five-minute outage that is tolerable once a quarter becomes untenable when migrations happen weekly. Additive, backward-compatible migrations avoid needing that window at all.
What makes a migration 'additive' rather than destructive?
An additive migration adds a new column, table or index. It doesn't remove or rename anything the running app still needs. A destructive one drops or renames a column the old code still reads. It breaks anything still running the previous version the moment it executes.
Does single-tenant architecture make schema changes harder in every way?
It makes rollout harder. A change has to reach every client's separate project reliably, instead of one shared database once. The method itself doesn't change. Additive-first design and background backfills work the same way, repeated per project instead of done once.