Multi-version engines store prior row images in two broadly different ways: writing every new version as an additional tuple inside the table itself, or keeping the current row in place and chaining prior images in a separate undo/rollback area. Compare the read and write consequences of these two designs.
answer
- axis: where the old image lives
- append: new tuple, direct reads, index churn, table growth
- undo: in-place row, fast current reads, chain walk for old readers
- rollback cheap in append, real work in undo
- long reader: soft space cost vs hard undo exhaustion
basics
~20 sAppend-in-table designs make every update a new tuple, so reads of any version are direct but indexes must point at the new location and the table grows. Undo-chain designs keep the current row in place, so current reads are fastest and indexes churn less, but old snapshots pay to reconstruct versions and can exhaust undo space.
solid answer
~60 s**New-tuple-in-table** (PostgreSQL-style): an UPDATE writes a whole new tuple in the heap and expires the old one. Reading *any* visible version is a direct fetch — no reconstruction. But the new tuple has a new physical location, so index entries generally must be added for it; a wide row is rewritten in full even for a one-byte change; and the table itself accumulates dead versions until cleanup runs. **Undo/rollback chain** (InnoDB- and Oracle-style): the current row is updated in place, and the prior image (or a delta) is written to a separate undo area, reachable via a back-pointer. Current-version reads are as fast as a non-versioned engine, the table does not grow with churn, and unchanged secondary indexes need no new entry. The costs land on old readers, who must walk the chain applying undo records to reconstruct their version — cost proportional to how far behind their snapshot is — and on undo capacity: a long-running reader can force undo retention until the engine runs out and fails the reader. So: append pays at write time and in table size; undo pays at old-read time and in undo capacity.
code
text · 8 linesappend-in-table:
heap page 12: [v1 creator=100 expirer=140][v2 creator=140 expirer=-]
index entries -> both locations; old reader reads v1 directly
undo-chain:
row (in place): creator=140 value='review' undo_ptr -> U7
undo U7: restore value='draft' prev -> U3
old reader: read row, apply U7 (and U3 if older) to rebuild its versiongo deeper
Recognize that some engines write a whole new row per update while others keep the row in place with prior images stored elsewhere.
Derive the basic consequences: index and space impact for the append design, reconstruction cost for the undo design, and where each puts its growth.
Reason from the workload — wide rows, index count, update rate, long readers — and predict which resource runs out first, plus the schema-level mitigations.
Treat it as a capacity and failure-mode decision: soft degradation via space and scan cost versus hard failure via undo exhaustion, and how that shapes transaction-duration policy and workload isolation between OLTP and analytics.
## The same logical model, two physical realizations Both designs implement identical semantics — versions stamped with creator and expirer, a snapshot deciding which one you see. They differ in *where the old image lives*, and nearly every practical difference between such engines follows from that one choice. ## Design A: every version is a tuple in the table An UPDATE writes a complete new tuple into the table's own storage and marks the previous tuple expired. **What it buys** - **Reads never reconstruct.** Whichever version your snapshot selects, it is a materialized row you read directly. Read cost is independent of how old your snapshot is, which makes long-running readers cheap and predictable. - **Rollback is nearly free.** Undoing means marking a transaction aborted; nothing has to be un-applied. - **No separate undo subsystem** to size, monitor, or exhaust. **What it costs** - **Write amplification.** A new tuple is the full row, even if one small column changed. Updating a wide row with a large text column rewrites all of it. - **Index maintenance.** The new version sits at a new physical location, so index entries must point at it. Naively that means inserting into *every* index on the table on every update, even indexes whose key columns did not change. Real engines mitigate this — a same-page update whose indexed columns are unchanged can be linked to the old tuple and skip index insertion — but the mitigation is fragile: it needs free space on the page, so fill-factor and page density become tuning concerns. - **Table growth and scan cost.** Dead versions occupy pages inside the table, so sequential scans read them and skip them, and the table's physical size reflects churn until cleanup reclaims space. - **Index entries can point at non-visible versions,** so an index-only read must still confirm visibility unless the engine maintains a side structure recording which pages are all-visible. ## Design B: current row in place, prior images in undo An UPDATE modifies the row where it lives and writes the information needed to reverse the change into a separate undo/rollback segment, linking the row to that record with a back-pointer; undo records chain to older undo records. **What it buys** - **The common case is optimal.** Most reads want the current version, and it is exactly where the index or scan lands — no chain walking, no dead tuples interleaved with live data. - **Stable physical layout.** The row's location does not change, so secondary indexes on unchanged columns need no maintenance, and index entries stay valid; clustered-index designs keep rows in key order rather than scattering new versions. - **Table size tracks live data,** not churn. **What it costs** - **Old readers pay per read.** A transaction whose snapshot is N commits behind must walk and apply N undo records for each row it touches. Cost grows with snapshot age and with how hot the row is, so a long analytical query over a churning table degrades non-linearly. - **Rollback is real work.** Aborting means applying undo records to restore rows, so rolling back a huge transaction can take as long as it took to run. - **Undo is a capacity resource.** The engine must retain every undo record any live snapshot might need. One forgotten long-running transaction forces unbounded retention; engines respond either by growing undo storage until it hurts, or by discarding old undo and failing readers with a "snapshot too old"-class error. - **Contention on undo structures** under very high write concurrency. ## How the tradeoff shows up in decisions - **Wide rows with hot narrow updates** (a counter column on a row carrying a large payload) suffer badly in the append design; splitting the volatile column into its own narrow table is a classic remedy. - **Many secondary indexes plus high update rates** amplify the append design's write cost; index count is a first-order tuning lever there. - **Long analytical reads against an OLTP table** are cheap-ish in the append design and dangerous in the undo design — both because of reconstruction cost and because of undo retention. - **Bulk deletes or updates** leave a large space debt in the append design and a large undo burst plus long rollback in the undo design. - **Retention of very old versions** is a hard capacity limit in undo designs and a soft (space and scan-cost) penalty in append designs. ## Answering well Do not present one design as superior. State the axis — *where the old image lives* — then derive each consequence from it: read path, index maintenance, write amplification, rollback cost, growth location, and which resource runs out first under a long-running reader. Naming real engines as exemplars is useful; the point is the mechanism, not the product.
- Why can a single-column update of a wide row be expensive in the append-in-table design, and what schema change mitigates it?Because the new version is a full physical copy of the row, so a one-byte change rewrites every column including large payloads, and it may also require new entries in every index. Splitting the frequently updated narrow column into a separate table keyed by the same identifier makes each update touch a small row with few indexes, at the cost of a join on read.
- In an undo-chain engine, what typically happens to a long-running report query when the underlying table is heavily updated?Each row it reads must be reconstructed by walking undo records back to its snapshot, so the query gets slower as it falls further behind and as the churn rate rises. If the engine's undo retention limit is reached, the records it needs are gone and the query fails with a snapshot-too-old-class error rather than returning stale-but-consistent data.
- Which design makes ROLLBACK of a very large transaction cheaper, and why?The append design: rollback only marks the transaction aborted, and its versions become invisible without any per-row work, so rollback is O(1) regardless of size. The undo design must apply undo records to restore each modified row, so rolling back a large transaction is real work roughly proportional to what it did.
Append-in-table is a filing cabinet where every revision gets its own new sheet filed alongside the others: any revision is one pull away, but the drawer fills and the index cards multiply. Undo-chain is a single sheet you edit in pencil while writing each erasure onto a stack of sticky notes: the current text is instantly readable, but reading last week's text means peeling the notes back one by one — and if the stack is thrown away, that reading is impossible.
saying these in an interview costs you the question
- Claiming one design is simply faster, without naming the workload.
- Asserting that the append design must insert into every index on every update, ignoring same-page optimizations — or the reverse, assuming those optimizations always apply.
- Believing rollback costs the same in both designs.
- Thinking undo-based engines never accumulate space problems because the table stays compact.
- Describing undo records as merely the write-ahead log used for crash recovery.