skip to content

Describe what a database does when you attach an already-populated table as a new partition of a partitioned table, and when you remove an existing partition from it. Which locks are taken, how can you make the attach avoid a full validation scan, and what does a concurrent detach (PostgreSQL's DETACH PARTITION CONCURRENTLY) buy you?

level: seniorimportance: must knowfreq 44%

answer

  1. attach validates the bound: scan unless CHECK proves it
  2. parent share-update-exclusive, attached table access-exclusive
  3. pre-build matching indexes before attaching
  4. plain detach = access-exclusive on parent, lock queue
  5. CONCURRENTLY: two transactions, no default partition, no tx block

basics

~20 s

Attach validates that every row in the incoming table satisfies the new bound, scanning it under an exclusive lock on that table unless a matching validated CHECK constraint lets the scan be skipped. Detach needs an exclusive lock on the parent; a concurrent detach takes a weaker lock in two steps instead of blocking all queries.

solid answer

~60 s

**Attach.** The engine must guarantee the invariant that every row in a partition satisfies its bound, so it scans the incoming table. It takes a share-update-exclusive lock on the parent (so reads and writes on the parent continue) plus an exclusive lock on the table being attached, and on the default partition if one exists, which it also scans. The scan is skipped if the incoming table already carries a validated CHECK constraint that implies the partition bound. So the safe recipe is: build the table standalone, add the CHECK constraint, load and validate it, create indexes matching the parent's partitioned indexes, then attach, which becomes near-instant. **Detach.** A plain detach takes an access-exclusive lock on the parent: it waits for every transaction touching the parent and blocks all new ones behind it. DETACH PARTITION CONCURRENTLY (PostgreSQL 14+) does it in two transactions under a weaker lock, marking the partition detach-pending and waiting for older snapshots to finish. It cannot run inside a transaction block, is unavailable when a default partition exists, and if interrupted leaves a state you finish with FINALIZE.

code

sql · 11 lines
sql
CREATE TABLE events_2026_09 (LIKE events INCLUDING DEFAULTS INCLUDING STORAGE);
ALTER TABLE events_2026_09
  ADD CONSTRAINT ck_bound
  CHECK (occurred_at >= '2026-09-01' AND occurred_at < '2026-10-01');

-- bulk load here, then build indexes matching the parent's
CREATE INDEX ON events_2026_09 (occurred_at);

SET lock_timeout = '3s';
ALTER TABLE events ATTACH PARTITION events_2026_09
  FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');

go deeper

for a junior

Know that attaching an existing table checks its rows against the bound and that detaching removes a partition without deleting its data.

for a middle

Explain the validation scan, the CHECK-constraint shortcut, and that detach takes a strong lock on the parent while attach does not.

for a senior

Give the full recipe: standalone load, constraint, matching indexes, lock_timeout with retry, concurrent detach and its restrictions and the FINALIZE recovery path.

for a principal

Position it as the swap primitive for zero-downtime data movement: blue-green loading of a partition, archive-then-drop pipelines, and the risk model of exclusive DDL on a busy cluster.

## Why attach and detach are interesting at all Both operations look like metadata edits, and in the best case they are. What makes them an interview topic is the correctness invariant behind them and the lock levels that enforcing it requires. The invariant: every row stored in a partition must satisfy that partition's bound, because the planner relies on it to skip partitions. ## ATTACH: the validation scan When you attach a populated standalone table, the engine cannot take your word that its rows fit the bound, so by default it reads the whole table and checks each row. On a large table that is minutes of I/O with locks held. Locks taken in PostgreSQL 12 and later: - **share-update-exclusive on the parent** - deliberately weak, so concurrent reads and writes against the partitioned table continue during the attach; - **access-exclusive on the table being attached** - it is about to change identity, so nothing else may touch it; - **access-exclusive on the default partition, if one exists**, which is also scanned to prove no row in it belongs in the newly covered range. The scan can be skipped. If the incoming table already has a validated CHECK constraint whose condition implies the partition bound, the engine proves the invariant from the catalog instead of from the data. That yields the standard low-impact loading recipe: 1. create the table standalone with the same column layout; 2. add `CHECK (occurred_at >= '2026-09-01' AND occurred_at < '2026-10-01')` and a NOT NULL on the key; 3. bulk-load it, with indexes built afterwards for speed; 4. create indexes matching each of the parent's partitioned indexes, with equivalent definitions; 5. attach; the bound check is proved from the constraint and the index attach is a catalog operation; 6. optionally drop the now-redundant CHECK afterwards. Step 4 matters as much as the constraint. If the parent carries partitioned indexes and the incoming table lacks equivalents, the attach builds them right there while holding its locks, which can dwarf the validation scan. Matching them beforehand turns index creation into a catalog attach. Constraints such as foreign keys on the parent are similarly re-checked or re-established during the attach. ## DETACH: the lock queue problem A plain `DETACH PARTITION` needs an access-exclusive lock on the parent because plans referencing the partitioned table must be invalidated. Access-exclusive conflicts with everything, so it waits behind the longest-running transaction that touched the parent, and while it waits every new query queues behind it. A five-minute analytic query can therefore turn a millisecond DDL into a five-minute outage for that table. The standard defence is `SET lock_timeout` plus retry with backoff: fail fast rather than build a queue. ## DETACH PARTITION CONCURRENTLY PostgreSQL 14 added a concurrent variant that avoids the long exclusive lock by splitting the work into two transactions: - the first marks the partition as detach-pending under a share-update-exclusive lock, so ongoing queries keep seeing it; - it then waits for transactions with older snapshots to finish, so that no running query still expects the partition to be part of the parent; - the second transaction completes the detach. Restrictions worth naming: it cannot run inside an explicit transaction block; it is not allowed when the partitioned table has a default partition; and because it waits, a long-running transaction still delays it, though without blocking others. If it is interrupted, the partition is left in the detach-pending state and you finish with `ALTER TABLE ... DETACH PARTITION ... FINALIZE`. Note also that a detached partition keeps the data: it becomes an ordinary table, which is exactly what you want for archive-then-drop. ## Other engines MySQL offers `EXCHANGE PARTITION` to swap a standalone table with a partition, with an explicit `WITHOUT VALIDATION` option that shifts responsibility to you, and `REORGANIZE PARTITION` which copies data. Oracle has `EXCHANGE PARTITION ... WITHOUT VALIDATION` plus `UPDATE INDEXES` to keep global indexes usable, and online variants. The shape of the tradeoff is identical everywhere: prove the bound cheaply, or pay for a scan under a lock. ## What to say in the interview Name the invariant, the two lock levels, the CHECK-constraint trick that skips the scan, matching indexes beforehand, the lock-queue hazard of access-exclusive DDL, lock_timeout with retries, and the concurrent detach with its restrictions.

  • Your ATTACH still ran for twenty minutes even though the incoming table had a matching validated CHECK constraint. What else could it have been doing?
    Most likely building indexes. If the parent has partitioned indexes and the incoming table has no equivalent index for each of them, the attach creates them while holding its locks. Create matching indexes on the standalone table first so the attach only records the parent-child link. A default partition on the parent is the other candidate, because it is scanned under an access-exclusive lock during every attach.
  • Why does a plain DETACH PARTITION on a busy table sometimes appear to hang, and what is the operational fix?
    It requests an access-exclusive lock on the parent, which conflicts with every other lock mode, so it waits for the oldest transaction still touching the table. While it waits in the queue, newly arriving queries queue behind it, so throughput collapses and it looks like a hang. Fix it by setting a short lock_timeout and retrying with backoff, running in a quiet window, or using the concurrent detach variant where available.

saying these in an interview costs you the question

  • Believing ATTACH is always metadata-only; without a proving CHECK constraint it scans the whole incoming table.
  • Thinking ATTACH blocks all reads on the parent; the parent lock is deliberately weak, it is the incoming table and any default partition that are locked exclusively.
  • Assuming DETACH CONCURRENTLY has no waiting at all; it still waits for older snapshots, and it is disallowed with a default partition or inside a transaction block.
  • Saying a detached partition's data is deleted; it becomes an ordinary standalone table.

context