-
Type:
Improvement
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: Block Manager
-
None
-
Storage Engines - Persistence
-
10.87
-
SE Persistence backlog
-
None
Summary
When a block read fails its checksum, __wti_block_read_off() reports the two checksums and dumps the raw block, but it never reports the fields already sitting in the block header it just read. In particular it never compares the header's disk_size against the size in the address cookie.
That single comparison distinguishes two failure modes that currently look identical in the logs and require manual hex decoding to tell apart:
- the right block, with corrupt bytes – disk_size matches the cookie size, the header is otherwise coherent. Consistent with bit rot in the block, and the existing single-bit flip detection is the right next step.
- the wrong block – disk_size disagrees with the cookie size, i.e. the bytes at that offset are a structurally valid block of a different size. Consistent with a stale, misdirected or lost read/write in the storage stack. Bit flip analysis is meaningless here, and the remediation is completely different.
Today both print the same "potential hardware corruption, read checksum error" line, so every occurrence needs someone to hand-decode the corrupt dump to work out which one it was. That decoding step is also awkward because the raw dump can contain user data, so it cannot simply be pasted around for analysis. Reporting the derived header fields instead avoids that entirely.
Current state on develop
In src/block/block_read.c, the failure path emits the checksum mismatch message, calls _wt_log_data_dump(), dumps free space and the extent lists, and then runs _block_bitflip_detect(). blk->disk_size is read into swap at the top of the retry loop and used for the swap.checksum comparison, but swap.disk_size is never looked at. Its only other uses in the block manager are salvage and checkpoint scan, where the expected size genuinely is not known, plus the write path that sets it.
Note also that __block_bitflip_detect() is called only under if (full_checksum_mismatch), so on the block header checksum branch – where swap.checksum did not match the cookie at all – no flip analysis runs and there is currently nothing beyond the two checksum values to go on. That branch is exactly where the header-field comparison adds the most.
Proposal
On the mismatch path, before or alongside the existing dump, log the decoded header and an explicit verdict on the size comparison. Suggested fields:
- swap.disk_size versus the size from the address cookie, called out when they differ.
- dsk->mem_size – an implausible value relative to disk_size is itself a signal, and a compressed block should have mem_size > disk_size.
- dsk->write_gen – lets a reader reason about whether the block is older than expected, which is the tell for a stale read.
- dsk->type, dsk->flags, dsk->version – a coherent, valid combination points to a real block from elsewhere rather than to random corruption.
Two cheap additions in the same area:
- On the block header checksum branch, test whether swap.checksum is a single-bit flip of the expected checksum, i.e. __builtin_popcount(swap.checksum ^ checksum) == 1. That is a 32-bit test with no scan cost and it answers "did the stored checksum field itself take a flip" directly.
- Consider running the existing bitflip detection on the header checksum branch too. It needs blk->checksum zeroed first, since that branch does not zero it, so the ordering matters and should be handled carefully.
All of this is diagnostic output only. No behavioural change: the read still fails and still panics as it does today.
Why it is worth doing
Every one of these events is a one-shot, unreproducible production failure. The block is gone by the time anyone looks, and the raw dump is often unavailable, partially captured, or handled under data-handling restrictions. Deriving the classification at the point of failure, when the block is in hand, is the only reliable way to get it – and it turns a multi-week manual investigation into reading one log line.
Suggested implementation notes
- Emit as a separate __wt_errx_id() message next to the existing mismatch message so it survives log filtering and can be matched on.
- Format the size comparison so the mismatch case is unambiguous and greppable, rather than printing two numbers and leaving the reader to compare them.
- Use the byte-swapped swap copy for the block header fields, and remember the page header is not byte-swapped on this path, so handle endianness explicitly rather than reading dsk fields directly.
- Guard against a wild mem_size/disk_size when formatting; the whole point is that these values are untrusted.
Testing
- Unit test alongside test/catch2/block/unit/test_block_bitflip.cpp covering the classification: a block whose header disk_size matches the requested size, and one where it does not, asserting the right message is produced.
- A Python test that corrupts a block on disk and checks the new output appears, in the same style as the existing corrupt-block tests.
Definition of done
- Header fields reported on the checksum mismatch path, with an explicit call-out when disk_size disagrees with the requested size.
- Single-bit test on the stored checksum for the block header checksum branch.
- Tests covering both classifications.
- No behavioural change to the read or panic path.
Related: WT-15979 covers checking the read block against the checksum in its own header, which is adjacent but distinct – this ticket is about surfacing the header's other fields for classification.
- is related to
-
WT-15979 On checksum mismatch compute the checksum of the read block ensuring it matches the checksum in the block header
-
- Open
-