Database Operations
Operational database concerns for DevOps — storage growth, bloat, and triaging a disk-full alert on a production database.
9 questions
SeniorDesignVery commonYou operate a busy production PostgreSQL holding data the business cannot lose. Design its backup and recovery strategy so you can recover from both a whole-host loss and a logical disaster — for example a bad migration that corrupts a table at 14:32 — with minimal data loss. Cover the choice between logical backups (a dump such as pg_dump) and physical backups (a base backup of the data files), full versus incremental, and how continuous write-ahead-log (WAL) archiving enables point-in-time recovery (PITR) so you can restore to just before 14:32. Explain where backups and WAL live so one failure cannot destroy both the data and its backups, why a replica is not a backup, and how you prove the strategy works — because a backup you have never restored is not a backup. State the recovery-point and recovery-time objectives (RPO/RTO) your design targets.
You operate a busy production PostgreSQL holding data the business cannot lose. Design its backup and recovery strategy so you can recover from both a whole-host loss and a logical disaster — for example a bad migration that corrupts a table at 14:32 — with minimal data loss. Cover the choice between logical backups (a dump such as pg_dump) and physical backups (a base backup of the data files), full versus incremental, and how continuous write-ahead-log (WAL) archiving enables point-in-time recovery (PITR) so you can restore to just before 14:32. Explain where backups and WAL live so one failure cannot destroy both the data and its backups, why a replica is not a backup, and how you prove the strategy works — because a backup you have never restored is not a backup. State the recovery-point and recovery-time objectives (RPO/RTO) your design targets.
Combine physical and logical backups. Take periodic physical base backups and archive the write-ahead log (WAL) continuously; replaying a base backup plus WAL to a chosen instant — point-in-time recovery (PITR) — restores to just before the bad migration, a low RPO. Keep logical dumps for portable per-table restores. Store backups and WAL off-host so one failure destroys neither, and restore-test them, since an untested backup is not a backup. A replica is not a backup; it copies corruption too.
Common mistakes
- ✗Treating a live replica as a substitute for real backups
- ✗Keeping only nightly dumps, so PITR to a precise moment is impossible
- ✗Storing backups on the same host, so one failure destroys both
Follow-up questions
- →How does continuous WAL archiving let you recover to 14:31 rather than last night?
- →Why must backups and WAL live on separate storage from the primary?
JuniorTheoryCommonWhat is connection pooling, and why does a database need a pooler like PgBouncer?
What is connection pooling, and why does a database need a pooler like PgBouncer?
Opening a database connection is expensive — a TCP handshake, authentication, and a backend process per connection — and each connection costs memory, so a server caps them with max_connections. A connection pool keeps a set of established connections and lends them to many short-lived clients instead of reopening one each time. A pooler like PgBouncer fronts the DB so hundreds of app instances share a bounded number of real connections, avoiding max_connections exhaustion.
Common mistakes
- ✗Setting
max_connectionsvery high instead of adding a pooler - ✗Assuming opening a connection per request is cheap
- ✗Thinking a pooler caches query results rather than reusing connections
Follow-up questions
- →How do transaction-mode and session-mode pooling differ in what they can share?
- →What symptoms show a database is running out of
max_connections?
JuniorTheoryCommonWhat is database replication (leader/follower), and why do teams replicate a database?
What is database replication (leader/follower), and why do teams replicate a database?
Replication keeps copies (followers/replicas) of a database in sync with a primary (leader) by streaming the leader's changes to them. The leader takes writes; each follower applies the same change stream and can serve reads. Teams replicate for read scaling (offload reads onto replicas), high availability (promote a follower if the leader fails), and a warm copy for disaster recovery. A replica is not a backup — it faithfully copies mistakes too.
Common mistakes
- ✗Thinking a live replica removes the need for backups
- ✗Assuming followers accept client writes like the leader does
- ✗Confusing replication (copies for reads/HA) with sharding (splitting data)
Follow-up questions
- →Why is a replica that faithfully copies a bad DELETE not a substitute for a backup?
- →What is the difference between replicating and sharding a database?
MiddleTheoryCommonHow does automatic database failover work, and what is split-brain?
How does automatic database failover work, and what is split-brain?
For high availability a database runs a primary with replicas plus a cluster manager (like Patroni) that health-checks it and, on failure, promotes a replica, repointing clients — automatic failover. The danger is split-brain: if the old primary is only partitioned, not dead, two nodes both accept writes and diverge. To prevent it the cluster needs quorum — a majority must agree who is primary — and fences the old node. Failover is only as safe as the replica is fresh.
Common mistakes
- ✗Thinking two primaries can safely both accept writes during a partition
- ✗Omitting quorum, so a network partition triggers split-brain
- ✗Treating a cold, hand-booted spare as high availability
Follow-up questions
- →Why does a quorum-based cluster prefer an odd number of members?
- →How do clients discover the new primary after a failover?
MiddleTheoryCommonSynchronous vs asynchronous replication — what trade-off do you accept with each?
Synchronous vs asynchronous replication — what trade-off do you accept with each?
With asynchronous replication the leader commits and returns at once, replicating afterward — fast, but a failover can lose transactions not yet shipped to the promoted replica, so the recovery-point objective (RPO) is above zero. Synchronous replication waits for a replica to confirm each commit, so a failover loses nothing (RPO near zero), but every write pays a round-trip and stalls if the replica is slow. Most run async, reserving sync for data they cannot lose.
Common mistakes
- ✗Believing a replica always holds every committed write after a failover
- ✗Ignoring that synchronous replication adds write latency
- ✗Assuming asynchronous replication has an RPO of zero
Follow-up questions
- →How does a synchronous replica going down stall writes on the leader?
- →What does an RPO of a few seconds mean for a bank versus a metrics store?
SeniorDesignCommonDesign a high-availability topology for a single relational database that today is one primary server — a single point of failure whose loss takes the product down. The database must survive the loss of the primary with automatic recovery and minimal data loss, and clients must reach the new primary without a manual reconfiguration. Cover the replica layout (a synchronous standby versus asynchronous read replicas and what each buys you), how failure is detected and a replica promoted automatically, how you prevent split-brain when the old primary is only network-partitioned rather than dead (quorum and fencing), and how application connections are repointed to the promoted node (a virtual IP, a proxy, or a connection pooler). State the recovery-point and recovery-time objectives your topology targets and the single point of failure you removed.
Design a high-availability topology for a single relational database that today is one primary server — a single point of failure whose loss takes the product down. The database must survive the loss of the primary with automatic recovery and minimal data loss, and clients must reach the new primary without a manual reconfiguration. Cover the replica layout (a synchronous standby versus asynchronous read replicas and what each buys you), how failure is detected and a replica promoted automatically, how you prevent split-brain when the old primary is only network-partitioned rather than dead (quorum and fencing), and how application connections are repointed to the promoted node (a virtual IP, a proxy, or a connection pooler). State the recovery-point and recovery-time objectives your topology targets and the single point of failure you removed.
Run a primary with a synchronous standby (RPO near zero) plus async replicas for reads. A cluster manager health-checks the primary and, on failure, promotes the freshest standby. Require quorum — a majority must agree who is primary — and fence the old node so a partition cannot leave two primaries writing (split-brain). Repoint clients via a proxy, virtual IP, or pooler so they reach the new primary automatically. The sync standby bounds data loss; automation bounds downtime.
Common mistakes
- ✗Calling a single primary highly available because the server is powerful
- ✗Relying on manual promotion, so downtime depends on a human
- ✗Omitting quorum and fencing, allowing split-brain on a partition
Follow-up questions
- →Why does a synchronous standby give a better RPO than async replicas alone?
- →How does a proxy or virtual IP spare clients from knowing which node is primary?
JuniorTheoryOccasionalWhy do a database's data files keep growing even when row counts stay roughly steady?
Why do a database's data files keep growing even when row counts stay roughly steady?
Most databases don't reclaim space immediately on UPDATE/DELETE. PostgreSQL's MVCC writes a new row version and marks the old one dead; until cleanup runs, those dead tuples occupy space — this is bloat. Indexes and write-ahead logs add to it. Space is reclaimed by background cleanup (autovacuum/VACUUM), so steady row counts can still mean growing files when cleanup falls behind.
Common mistakes
- ✗Assuming
DELETEimmediately frees the disk space it occupied - ✗Not knowing MVCC keeps dead row versions until cleanup runs
- ✗Forgetting indexes and WAL also consume growing space
Follow-up questions
- →How does autovacuum decide when a table has enough dead tuples to clean?
- →Why does
VACUUMreclaim space for reuse but often not shrink the file on disk?
MiddleDebuggingOccasionalA disk-full alert fires on a production database host — diagnose and respond
A disk-full alert fires on a production database host — diagnose and respond
First confirm with df/du what is eating space — data, bloat from dead tuples, WAL, or logs. Immediate relief: free safe space (rotate logs, archive old WAL) to keep the DB writable. Durable fix for the bloat shown: run VACUUM (ANALYZE) — it reclaims dead-tuple space and refreshes planner stats; then investigate the lagging autovacuum and right-size the disk. Never delete files in the data directory by hand.
Common mistakes
- ✗Deleting files inside the data directory by hand, corrupting the DB
- ✗Skipping triage and acting before knowing what consumes the space
- ✗Treating
VACUUMas a query tuner rather than space reclamation
Follow-up questions
- →Why does
VACUUMreclaim space for reuse butVACUUM FULLis needed to shrink the file? - →What can cause autovacuum to fall behind on a high-churn table?
SeniorDesignOccasionalYou must change the schema of a 500-million-row table in a busy production database — add a column, backfill it from existing data, then switch the application to it — with no downtime and without blocking live reads and writes. Design the migration. Explain why a naive single ALTER (adding a NOT NULL column with a default, or rewriting a column's type) can take a long table-level lock that queues behind running queries and stalls the whole table, and how the expand-contract pattern avoids it: add the new column as nullable, backfill in small batches rather than one giant transaction, dual-write from the application, switch reads to the new column, and only then drop the old one. Cover how batched backfill limits lock time, bloat, and replication lag, how a short lock_timeout with retries prevents a lock-queue pile-up, and how you roll back safely at each step.
You must change the schema of a 500-million-row table in a busy production database — add a column, backfill it from existing data, then switch the application to it — with no downtime and without blocking live reads and writes. Design the migration. Explain why a naive single ALTER (adding a NOT NULL column with a default, or rewriting a column's type) can take a long table-level lock that queues behind running queries and stalls the whole table, and how the expand-contract pattern avoids it: add the new column as nullable, backfill in small batches rather than one giant transaction, dual-write from the application, switch reads to the new column, and only then drop the old one. Cover how batched backfill limits lock time, bloat, and replication lag, how a short lock_timeout with retries prevents a lock-queue pile-up, and how you roll back safely at each step.
Never ship one blocking ALTER on a hot table — a NOT NULL column with a default or a type change can take a table-level lock that blocks every read and write. Use expand-contract: add the column nullable (a fast metadata change), backfill in batches to avoid a long lock or replica lag, dual-write from the app, switch reads over, and drop the old one only after nothing reads it. A short lock_timeout with retries stops a blocked DDL queuing all traffic; each step is reversible.
Common mistakes
- ✗Shipping one blocking ALTER that locks a hot table
- ✗Backfilling all rows in a single long transaction
- ✗Dropping the old column before all reads move to the new one
Follow-up questions
- →Why does adding a nullable column avoid the lock that a NOT NULL default takes?
- →How does a batched backfill keep replication lag and bloat under control?