Skip to main content
RunBook Academy

PostgreSQLXVIII · Platforms, Corruption and Production ArchitecturePlatforms

Automating PostgreSQL, and change management for database change

Intermediate⏱ ~30 min

What you'll learn

  • Decide what to automate and what to keep manual
  • Apply expand-and-contract to make schema change reversible
  • Make migrations safe by default rather than by discipline
  • Separate configuration management from state management

Prerequisites

Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27

Not yet marked complete on this device.

Almost everything in this course can be automated. Some of it should not be.

What to automate

Anything repetitive and reversible:

  • Cluster provisioning and configuration.
  • Backups, and the restore tests from lesson XIII-08.
  • Monitoring and alerting definitions.
  • Minor version upgrades — lesson XVII-05’s binary swap.
  • Routine maintenance windows.
  • Schema migrations, with the safeguards below.

Anything nobody should have to remember:

  • lock_timeout on the migration role, from lesson XVII-04.
  • idle_in_transaction_session_timeout on application roles.
  • Retention that is bounded by the oldest backup, from lesson XIII-05.

What not to automate

Failover decisions on a single important cluster. Lesson XV-04’s arithmetic: the most common HA-caused outage is failing over when nothing was wrong.

Anything destructive without a human. VACUUM FULL, DROP, pg_resetwal, delete_old_cluster.sh.

Responding to corruption. Lesson XVIII-05: almost everything that feels like fixing it destroys evidence.

Killing sessions by rule. A script that terminates anything running over N seconds will eventually terminate the migration, the backup, or the report that matters.

Expand and contract

The pattern that makes schema change reversible. Renaming a column, without a moment where old and new code cannot both run:

-- 1. EXPAND: add the new column. Reversible; nothing reads it yet.
ALTER TABLE users ADD COLUMN email_address text;

-- 2. BACKFILL, in batches (lesson XVII-04)
UPDATE users SET email_address = email
 WHERE id IN (SELECT id FROM users WHERE email_address IS NULL LIMIT 10000);

-- 3. DUAL WRITE: application writes both columns. Deploy, and let it soak.

-- 4. SWITCH READS to the new column. Deploy. Still fully reversible.

-- 5. STOP WRITING the old column. Deploy.

-- 6. CONTRACT: after long enough to be certain.
ALTER TABLE users DROP COLUMN email;

Steps 1 to 5 are reversible at every point, and each is a separate deployment. Only step 6 is not, and by then the column has been unused long enough that dropping it is a decision rather than a risk.

It is slower than renaming the column. It is slower in exchange for never having a moment where a rollback is impossible, and on a system where downtime is expensive that trade is not close.

Making migrations safe by default

-- on the role migrations run as
ALTER ROLE migrator SET lock_timeout = '3s';
ALTER ROLE migrator SET statement_timeout = '30s';

Lesson XVII-04 measured why: a waiting ALTER TABLE blocks every query behind it, so an unbounded lock wait is an outage. Setting it on the role means the least careful migration anyone writes still fails loudly rather than stalling the site.

A checklist worth enforcing in review:

  • Does it rewrite the table? (lesson XVII-04’s table)
  • Does it take ACCESS EXCLUSIVE, and for how long?
  • Is lock_timeout set?
  • Is any backfill batched?
  • Is it reversible? If not, is that intended and agreed?
  • Has it run against a copy of production-sized data?
  • Can the previous application version run against the new schema?

That last one is what makes deployments independent of migrations, and it is the question expand-and-contract exists to keep answerable.

What to take from this

  • Automate the repetitive and reversible; keep judgement manual.
  • Database change is not application change. A dropped column does not come back with a deployment.
  • Expand and contract makes every step reversible except the last, and puts the last one weeks later.
  • Set lock_timeout on the migration role, so safety is the default rather than a habit.
  • Review for rewrite, lock, batching, reversibility, and whether the previous application version still runs.
  • Configuration management handles packages and files. Schema needs a migration tool; data needs neither.
  • ALTER SYSTEM writes a file configuration management does not manage. Check pg_settings.source.

Cross-course references

  • Ansible for Production Sysadmins — Part XII (Idempotency), Part XXXVI (Drift) and Part XXX (Host targeting and blast radius) cover automating configuration without automating an outage.
  • Git, CI/CD & GitOps — Part CVI (Change management), Part LVII (Approval gates) and Part LXXV (Drift) cover treating a schema change as a delivery with a review and a record.
  • Linux for Production Sysadmins — Part LXXIV (Configuration drift) covers detecting the setting somebody changed during an incident and never put back.

Quiz

Knowledge check · 6 questions

  1. Q1. A migration dropped a column, and the deployment is rolled back to the previous application version. What is the state of the data?

  2. Q2. Why must a migration be backward compatible with the application version currently deployed?

  3. Q3. A configuration management run reports the cluster converged, but a setting an operator changed during an incident is still in effect. Why?

  4. Q4. Which should generally NOT be automated? Select all that apply.

  5. Q5. A down migration that reverses a backfill restores the original values.

  6. Q6. Describe expand-and-contract for renaming a column, and explain what it buys.

Passing score: 75%. Answers are checked in this browser.