How ZFS recovers a pool after a crash

Written by an AI pipeline from the sources listed below and checked automatically for tone and accuracy. It may still contain mistakes, so check the sources for anything important.
A pool crash leaves millions of blocks scattered across several disks, but ZFS does not scan them all. Instead it starts from a single record that lives on every disk: the uberblock. The uberblock stores the transaction-group number of the last successful sync and a pointer to the root of the pool’s metadata tree. From that root every other block can be reached, so the pool can be rebuilt in a few reads instead of a full scan.
The source describes a ring of uberblock slots on each vdev label. When a transaction group (txg) commits, ZFS writes a new uberblock to the next slot in the ring. The previous uberblock stays untouched, which means a power loss during the write cannot corrupt the older copy.
On import ZFS reads every slot, discards the ones that fail checksum or have the wrong magic number, and picks the valid uberblock with the highest txg. That uberblock becomes the entry point for the whole pool.
How the uberblock ring works
Each vdev label contains a fixed-size region (128 KiB) divided into equal slots. A slot is at least 1 KiB and never larger than 8 KiB, so the number of slots depends on the disk’s sector size. With 512-byte sectors the ring holds 128 slots; with 4 KiB sectors it holds 32 slots. The slot size is exactly one sector, so a write always aligns to the underlying hardware.
When a txg finishes, vdev_uberblock_sync computes the slot index as
int m = spa_multihost(vd->vdev_spa) ? MMP_BLOCKS_PER_LABEL : 0;
int n = (ub->ub_txg - (RRSS_GET_STATE(ub) == RRSS_SCRATCH_VALID)) %
(VDEV_UBERBLOCK_COUNT(vd) - m);
where m accounts for the Multi-Host Metadata Protection (MMP) block that occupies the last slot when multihost mode is on. The modulo operation makes the uberblocks rotate around the ring, guaranteeing that the newest copy never overwrites the one from the previous txg.
Because the ring is independent on each disk, a crash that damages one disk does not prevent the pool from being imported as long as at least one readable label contains a valid uberblock. The ring also provides natural wear-leveling: the same physical sector is not rewritten on every txg, only the slot that corresponds to the current modulo value.
Choosing the newest uberblock on import
During pool import ZFS reads every slot from every label on every readable vdev. The function vdev_uberblock_load_done is called for each slot. It first checks the I/O error code and runs uberblock_verify, which validates the magic number (0x00bab10c) and the per-slot checksum. If the slot passes, the uberblock is compared with the current best candidate using vdev_uberblock_compare.
The comparison orders by txg first, then by timestamp. The uberblock with the highest txg wins; if two have the same txg the later timestamp breaks the tie. The selected uberblock is stored in ubl_latest and later used to locate the MOS (Meta-Object Set) root block pointer.
Because every slot carries its own checksum, a corrupted slot is ignored automatically. The algorithm therefore tolerates both read errors and partially written uberblocks, which are common after an abrupt power loss.
What the uberblock structure contains
The on-disk layout is defined in include/sys/uberblock_impl.h of OpenZFS 2.4.4:
struct uberblock {
uint64_t ub_magic; /* UBERBLOCK_MAGIC */
uint64_t ub_version; /* SPA_VERSION */
uint64_t ub_txg; /* txg of last sync */
uint64_t ub_guid_sum; /* sum of all vdev guids */
uint64_t ub_timestamp; /* UTC time of last sync */
blkptr_t ub_rootbp; /* MOS objset_phys_t */
};
ub_magicmust equal0x00bab10c; any slot lacking this value is rejected as not an uberblock.ub_txgdrives the “newest-wins” rule on import.ub_guid_sumensures the uberblock belongs to the correct pool.ub_timestampprovides a tie-breaker and aids debugging.ub_rootbppoints to the MOS, the root of the pool’s object hierarchy; all other data blocks are reachable by traversing the tree that starts at this pointer.
The combination of these fields gives ZFS enough information to reconstruct the pool’s state without scanning the entire disk layout.
The takeaway
ZFS recovers a crashed pool by reading a small, rotating ring of uberblocks on each disk, discarding any that fail checksum or have the wrong magic, and selecting the one with the highest transaction-group number.
The uberblock’s concise structure, magic, version, txg, GUID sum, timestamp, and root block pointer, provides everything needed to locate the pool’s metadata tree and resume operation. Understanding the ring layout and the import selection algorithm helps diagnose import failures and explains why ZFS tolerates power loss without losing the last consistent state.