ZFS pool import is an atomic operation. The kernel validates vdev labels, reads the uberblock ring, replays the ZIL, and loads the DDT into RAM. Any step that fails its checksum or runs out of resources aborts the entire import and reports a generic I/O error. The real cause is almost always one of these six.
DDT RAM Exhaustion
Dedup tables must fit in memory at import. The working rule is approximately 5 GB of RAM per 1 TB of deduplicated data. A pool that ran fine on a 128 GB box can kernel-panic a 64 GB recovery host.
Missing SLOG vdev
A dedicated NVMe or SSD log device disappears. ZFS refuses to import without confirmation. The actual data loss is bounded: only sync writes since the last txg commit.
Uberblock Corruption
The 128-entry uberblock ring on every vdev label points to past transaction groups. If the newest uberblocks are corrupted, the pool needs a manual rewind to an older valid TXG.
Vdev Label Damage
Each member stores four labels (L0/L1 at the front, L2/L3 at the back). Severe power events or partial overwrites can destroy all four on a single drive, even though the data blocks are intact.
Member Drive Failure During TXG
A drive develops bad sectors mid-write. The TXG commit half-completes. The uberblock that references the failed block cannot be validated. Forensic rewind to the previous TXG recovers the pool.
Hardware RAID Hiding Errors
ZFS deployed on top of PERC, MegaRAID, or Smart Array in RAID mode. The controller masks individual drive errors until the virtual disk collapses, presenting ZFS with a coherent failure rather than a recoverable one.
dRAID Vdev Topology: Why the Config String Is Not Optional
OpenZFS 2.1 and later, including current TrueNAS SCALE builds, add a distributed-parity vdev type called dRAID. It is not a friendlier RAIDZ. The layout is declustered: data, parity, and spare capacity are permuted across every member instead of sitting on fixed columns, which makes a dRAID vdev harder to reconstruct offline, not easier. The whole topology is described by a config string written into the vdev label on every member:
# dRAID config string grammar: draid[parity]:[data]d:[children]c:[spares]s
draid2:4d:11c:1s
# parity = 2 redundancy level per redundancy group (1, 2, or 3)
# 4d = 4 data devices per redundancy group
# 11c = 11 total member devices in the vdev
# 1s = 1 distributed spare (sliced across all children, not a whole drive)
That draid2:4d:11c:1s string is required to compute where any given block lives in the permutation layout. Read the exact topology wrong and the block positions come out wrong. When we reconstruct a dRAID vdev from cloned images, the first job is reading that string off the surviving vdev labels; there is no recovering block geometry without it.
The distributed spare is the field most people misread. It is not a dedicated hot-spare drive sitting idle. It is pre-allocated spare capacity sliced and interleaved across all children in the same permutation.
When a member fails, the sequential resilver writes the reconstructed data into those distributed-spare positions across the surviving drives, not onto one separate replacement disk. That is why a dRAID resilver restores redundancy faster than a RAIDZ healing resilver: it is a fixed-width sequential rebuild reading from all children at once, at the cost of higher whole-array I/O load while it runs.
Fault tolerance is still the parity level and nothing more. A draid2 vdev survives two lost members; lose a third before the resilver finishes and the vdev FAULTS and the pool goes offline, same math as raidz2.
We image every member through a write-blocker, read the topology string off the labels, and reconstruct on a forensic host using PC-3000 Express and Data Extractor Express RAID Edition. The declustered layout takes the same enterprise TrueNAS handling covered on our TrueNAS server recovery workflow.