]> git.hungrycats.org Git - linux/commit
btrfs: raid56: drop cached rbios when their stripes' extents are freed
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Mon, 3 Aug 2026 07:24:38 +0000 (03:24 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:39:59 +0000 (17:39 -0400)
commitd6360dc9a25b287562b6fca968583327748566cb
treee7cf60d178ee209a0ee2169736d52ac159d50fb9
parent89b21ce65aa682ae88e2e0f2a4f7e03fad24e070
btrfs: raid56: drop cached rbios when their stripes' extents are freed

A cached rbio keeps an in-memory copy of its full stripe's data
sectors so that a later sub-stripe write can steal the pages (see
steal_rbio()) and skip the RMW read.  That is only safe while the
cache and the disk agree.  Nothing invalidates cache entries when the
stripe's extents are freed, so an entry can outlive the data it
describes: the space is reallocated, a sub-stripe write to the reused
stripe steals the stale pages, and parity is generated from the
cache's memory of the stripe's previous life instead of from disk.

If the on-disk content diverged from that memory in the meantime, the
result is silent, latent damage.  The divergence does not require a
second write: a device error that corrupts a column holding only
freed data is invisible (no live extent, no csum to fail, nothing for
scrub to check), but the stale cached copy of that column predates
the corruption.  The poisoned write then produces parity consistent
with the cache's memory and inconsistent with the actual on-disk
column.  Every read of the live data still passes csum and scrub
reports nothing, so the loss surfaces only when a device failure
forces reconstruction -- which XORs the real on-disk garbage against
parity that remembers different bytes, and hands back trash.

This was found and proven byte-exactly on a raid5 test array: a
stripe's parity solved uniquely as the XOR of fresh data with the
PRE-corruption content of a free-space column -- bytes that existed
nowhere on disk at write time and could only have come from the rbio
cache.  The resulting single-column loss was undetectable by scrub
and unrecoverable by reconstruction.

Drop cache entries overlapping freed extents at unpin time, when the
space returns to circulation.  The walk is gated on raid56 block
groups, so filesystems without raid56 never touch it, and the LRU
list is bounded (RBIO_CACHE_SIZE entries).

The cost of dropping entries was measured on an instrumented stock
kernel that marks affected entries instead of dropping them, and
counts a reintroduced RMW full-stripe read whenever a read-skip was
only possible because of a marked entry.  In a worst-case free-heavy
small-file churn on a 5-device raid5 array with raid5 metadata --
conditions chosen to flatter the cache, and it delivers, with 87% of
sub-stripe RMWs skipping their read -- only 1% of those skips (64 of
6430) were enabled by entries this patch drops, costing 0.04-0.12
MB/s of extra reads against the workload's own 0.3-0.7 MB/s of
writes.  93% of the entries the patch drops were evicted without
ever being stolen again.

Assisted-by: Claude:claude-fable-5
fs/btrfs/extent-tree.c
fs/btrfs/raid56.c
fs/btrfs/raid56.h