Repository navigation
test: a crash part-way through maintenance loses nothing (#1341) - #1423
Conversation
OffgridwithJD
left a comment
There was a problem hiding this comment.
- What it holds: compact_rewrite, recluster, compact and vacuum are each crashed twice: once uncommitted, after another session checkpoints, and once committed, with no checkpoint since. After recovery the table is checked by md5, row groups and an index scan, and the server log must show a crash recovery.
- Red (PG17): data pages written without WAL,
if (false && RelationNeedsWAL(rel))inPgColumnarWriteLogicalData(.so b9726239 -> f9467d41), gives 13 + 3:after a crash with compact_rewrite committed, the table is as compact_rewrite left it, with the same rows: got [|10/6400||], and the same for recluster and vacuum. The twin fails withzstd decompression failed or produced the wrong length (expected 235).- The recovery arms discriminate: with the SIGKILL in
crash_clusterreplaced bypg_ctl -m fast stop, all 8 go red withno recovery logged (0 before, 0 after).
- The recovery arms discriminate: with the SIGKILL in
- Blocker: nothing asserts that the function did any work, so a no-op passes every arm. Measured with
pgcolumnar_compact_rewritereduced torewritten = 0(.so ed94c1d4): shell 16/16 and twin 1 passed, including the two compact_rewrite rows the ledger dates. A function that changes nothing is trivially crash-safe, so both crash points need the premise #1419 has:- committed:
after's row groups differ from the fresh table's; - uncommitted: the open session reports its row groups before it names itself (in the shell that output currently goes to /dev/null).
- committed:
- Green at head: shell 16/16 and twin 1 passed on PG17 and PG18. The ledger, 2790 / 2107 / 683, matches the tsv, the gate agrees against origin/main, all 16 names have rows, and
cluster_tests566 matches collection.
🤖 Generated with Claude Code
…sh, in both twins (#1341) Review of #1423 by OffgridwithJD: with compact_rewrite reduced to a no-op, every arm passed, since a function that changes nothing is trivially crash-safe. Each function must now change the row groups: uncommitted, as its open session sees them (named into application_name, so a SIGKILL cannot lose the reading); and committed, before the crash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@OffgridwithJD, addressed in 4fc2455, rebased onto main f2bd5f1.
🤖 Generated with Claude Code |
36fd27e to
4fc2455
Compare
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approved at 4fc2455.
- The did-work arms hold. I applied a no-op mutant to each function in turn, on PG17 and PG18:
rewritten = 0,reclustered = 0inrecluster_verb,retired = 0in compact, and thepgcolumnar_compact_relationcall removed from vacuum. Each gives 22 + 2 in the shell: that function's "changed the row groups in its open transaction" and "changed the row groups before the crash", withgot [unchanged (10/10000)]. The twin fails on the same uncommitted arm, and a copy of the twin with the first loop emptied fails on the committed arm for all four. (PG17 .so bb5b7477 → 71ae4cf7 / 2461aefe / fd8e74d0 / 6b0854c8; PG18 4d5a6995 → d1ef9aea / 675cc1d5 / 0f4caaa9 / 94c4e21a.) - The
application_namereading is the in-transaction count, taken before the SIGKILL. The suite read compact_rewrite 10/6400, recluster 1/5400, compact 9/9000 and vacuum 1/5400 about 15 ms before the kill. At the same moment a second session read 10/10000, and the open session was still active with an xid. With the open session made to report a fixedmc_open 10/10000, all 4 arms go red: 20 + 4. - The WAL mutant gives 3 red on PG17 and PG18: the committed arms of compact_rewrite, recluster and vacuum, with
got [|10/6400||]and[|1/5400||]. The twin fails with the zstd error. The recovery arms still discriminate: with the kill replaced bypg_ctl -m fast stop, all 8 go red withno recovery logged (0 before, 0 after). - Green at head: shell 24/24 and twin 24 pass, on PG17 and PG18. The census, 3031 / 2301 / 730, matches the tsv and the paragraph, and
cluster_tests577 matches collection. - The 8 split
recovery_minimal_walrows in this diff are the main-side defect that #1434 fixes. When you rebase onto it, take main's text for those rows. A branch that keeps the split text will be refused by the ledger reader there, so it can't slip through. - Minor: TESTS.md §119 doesn't mention the new did-work arms yet.
🤖 Generated with Claude Code
…ws are reworded The mutation column is a ';'-separated set, written back sorted. A --mutation whose text held "; " was stored as two mutations, one a fragment starting with a space, and every later merge re-sorted it. commandprompt#1427's recovery_minimal_wal text did this to 8 rows, and commandprompt#1423, commandprompt#1424 and commandprompt#1426 each carried them as a change of their own. Four older harness_selftest rows were split the same way. - merge refuses a --mutation holding ';' (rc 2, ledger untouched). - the reader refuses a ';'-piece with whitespace at an end, by line: the writer joins with a bare ';', so such a piece is a fragment, never a mutation. - the 12 rows are reworded without ';', from their original text in history. Only field 6 changes; the census stays 3007 / 2282 / 725. Red first: the two refusal arms got 0 want 2 on the unchanged tool, and the committed-ledger arm was red at line 1783 with the refusal in and the rows not yet reworded. guard_tests 435 -> 438 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CREATE UNLOGGED TABLE and ALTER TABLE ... SET UNLOGGED are refused (0A000) and leave nothing changed; ALTER TABLE ... SET TABLESPACE is refused and the table stays where it was with its rows; CREATE INDEX CONCURRENTLY is refused and leaves its index marked invalid, as core does for any failed concurrent build. Nothing tested any of them. Arms in both twins of am_contracts, each red under its own mutation. limitations.md now names SET UNLOGGED and SET TABLESPACE, and says to drop the invalid index a refused concurrent build leaves. cluster_tests +1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N
…by hand, and the row_lock_buckets bound (#1341 items 55, 56, 16) Squashed for the rebase onto main; the commits it replaces were: - test: advisory_lock_class's first writer commits on a flag row, not after a sleep (#1341) - test: a bulk UPDATE holds row_lock_buckets row locks, not one per row (#1341 item 16) - test: the row-identity lock is out of SQL's reach, and bulk_parallel_writer set by hand is safe (#1341 items 55, 56) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as (#1341) A new suite, maintenance_rollback, and its pytest twin. compact_rewrite, recluster, compact, vacuum and vacuum_sorted each run inside a transaction on a table with row groups of 1000 rows, two rows in five deleted, and one group deleted whole. Each one's effect on the row groups is read inside the transaction, which then rolls back. The table must hold the same rows by content and the same row groups, and an index scan must agree. The same then runs under a savepoint, later writes commit, and every function runs for real. 13/13 on each of PG15-19, and the twin on each. Nine checks are dated on PG17 by one mutant: free space reused whatever its freeing transaction. compact_rewrite then wrote its new groups over the groups it had just retired, and the rollback brought those groups back pointing at overwritten bytes ('corrupt encoded chunk'). cluster_tests 565 -> 566. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N Squashed for the rebase onto main; it replaces: - test: maintenance_rollback's rows re-dated on fresh tables, restated on main 1f7b5da (#1341) - test: maintenance_rollback gives each function a fresh table (#1341) - test: a maintenance function that rolls back leaves the table as it was (#1341)
…ith a unique index (#1421), with #1428 stacked on it Squashed for the rebase onto main; it replaces: - docs: the CHANGELOG states the keyed UPDATE's cost as both of us measured it (#1421) - test: the two prepared premises enter the ledger as never, on #1422's new head (#1341) - test: prepared_across_sessions asserts each ROLLBACK PREPARED arm's transaction was prepared, in both twins (#1341) - test: a prepared transaction, seen from other sessions and across a crash (#1341) - test: the memory arms enter the ledger, two dated, and the counts follow (#1421) - fix: the per-row serialization check frees its catalog reads (#1421) - test: a keyed UPDATE over groups with deletes keeps its memory bounded, in both twins (#1421) - docs: split a limitations.md sentence the plain-language gate measured as too long - fix: a concurrent same-row UPDATE gets 40001, not 23505, on a table with a unique index (#1421) - test: a concurrent same-row UPDATE must get 40001 with a unique index too (#1421, red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new suite, maintenance_crash, and its pytest twin on a private cluster.
compact_rewrite, recluster, compact and vacuum each run twice on a fresh
table with row groups of 1000 rows, two rows in five deleted, one group
deleted whole, and an index:
- in an open transaction, another session checkpoints, and the server
crashes. After recovery the table is as it was;
- committed, and the server crashes before any checkpoint. After
recovery the table is as the function left it.
Each arm checks that a crash recovery was logged, and the table by
content, row groups and index.
16/16 on each of PG15-19, and the twin on each. Four checks are dated on
PG17 by two mutants: data pages written without WAL ('zstd decompression
failed' after a committed rewrite), and free space reused whatever its
freeing transaction ('corrupt encoded chunk' after an uncommitted
compact_rewrite). This completes the maintenance item with #1419.
cluster_tests 565 -> 566.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N
Squashed for the rebase onto main; it replaces:
- test: maintenance_crash's rows re-dated with the work arms, restated on main (#1341)
- test: maintenance_crash asserts each function did work before the crash, in both twins (#1341)
- test: a crash part-way through maintenance loses nothing (#1341)
4fc2455 to
08f762a
Compare
|
@OffgridwithJD, restated as 08f762a, in a chain on main 670a58c: #1413 → #1415 → #1419 → #1422 → #1423 → #1424, this one on top of #1422. The patch is identical to 4fc2455, the head you approved, apart from re-derived bookkeeping: the budget paragraph, re-anchored on the previous link's census and matching the tsv; 🤖 Generated with Claude Code |
…ws are reworded The mutation column is a ';'-separated set, written back sorted. A --mutation whose text held "; " was stored as two mutations, one a fragment starting with a space, and every later merge re-sorted it. commandprompt#1427's recovery_minimal_wal text did this to 8 rows, and commandprompt#1423, commandprompt#1424 and commandprompt#1426 each carried them as a change of their own. Four older harness_selftest rows were split the same way. - merge refuses a --mutation holding ';' (rc 2, ledger untouched). - the reader refuses a ';'-piece with whitespace at an end, by line: the writer joins with a bare ';', so such a piece is a fragment, never a mutation. - the 12 rows are reworded without ';', from their original text in history. Only field 6 changes; the census stays 3007 / 2282 / 725. Red first: the two refusal arms got 0 want 2 on the unchanged tool, and the committed-ledger arm was red at line 1783 with the refusal in and the rows not yet reworded. guard_tests 435 -> 438 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ws are reworded The mutation column is a ';'-separated set, written back sorted. A --mutation whose text held "; " was stored as two mutations, one a fragment starting with a space, and every later merge re-sorted it. commandprompt#1427's recovery_minimal_wal text did this to 8 rows, and commandprompt#1423, commandprompt#1424 and commandprompt#1426 each carried them as a change of their own. Four older harness_selftest rows were split the same way. - merge refuses a --mutation holding ';' (rc 2, ledger untouched). - the reader refuses a ';'-piece with whitespace at an end, by line: the writer joins with a bare ';', so such a piece is a fragment, never a mutation. - the 12 rows are reworded without ';', from their original text in history. Only field 6 changes; the census stays 3007 / 2282 / 725. Red first: the two refusal arms got 0 want 2 on the unchanged tool, and the committed-ledger arm was red at line 1783 with the refusal in and the rows not yet reworded. guard_tests 435 -> 438 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The crash half of the #1341 core-am item "maintenance functions that roll back or crash part-way". #1419 is the rollback half. Test-only: no source change.
What it adds
A new suite,
test/maintenance_crash.sh, and its pytest twin,test_maintenance_crash.py. The twin uses a private cluster, because every arm takes the postmaster down. The shell kills its process group, as recovery.sh does. The twin stops it with-m immediate, which leaves the same redo for the next start.compact_rewrite,recluster,compactandvacuumeach run twice on a fresh table (row groups of 1000 rows, two in five rows deleted, one group deleted whole, an index):CHECKPOINT; crashEach arm checks that a crash recovery was logged ("database system was not properly shut down"), then the table by content (an md5 over every row), row groups, and an index scan.
Evidence
PgColumnarWriteLogicalData)zstd decompression failed or produced the wrong lengthcorrupt encoded chunk (DOD element width invalid)run_all_versions.sh(in C order:maintenance_crashbeforemaintenance_due, which the selftest caught),test_compare_to_bash.pyCOMPLETE, TESTS.md §115.cluster_testsgoes 565 → 566.🤖 Generated with Claude Code
https://claude.ai/code/session_019VvWuX1zUwk6Y513waRd4N