Skip to content

draid: fix data corruption after disk clear - #18294

Merged
behlendorf merged 1 commit into
openzfs:masterfrom
andriytk:cksumerrsfix
Mar 11, 2026
Merged

behlendorf merged 1 commit into
openzfs:masterfrom
andriytk:cksumerrsfix

Conversation

@andriytk

@andriytk andriytk commented Mar 7, 2026 •

Copy link
Copy Markdown
Contributor

Motivation and Context

Currently, when there there are several faulted disks with attached dRAID spares, and one of those disks is cleared from errors (zpool clear), followed by its spare being detached, the data in all the remaining spares that were attached while the cleared disk was in FAULTED state might get corrupted (which can be seen by running scrub). In some cases, when too many disks get cleared at a time, this can result in data corruption/loss.

Description

dRAID spare is a virtual device whose blocks are distributed among other disks. Those disks can be also in FAULTED state with attached spares on their own. When a disk gets sequentiall resilvered (rebuilt), the changes made by that resilvering won't get captured in the DTL (Dirty Time Log) of other FAULTED disks with the attached spares to which the data is written during the resilvering (as it would normally be done for the changes made by the user if a new file is written or some existing one is deleted). It is because sequential resilvering works on the block level, without touching or looking into metadata, so it doesn't know anything about the old BPs or transactions groups that it is resilvering. So later on, when that disk gets cleared from errors and healing resilvering is trying to sync all the data from its spare onto it, all the changes made on its spare during the resilvering of other disks will be missed because they won't be captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a scenario:

  1. d1 fails and gets resilvered to some spare s1 - OK.
  2. d2 fails and gets sequentially resilvered on draid spare s2. Now, in some slices, s2 would map to d1, which is failed. But d1 has s1 spare attached, so the data from that resilvering goes to s1, but not recorded in d1's DTL.
  3. Now, d1 gets cleared and its s1 gets detached. All the changes done by the user (writes or deletions) have their txgs captured in d1's DTL, so they will be resilvered by the healing resilver from its spare (s1) - that part works fine. But the data which was written during resilvering of d2 and went to s1 - that one will be missed from d1's DTL and won't get resilvered to it. So here we are:
  4. s2 under d2 is corrupted in the slices which map to d1, because d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and so on - all their spares will be corrupted. Because, in some slices, each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of non-writable devices during sequential resilvering so when healing resilver starts on disk clear, it would be able to check and heal blocks from all txgs.

How Has This Been Tested?

Here's a simple scenario to reproduce the problem:

  1. Create a pool with dRAID vdev and at least 2 dRAID spares and write some amount of data to it, around 70% of its capacity or at least 70G (depending on the number of children, in my case it was 30).
  2. Fail any disk (zpool offline -f).
  3. Wait until it gets sequentially resilvered and scan is finished.
  4. Fail another disk in a similar way.
  5. Again wait until it gets resilvered and scan is finished.
  6. Now, clear the 1st failed disk from errors (zpool clear).
  7. Wait until healing resilver is finished and spare is detached.
  8. Now scrub the pool (pool scrub).

Here's a helper script for this scenario:

#!/bin/bash

sudo systemctl stop zed

while true; do
  echo "try $((++c)), fail two random disks one after another..."
  disks=$(zpool status | grep 'wwn.*ONL' | shuf | head -2 | awk '{print $1}')
  i=0
  for d in $disks; do
    echo "failing $d..."
    sudo zpool offline -f testpool $d
    sudo zpool replace -s -w testpool $d "draid3-0-$((i++))"
  done
  zpool status
  echo "wait for scan to complete..."
  while ! zpool status | grep -q 'scan: scrub repaired'; do sleep 1; done
  zpool status | grep 'scan: scrub repaired 0B' || break
  zpool status
  echo "clearing 1st disk..."
  d1=$(zpool status | grep -B1 'draid3-0-0' | grep wwn | awk '{print $1}')
  sudo zpool clear testpool $d1
  echo "wait for resilver to complete..."
  while ! zpool status | grep -q 'scan: resilvered'; do sleep 1; done
  zpool status
  echo "scrub..."
  sudo zpool scrub testpool
  while ! zpool status | grep -q 'scan: scrub repaired'; do sleep 1; done
  zpool status | grep 'scan: scrub repaired 0B' || break
  echo "clearing pool..."
  sudo zpool clear testpool
  while ! zpool status | grep -q 'scan: resilvered'; do sleep 1; done
done

Without fix, it would reproduce on the 1st try on most of cases. With the fix, it didn't reproduce after running overnight.

I've also updated redundancy_draid_spare1 automation test which captures the problem in 30% cases without the patch and doesn't fail with the patch:

Results Summary
PASS	 298
FAIL	   2

Running Time:	00:36:10
Percent passed:	99.3%

(Those 2 failures were not after disk clear, and they seem to be caused by some other known issue(s), see commit d565835.)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Quality assurance (non-breaking change which makes the code more robust against bugs)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Library ABI change (libzfs, libzfs_core, libnvpair, libuutil and libzfsbootenv)
  • Documentation (a change to man pages or other documentation)

Checklist:

Closes #18286.

Comment thread module/zfs/vdev_draid.c Resolved
@andriytk
andriytk force-pushed the cksumerrsfix branch 4 times, most recently from 4cdfe51 to b07bee0 Compare March 8, 2026 18:37
Comment thread module/zfs/vdev.c Resolved
Comment thread module/zfs/vdev.c Resolved
Comment thread module/zfs/vdev.c Resolved

@akashb-22 akashb-22 left a comment

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.

LGTM.

@andriytk
andriytk force-pushed the cksumerrsfix branch 3 times, most recently from 12d583c to 2448181 Compare March 9, 2026 12:30
@andriytk
andriytk requested a review from behlendorf March 9, 2026 14:57

@behlendorf behlendorf left a comment

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.

This likely explains the rare checksum errors reported by redundancy_draid_spare[1,3] which both use zpool detach. Let's remove those exceptions from tests/test-runner/bin/zts-report.py.in so we can verify that's the case.

    'redundancy/redundancy_draid_spare1': ['FAIL', known_reason],
    'redundancy/redundancy_draid_spare3': ['FAIL', known_reason],

Comment thread module/zfs/vdev.c Resolved
@behlendorf behlendorf added the Status: Code Review Needed Ready for review and testing label Mar 9, 2026
@andriytk

andriytk commented Mar 9, 2026 •

Copy link
Copy Markdown
Contributor Author

This likely explains the rare checksum errors reported by redundancy_draid_spare[1,3] which both use zpool detach...

I'm afraid, it's not. I tried redundancy_draid_spare1 with the patch 100 times in the loop, and I still saw those rare cksum errors 2 times, as I mentioned in the description. It must be something else...

And it may happen even before zpool detach after only a single disk failure, for example:

20:58:39.28 SUCCESS: zpool create -O compression=off -f -m /var/tmp/testdir testpool draid2:5d:13c:2s /var/tmp/basedir.317136/vde
v0 /var/tmp/basedir.317136/vdev1 /var/tmp/basedir.317136/vdev2 /var/tmp/basedir.317136/vdev3 /var/tmp/basedir.317136/vdev4 /var/t
mp/basedir.317136/vdev5 /var/tmp/basedir.317136/vdev6 /var/tmp/basedir.317136/vdev7 /var/tmp/basedir.317136/vdev8 /var/tmp/basedi
r.317136/vdev9 /var/tmp/basedir.317136/vdev10 /var/tmp/basedir.317136/vdev11 /var/tmp/basedir.317136/vdev12
20:58:39.28 NOTE: Filling up the filesystem ...
20:58:40.52 SUCCESS: zpool sync testpool
20:58:40.53 SUCCESS: eval du -a /var/tmp/testdir > /var/tmp/basedir.317136/pre-record-file.317136 2>&1
20:58:40.89 SUCCESS: zpool offline -f testpool /var/tmp/basedir.317136/vdev0
20:58:40.90 SUCCESS: check_vdev_state testpool /var/tmp/basedir.317136/vdev0 FAULTED
20:58:46.05 SUCCESS: zpool replace -w -s testpool /var/tmp/basedir.317136/vdev0 draid2-0-0
20:58:46.06 SUCCESS: check_vdev_state spare-0 DEGRADED
20:58:46.06 SUCCESS: check_vdev_state draid2-0-0 ONLINE
20:58:46.08 SUCCESS: check_hotspare_state testpool draid2-0-0 INUSE
20:58:46.13 SUCCESS: zpool scrub testpool
20:58:47.18 SUCCESS: wait_scrubbed testpool
20:58:47.22   pool: testpool
20:58:47.22  state: DEGRADED
20:58:47.22 status: One or more devices are faulted in response to persistent errors.
20:58:47.22     Sufficient replicas exist for the pool to continue functioning in a
20:58:47.22     degraded state.
20:58:47.22 action: Replace the faulted device, or use 'zpool clear' to mark the device
20:58:47.22     repaired.
20:58:47.22   scan: scrub repaired 1K in 00:00:00 with 0 errors on Mon Mar  9 20:58:46 2026
20:58:47.22   scan: resilvered (draid2:5d:13c:2s-0) 54.6M in 00:00:05 with 0 errors on Mon Mar  9 20:58:45 2026
20:58:47.22 config:
20:58:47.22
20:58:47.22     NAME                                 STATE     READ WRITE CKSUM
20:58:47.22     testpool                             DEGRADED     0     0     0
20:58:47.22       draid2:5d:13c:2s-0                 DEGRADED     0     0     0
20:58:47.22         spare-0                          DEGRADED     0     0     1
20:58:47.22           /var/tmp/basedir.317136/vdev0  FAULTED      0     0     0  external device fault
20:58:47.22           draid2-0-0                     ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev1    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev2    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev3    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev4    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev5    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev6    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev7    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev8    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev9    ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev10   ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev11   ONLINE       0     0     0
20:58:47.22         /var/tmp/basedir.317136/vdev12   ONLINE       0     0     0
20:58:47.22     spares
20:58:47.22       draid2-0-0                         INUSE     currently in use
20:58:47.22       draid2-0-1                         AVAIL
20:58:47.22
20:58:47.22 errors: No known data errors

But it always happens after sequential resilvering, as far as I've noticed.

@behlendorf

Copy link
Copy Markdown
Contributor

I still saw those rare cksum errors 3 times...

Ah okay. That's good to know. I somehow missed that in your initial comment.

@amotin amotin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I hope we won't shoot ourself in a foot, declaring whole disks missing after few transient write errors.

Comment thread module/zfs/vdev.c Resolved
@andriytk
andriytk force-pushed the cksumerrsfix branch 2 times, most recently from 91462aa to 6b66a0c Compare March 9, 2026 20:28
@andriytk
andriytk requested a review from amotin March 9, 2026 20:34
@behlendorf

Copy link
Copy Markdown
Contributor

I hope we won't shoot ourself in a foot, declaring whole disks missing after few transient write errors.

Yeah, that's a valid concern. Marking the whole disk suspect does mean the mirror/raidz/draid code will skip this vdev when reading. Only if the checksum verification fails will the vdev code attempt to read the stale vdev in order to reconstruct the data. That's not great, but functionally it doesn't seem wrong. The vdev is missing data, and without a real mechanism to track those known missing offsets we can't really really trust it until a healing resilver or scrub completes (even if it is mostly intact).

@andriytk

Copy link
Copy Markdown
Contributor Author

I hope we won't shoot ourself in a foot, declaring whole disks missing after few transient write errors.

The testing shows that it doesn't. It only starts the whole disk healing resilver when there were other failed disks rebuilt already after that disk failure.

Here's how I tested it:

  1. Fail a disk on a pool with some data in it.
  2. Wait for it to be rebuilt (sequential resilver).
  3. Clear that disk.

The healing resilver after the clearing will complete almost immediately, much faster than the sequential resilver of the disk when it was faulted.

It looks like DTL of the faulted disk is not touched when it is being rebuilt. It's only when other disks are rebuilt to its spare its DTL is updated. Which is what we need.

Comment thread tests/zfs-tests/include/libtest.shlib Resolved
@behlendorf behlendorf added Status: Accepted Ready to integrate (reviewed, tested) and removed Status: Code Review Needed Ready for review and testing Status: Accepted Ready to integrate (reviewed, tested) labels Mar 10, 2026
@andriytk
andriytk marked this pull request as draft March 11, 2026 08:45
@github-actions github-actions Bot added Status: Work in Progress and removed Status: Code Review Needed Ready for review and testing labels Mar 11, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentiall resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes openzfs#18286
@andriytk
andriytk marked this pull request as ready for review March 11, 2026 09:57
@github-actions github-actions Bot added Status: Code Review Needed Ready for review and testing and removed Status: Work in Progress labels Mar 11, 2026
@andriytk
andriytk requested review from amotin and behlendorf March 11, 2026 09:58
@andriytk

andriytk commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor Author

The patch is updated, mainly with the changes suggested by @behlendorf:

  1. At vdev_stat_update(), pull the open txg from the dsl_pool_t. Added spa_open_txg() helper for this.
  2. Use the min and max txgs which were stored when starting the sequential resilver to set the DTL txg range.  One nice side effect is newly written txg won't be added to the DTL unless there were failed writes. Added vdev_rebuild_txgs() helper for this.
  3. Start vrp_min_txg at TXG_INITIAL. Technically not needed, but it's a bit nicer.

Comment thread module/zfs/vdev_rebuild.c Resolved
@amotin amotin added Status: Accepted Ready to integrate (reviewed, tested) and removed Status: Code Review Needed Ready for review and testing labels Mar 11, 2026
@andriytk

andriytk commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Testing result of redundancy tests run in loop 100 times:

$ ./scripts/zfs-tests.sh -T redundancy -I 100
...
Results Summary
PASS	 1699
FAIL	   1

Running Time:	08:29:17
Percent passed:	99.9%

The single failure was from redundancy_draid_spare1, which is the known issue:

10:44:10.76 SUCCESS: mkdir /var/tmp/basedir.692865
10:44:10.77 SUCCESS: truncate -s 268435456 /var/tmp/basedir.692865/vdev0 /var/tmp/basedir.692865/vdev1 /var/tmp/basedir.692865/vdev2 /var/tmp/basedir.692865/vdev3 /var/tmp/
basedir.692865/vdev4 /var/tmp/basedir.692865/vdev5 /var/tmp/basedir.692865/vdev6 /var/tmp/basedir.692865/vdev7 /var/tmp/basedir.692865/vdev8 /var/tmp/basedir.692865/vdev9 /
var/tmp/basedir.692865/vdev10 /var/tmp/basedir.692865/vdev11
10:44:10.85 SUCCESS: zpool create -O compression=off -f -m /var/tmp/testdir testpool draid1:1d:12c:3s /var/tmp/basedir.692865/vdev0 /var/tmp/basedir.692865/vdev1 /var/tmp/b
asedir.692865/vdev2 /var/tmp/basedir.692865/vdev3 /var/tmp/basedir.692865/vdev4 /var/tmp/basedir.692865/vdev5 /var/tmp/basedir.692865/vdev6 /var/tmp/basedir.692865/vdev7 /v
ar/tmp/basedir.692865/vdev8 /var/tmp/basedir.692865/vdev9 /var/tmp/basedir.692865/vdev10 /var/tmp/basedir.692865/vdev11
10:44:10.85 NOTE: Filling up the filesystem ...
10:44:11.23 SUCCESS: zpool sync testpool
10:44:11.24 SUCCESS: eval du -a /var/tmp/testdir > /var/tmp/basedir.692865/pre-record-file.692865 2>&1
10:44:11.45 SUCCESS: zpool offline -f testpool /var/tmp/basedir.692865/vdev0
10:44:11.46 SUCCESS: check_vdev_state testpool /var/tmp/basedir.692865/vdev0 FAULTED
10:44:11.51 SUCCESS: zpool replace -w -s testpool /var/tmp/basedir.692865/vdev0 draid1-0-0
10:44:11.52 SUCCESS: check_vdev_state spare-0 DEGRADED
10:44:11.52 SUCCESS: check_vdev_state draid1-0-0 ONLINE
10:44:11.53 SUCCESS: check_hotspare_state testpool draid1-0-0 INUSE
10:44:11.57 SUCCESS: zpool scrub testpool
10:44:12.63 SUCCESS: wait_scrubbed testpool
10:44:12.65 SUCCESS: verify_pool testpool
10:44:12.66 SUCCESS: check_pool_status testpool scan repaired 0B
10:44:12.68 SUCCESS: check_pool_status testpool scan with 0 errors
10:44:12.70 SUCCESS: zpool offline -f testpool /var/tmp/basedir.692865/vdev1
10:44:12.71 SUCCESS: check_vdev_state testpool /var/tmp/basedir.692865/vdev1 FAULTED
10:44:12.76 SUCCESS: zpool replace -w -s testpool /var/tmp/basedir.692865/vdev1 draid1-0-1
10:44:12.76 SUCCESS: check_vdev_state spare-1 DEGRADED
10:44:12.77 SUCCESS: check_vdev_state draid1-0-1 ONLINE
10:44:12.78 SUCCESS: check_hotspare_state testpool draid1-0-1 INUSE
10:44:12.81 SUCCESS: zpool detach testpool /var/tmp/basedir.692865/vdev1
10:44:12.84 SUCCESS: zpool scrub testpool
10:44:13.92 SUCCESS: wait_scrubbed testpool
10:44:13.96   pool: testpool
10:44:13.96  state: DEGRADED
10:44:13.96 status: One or more devices are faulted in response to persistent errors.
10:44:13.96     Sufficient replicas exist for the pool to continue functioning in a
10:44:13.96     degraded state.
10:44:13.96 action: Replace the faulted device, or use 'zpool clear' to mark the device
10:44:13.96     repaired.
10:44:13.96   scan: scrub repaired 2K in 00:00:00 with 0 errors on Wed Mar 11 10:44:12 2026
10:44:13.96   scan: resilvered (draid1:1d:12c:3s-0) 63.7M in 00:00:00 with 0 errors on Wed Mar 11 10:44:12 2026
10:44:13.96 config:
10:44:13.96
10:44:13.96     NAME                                 STATE     READ WRITE CKSUM
10:44:13.96     testpool                             DEGRADED     0     0     0
10:44:13.96       draid1:1d:12c:3s-0                 DEGRADED     0     0     0
10:44:13.96         spare-0                          DEGRADED     0     0     0
10:44:13.96           /var/tmp/basedir.692865/vdev0  FAULTED      0     0     0  external device fault
10:44:13.96           draid1-0-0                     ONLINE       0     0     0
10:44:13.96         draid1-0-1                       ONLINE       0     0     1
10:44:13.96         /var/tmp/basedir.692865/vdev2    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev3    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev4    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev5    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev6    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev7    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev8    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev9    ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev10   ONLINE       0     0     0
10:44:13.96         /var/tmp/basedir.692865/vdev11   ONLINE       0     0     0
10:44:13.96     spares
10:44:13.96       draid1-0-0                         INUSE     currently in use
10:44:13.96       draid1-0-1                         INUSE     currently in use
10:44:13.96       draid1-0-2                         AVAIL
10:44:13.96
10:44:13.96 errors: No known data errors
10:44:13.96 SUCCESS: zpool status -v
10:44:13.96 Unexpected CKSUM errors found on testpool (1)

@behlendorf

Copy link
Copy Markdown
Contributor

Thanks for the additional local testing, my local test results match up with yours. I'll wait for the remaining CI builder to wrap up and get this merged.

@behlendorf
behlendorf merged commit b403040 into openzfs:master Mar 11, 2026
42 of 46 checks passed
@andriytk
andriytk deleted the cksumerrsfix branch March 11, 2026 22:17
behlendorf pushed a commit to behlendorf/zfs that referenced this pull request Apr 7, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes openzfs#18286
Closes openzfs#18294
ofaaland pushed a commit to llnl/zfs that referenced this pull request Apr 17, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes openzfs#18286
Closes openzfs#18294
ofaaland pushed a commit to llnl/zfs that referenced this pull request Apr 17, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes openzfs#18286
Closes openzfs#18294
tonyhutter pushed a commit that referenced this pull request May 12, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes #18286
Closes #18294
tonyhutter pushed a commit that referenced this pull request May 12, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes #18286
Closes #18294
tonyhutter pushed a commit to tonyhutter/zfs that referenced this pull request Jun 8, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes openzfs#18286
Closes openzfs#18294
lundman pushed a commit to openzfsonosx/openzfs-fork that referenced this pull request Jul 30, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes openzfs#18286
Closes openzfs#18294
pull Bot pushed a commit to A-Archives-and-Forks/openzfs that referenced this pull request Sep 26, 2026
Currently, when there there are several faulted disks with attached
dRAID spares, and one of those disks is cleared from errors (zpool
clear), followed by its spare being detached, the data in all the
remaining spares that were attached while the cleared disk was in
FAULTED state might get corrupted (which can be seen by running scrub).
In some cases, when too many disks get cleared at a time, this can
result in data corruption/loss.

dRAID spare is a virtual device whose blocks are distributed among
other disks. Those disks can be also in FAULTED state with attached
spares on their own. When a disk gets sequentially resilvered (rebuilt),
the changes made by that resilvering won't get captured in the DTL
(Dirty Time Log) of other FAULTED disks with the attached spares to
which the data is written during the resilvering (as it would normally
be done for the changes made by the user if a new file is written or
some existing one is deleted). It is because sequential resilvering
works on the block level, without touching or looking into metadata,
so it doesn't know anything about the old BPs or transactions groups
that it is resilvering. So later on, when that disk gets cleared
from errors and healing resilvering is trying to sync all the data
from its spare onto it, all the changes made on its spare during the
resilvering of other disks will be missed because they won't be
captured in its DTL. That's why other dRAID spares may get corrupted.

Here's another way to explain it that might be helpful. Imagine a
scenario:

1. d1 fails and gets resilvered to some spare s1 - OK.
2. d2 fails and gets sequentially resilvered on draid spare s2. Now,
   in some slices, s2 would map to d1, which is failed. But d1 has s1
   spare attached, so the data from that resilvering goes to s1, but
   not recorded in d1's DTL.
3. Now, d1 gets cleared and its s1 gets detached. All the changes
   done by the user (writes or deletions) have their txgs captured
   in d1's DTL, so they will be resilvered by the healing resilver
   from its spare (s1) - that part works fine. But the data which
   was written during resilvering of d2 and went to s1 - that one
   will be missed from d1's DTL and won't get resilvered to it. So
   here we are:
4. s2 under d2 is corrupted in the slices which map to d1, because
   d1 doesn't have that data resilvered from s1.

Now, if there are more failed disks with draid spares attached which
were sequentially resilvered while d1 was failed, d3+s3, d4+s4 and
so on - all their spares will be corrupted. Because, in some slices,
each of them will map to d1 which will miss their data.

Solution: add all known txgs starting from TXG_INITIAL to DTLs of
non-writable devices during sequential resilvering so when healing
resilver starts on disk clear, it would be able to check and heal
blocks from all txgs.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Andriy Tkachuk <andriy.tkachuk@seagate.com>
Closes openzfs#18286
Closes openzfs#18294
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Accepted Ready to integrate (reviewed, tested)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CKSUM errors on dRAID spares after clearing disk with attached spare

4 participants