Skip to content

fix(wal): throw on a checksum-setting mismatch instead of dropping the WAL - #1358

Draft
zahariash wants to merge 3 commits into
LadybugDB:mainfrom
zahariash:fix/wal-checksum-mismatch-throws
Draft

zahariash wants to merge 3 commits into
LadybugDB:mainfrom
zahariash:fix/wal-checksum-mismatch-throws

Conversation

@zahariash

@zahariash zahariash commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

This work was produced with the help of language models.

Fixes #1357.

Opening a database with enableChecksums set the other way from its WAL's header deleted the WAL, and every commit in it, when throwOnWalReplayFailure was false, the engine default. dryReplay checked the flag inside the try whose catch (...) turns read errors into a torn tail, so the mismatch was taken for a WAL without a complete commit. The flag is now read with the plain reader before that try, the way attached-graph recovery already reads it (tryReadWALChecksumFlag). The checksum writer puts each object's checksum after its bytes, so the header sits at the same offset in both modes. A mismatch throws the existing enableChecksums error in both modes and leaves the WAL untouched. A header too short to hold the flag holds no commit, so it still takes the lenient path. The main WAL and the frozen checkpoint WAL both go through dryReplay; graph WALs already take the flag from their own header.

The enableChecksums comment on SystemConfig and docs/checkpoint_recovery.md now say that the setting must match the WAL.

Behavior change. With throwOnWalReplayFailure false, the C++ and C API default, an open with the other enableChecksums setting used to succeed and silently drop every commit in the WAL. It now throws the enableChecksums error, for read-write and read-only opens alike, so an embedder on the lenient default sees a new error where it got an open but empty database. Reopening with the matching setting recovers the commits.

Tests. WalTest.WALChecksumConfigMismatchNoThrow expected the open to succeed with no tables left. It now expects the enableChecksums error from a read-only and a read-write open, the WAL's bytes unchanged, and both tables back after reopening with the right setting. FrozenWALRecoveryTest.FrozenWALWithOtherChecksumSettingIsKept checks the same, without the read-only open, for a frozen checkpoint WAL of 100 inserts, which main deleted. It writes the WAL with the setting flipped, so the two tests cover both directions. Both fail on main. WalTest.TornWALHeaderNoThrow cuts the WAL to 10 bytes, shorter than the 16-byte database ID, and opens with the other setting: the open succeeds with no tables, as on main. Full ctest (Release, runtime checks on): 3166 passed.

zahariash and others added 2 commits October 23, 2026 00:45
…e WAL

With throwOnWalReplayFailure false, the engine default, opening a
database with enableChecksums set the other way from its WAL's header
deleted the WAL and every commit in it. dryReplay checked the flag
inside the catch that treats read errors as a torn tail, so the
mismatch looked like a WAL without a complete commit.

The flag is now read with the plain reader before that try, the way
attached-graph recovery already reads it, and a mismatch throws in both
modes, leaving the WAL untouched. A header too short to hold the flag
still takes the lenient path. This covers the main and the frozen
checkpoint WAL, and read-only opens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frozen-WAL mismatch test now writes its WAL with the checksum
setting flipped, so together with the active-WAL test both directions
of a mismatch run in CI. The comment on tryReadWALChecksumFlag now says
which caller setting rethrows a torn header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/storage/wal/wal_replayer.cpp Outdated

static void checkWALHeader(const WALHeader& header, bool enableChecksums) {
if (enableChecksums != header.enableChecksums) {
static void checkWALChecksumFlag(bool walEnableChecksums, bool enableChecksums) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low (non-blocking, both call sites verified correct): two bare bools (walEnableChecksums, enableChecksums) read naturally in either order. Consider an enum or keeping the header-typed parameter so a swap is a compile error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e1210ec: tryReadWALHeader returns the header and checkWALHeader(const WALHeader&, bool) takes it, so the two flags can no longer be swapped silently.

@adsharma

adsharma commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

[ release notes are generated from PR descriptions, so no separate action needed ]

One callout from review (inline nit posted separately):

Intended behavior break — release note needed: opens that previously succeeded with throwOnWalReplayFailure=false (the C++/C engine default) now throw, including read-only opens. That is exactly the fix (previously 'success' meant silent loss of every WAL commit), and docs/comments are updated in-PR — but downstream/embedders on the lenient default will see a new throw where they got an empty-but-open DB. Release notes here are auto-generated from PR titles so no separate file to edit; just making sure the behavior break is explicit in the PR body before merging. (Re-posting: my earlier comment on this did not persist.)

@zahariash

Copy link
Copy Markdown
Contributor Author

Thanks — the body now has a Behavior change paragraph: read-write and read-only opens on the lenient default now throw instead of opening without the WAL's commits.

Two bare bools could be swapped at a call site without a compile error;
checkWALHeader takes the header again and tryReadWALHeader returns it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: opening with the other enableChecksums setting deletes the WAL

2 participants