The schema had been stable for months. Then the specs changed. You need a new column.
Adding a new column to a database sounds simple, but every choice affects performance, reliability, and future changes. The wrong approach can lock tables, block writes, and break code. The right approach keeps production running with zero downtime.
In SQL, creating a new column begins with ALTER TABLE. In PostgreSQL:
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
This adds the column, but it’s only the first step. You must decide on defaults, nullability, indexing, and backfilling.
Defaults and NULLs
If you add a non-nullable column with a default, PostgreSQL rewrites the whole table. On large datasets, that means minutes or hours of locks. To avoid that, first add the column as nullable, then backfill in batches, and finally set it to NOT NULL once the data is ready.
Indexing the New Column
If the new column is part of query filters or joins, create an index. Build it concurrently to prevent write blocking:
CREATE INDEX CONCURRENTLY idx_users_last_login ON users(last_login);
Backfilling Without Downtime
Backfill data with small transactions, committing every few thousand rows. Monitor the load. If replication lag spikes, pause. Never assume the migration will behave the same in production as in staging.
Application Layer Changes
Deploy a version of the app that can handle both the old and new schema. Write paths should populate both formats until the switchover is complete. Read paths should be resilient to NULL until the data is consistent.
Automation and Rollbacks
Automating schema changes reduces human error. Keep rollback scripts ready. If metrics show degraded performance, revert quickly.
A new column is more than a single SQL statement. It’s a migration plan, a deployment strategy, and a performance test.
See how to add a new column, backfill safely, and deploy the change live in minutes with hoop.dev.