Repository navigation
fix(txn): log an ABORT record when a committed transaction cannot be applied - #1380
Conversation
|
ABORT-record approach looks right (sorted non-overlapping blocks, end-exclusive skip, torn-ABORT stands, unknown-ABORT ignored). Two correctness nits:
|
|
Done in 8363d7fb8, 8c8d92b19 and e1263628b. Cross-file ABORT. This can't happen, but nothing pinned it. After the failed publish, the transaction stays an active writer until the caller's rollback, which runs after Offsets. Both are file offsets, with checksums on or off: no checksum entry is open between records, and Stray ABORT. The merge with #1378 turned up one more case. An |
|
@zahariash - could you rerun the generate WAL script to fix sanity checks? |
…applied TransactionManager::commit makes a transaction's COMMIT record durable before publishCommit() applies it. When publish failed (a full buffer pool, or a named graph dropped by a concurrent DROP GRAPH), the caller rolled the transaction back in memory while the WAL kept the record, so a crash before the next checkpoint made recovery apply a COMMIT the client saw fail; a retried primary-key insert then left a database that no longer opened. - After such a failure, append and sync an ABORT record (new WAL record type) naming the transaction by the WAL offset where its records start, then report the original error. If the ABORT cannot be written either, poison the WAL: writes are refused until a reopen, which applies the transaction. - Recovery's dry pass collects aborted transactions; the replay loops of the active, frozen and graph WALs skip their records. - Remove the unused Transaction::commit(WAL*), a second publish path without this handling; tools/wal_dump prints the new record; docs describe it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed commit's ABORT record must land in the WAL file that holds its COMMIT and name the offset dryReplay reads for the block's first record; otherwise recovery ignores the ABORT and applies the rolled-back transaction. WAL::logAbort now asserts that the WAL writer was not reset since the COMMIT: rotation and retirement reset it, and a checkpoint does either only after the failed transaction is rolled back, which happens after its ABORT is synced. WAL::logCommittedWAL asserts that the block grows the writer by exactly its own bytes. The crash test of an engine-written ABORT also runs with per-record checksums off, where the offsets differ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the last CHECKPOINT dryReplay advanced the replay end and cleared the trailing-CHECKPOINT flag for every ABORT record. A stray one after a CHECKPOINT whose shadow was not applied yet therefore turned recovery into a second replay of the records before an earlier pinned CHECKPOINT, which fails on a duplicated primary key. The engine logs an ABORT only after its own COMMIT, with no CHECKPOINT between them; any other ABORT is now ignored entirely. WAL::logAbort refuses, rather than only asserting, an ABORT whose COMMIT is not in the open WAL file; the failed commit's caller then poisons the WAL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sed context parameters The dry-pass and test comments said "after the last CHECKPOINT" where they mean the last one before the ABORT. WAL::logAbort documents its refusal, and neither it nor logAbortOfDurableCommit needs the client context any more. docs/checkpoint_recovery.md states where an ABORT lands, which ones recovery ignores, and that a failed COMMIT stays unapplied once its ABORT is durable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A COMMIT that a later ABORT undoes still proves the pinned checkpoint before it finished, so recovery skips through the CHECKPOINT record despite the next checkpoint's leftover shadow and leaves the aborted transaction out. Needs both the pinned-checkpoint recovery and the ABORT record; it goes into whichever of the two lands second. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clang-tidy flags the move of a uint64_t (performance-move-const-arg). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An ABORT never clears the flag that a WAL ends in a CHECKPOINT record, and the shared replay helper skips the transactions such records name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rerun scripts/generate_wal_typespec.py so the generated file matches the generator output again; the hand-edited std::move removal failed the sanity check that compares the two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An ABORT in a graph WAL had no test, and none covered a failed commit with another writer's commits around it, the normal case now that multi-writer mode is the default. The comment in loadGraphsFromCatalog said dryReplay's bound advances only past COMMIT records; it also advances past ABORT and CHECKPOINT records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f3651df to
7e7618f
Compare
This work was produced with the help of language models.
Fixes #1377.
A
COMMITthat failed withBuffer manager exception: Unable to allocate memory! The buffer pool is full and no memory could be freed!after its WAL commit record was durable was rolled back in memory, but a crash before the next checkpoint made recovery apply it. #1377's repro reopened with 187 nodes where the connection saw 186, and with the failed primary-key insert retried, reopening failed withFound duplicated primary key value 186, which violates the uniqueness constraint of the primary key column..Fix.
COMMITrecord is durable,TransactionManager::commitappends and syncs anABORTrecord naming the offset of the transaction's records, then reports the original error.COMMITis not applied and the database stays writable. AnABORTnaming no transaction committed after the lastCHECKPOINTbefore it is ignored.ABORTcannot be written either, the WAL refuses writes until a reopen, which then applies the transaction, as the error says.COMMITand itsABORTshare a WAL file;WAL::logAbortrefuses an offset outside the open file.Transaction::commit(WAL*)is removed, andtools/wal_dumpprints the new record.Compatibility. Older builds read
ABORTas a corrupt tail: they stop replay there and apply the transaction it undoes, or refuse to open withthrowOnWalReplayFailure. The record lasts only until the next checkpoint retires the WAL;docs/checkpoint_recovery.mdlists it with the other records older builds cannot read.Tests. Three
FailedCommitTestcases crash after aCOMMITfails while being applied, one with checksums off;mainreopens with the failed relationship or node, or fails on the duplicated key. Others append anABORTthat names one of three transactions, sits in a frozen WAL, is torn, names nothing, or follows an unappliedCHECKPOINT, or make theABORTor its sync fail.AbortRecordSkipsTheTransactionInAGraphWALappends one to a graph's WAL from a standalone session, andFailedCommitLeavesOtherWritersCommitsfails a commit between another writer's commits; each fails without its part of the fix. Full ctest: 3372/3372, with multi-writer mode as the default; storage-compat suite, every release: same results asmain.With #1378.
mainis merged in, so the aborted-block skip now runs in #1378's replay helper, which replays the active and the frozen WAL.AbortedCommitAfterPinnedCheckpointIsSkippedchecks that anABORTafter a pinned checkpoint still keeps its transaction out. Without the skip in that helper, it and seven failed-commit tests fail. The recovery simulator passed on the merge, 40 workloads of 60 steps.