Sala 2

El árbol y el uberblock

ZFS no tiene fsck. Tras un fallo, el pool debe abrirse o bien en el estado anterior al último grupo de transacciones, o bien en el estado posterior, nunca en una mezcla de ambos, aunque un solo txg escriba miles de bloques en varios discos.

Así que todo el cambio de estado tiene que reducirse a una sola escritura pequeña que ha ocurrido o no. Y esa escritura no debe poder destruir el estado anterior si se va la corriente a mitad de ella. El código lo consigue con un árbol que cuelga de un único puntero, una regla según la cual un bloque vivo nunca se sobrescribe, y un orden de escrituras al final de cada txg.

1 Todo cuelga de un puntero

El uberblock contiene un solo puntero de bloque, ub_rootbp. Apunta al meta objset del pool, el MOS, que empieza por un dnode, el meta-dnode, cuyos bloques son bloques de dnodes. Un dnode es la raíz de un objeto: sus dn_blkptr[] apuntan a dn_nlevels − 1 niveles de bloques indirectos, y el último nivel apunta a los datos. Cada puntero es un blkptr_t de 128 bytes que contiene hasta tres direcciones y la suma de comprobación del bloque de debajo. Desde el uberblock hacia abajo, no hace falta nada más para encontrar cada bloque del pool. La figura dibuja un archivo con tres niveles y cuatro bloques de datos bajo el objset.

uberblock · txg 4210ranura 114 · ub_rootbpobjset4210objset4211dnodes4210dnodes4211L2 n.º 04210L2 n.º 04211L1 n.º 04190L1 n.º 14210L1 n.º 14211D04190D14120D24180D24211D34210
  • copia antigua
  • en memoria, sin escribir

Vista 3D del mismo estado. Arrastra, o usa las flechas sobre la vista activa, para girarla; + y − para acercar o alejar. Todos los controles de arriba funcionan igual en 3D.

L04200L14200L24200L34200arranquebloques

La confirmación

El txg 4211 está abierto: 5 bloques por escribir. Paso siguiente: spa_sync los escribe.

  1. siguiente spa_sync escribe los bloques nuevos del txg 4211, cada uno en un lugar nuevo. código
  2. pendiente Volcado: cada disco escrito en este txg vuelca su caché de escritura. código
  3. pendiente Etiquetas pares: L0 y L2 reciben la configuración del txg 4211. código
  4. pendiente Uberblocks: el txg 4211 va a la ranura 115 de los cuatro anillos. código
  5. pendiente Etiquetas impares: L1 y L3 reciben la configuración del txg 4211. código

Última importación

Aún no hay ninguna importación. Desenchufa en cualquier paso y después importa el pool.

En el disco

EtiquetaEmpieza en códigoConfiguraciónRanura 114 (+242 KiB)Ranura 115 (+243 KiB)
L00 MiBtxg 4200txg 4210txg 4083
L10,25 MiBtxg 4200txg 4210txg 4083
L21023,5 MiBtxg 4200txg 4210txg 4083
L31023,75 MiBtxg 4200txg 4210txg 4083

Un disco de 1 GiB, ashift 9: cada anillo tiene 128 ranuras de 1 KiB. No está a escala: una etiqueta ocupa 256 KiB, la zona de arranque 3,5 MiB, el resto contiene los bloques.

Versión en texto de esta figura

El uberblock más reciente en el disco es el del txg 4210, en la ranura 114. Apunta al objset del txg 4210. El árbol que alcanza: objset (txg 4210), dnodes (txg 4210), L2 n.º 0 (txg 4210), L1 n.º 0 (txg 4190), L1 n.º 1 (txg 4210), D0 (txg 4190), D1 (txg 4120), D2 (txg 4180), D3 (txg 4210).

El txg 4211 tiene copias nuevas de objset, dnodes, L2 n.º 0, L1 n.º 1 y D2; El txg 4211 está abierto: 5 bloques por escribir. Paso siguiente: spa_sync los escribe.

Etiquetas: L0 configuración txg 4200, ranura 115 txg 4083; L1 configuración txg 4200, ranura 115 txg 4083; L2 configuración txg 4200, ranura 115 txg 4083; L3 configuración txg 4200, ranura 115 txg 4083.

2 Una escritura copia todo su camino

Un bloque modificado nunca se escribe en su sitio: recibe una dirección nueva. Su padre contiene su puntero, dirección y suma de comprobación, así que el padre también cambia: dbuf_dirty marca al padre como modificado, y así sucesivamente hasta el dnode, marcado como modificado en su bloque de dnodes, hasta el objset. Un bloque de datos modificado da una copia nueva de sí mismo y de cada bloque de su camino hasta la raíz; todos los demás bloques los comparten el árbol antiguo y el nuevo. El árbol antiguo queda intacto: el último uberblock sincronizado lo sigue alcanzando entero. En la figura, escribe D2 y después D3 en el mismo txg: sus ancestros comunes se copian una sola vez. La sala siguiente sigue las sumas de comprobación a lo largo de ese camino.

3 La confirmación, en cuatro fases

Al final de un txg, una vez que spa_sync ha escrito los bloques nuevos, vdev_config_sync ejecuta cuatro pasos en un orden fijo. Primero vuelca la caché de escritura de cada disco escrito en el txg, para que el árbol nuevo esté en almacenamiento estable antes de que nada apunte a él. Después escribe las etiquetas pares, L0 y L2, con la configuración nueva. Después el uberblock, en la ranura ub_txg módulo el número de ranuras de las cuatro etiquetas, que el uberblock del txg anterior no ocupa: esa escritura es la confirmación, el momento en que el puntero raíz pasa al árbol nuevo. Por último, las etiquetas impares, L1 y L3. Cada disco lleva cuatro etiquetas de 256 KiB, dos al principio y dos al final, y cada etiqueta termina con un anillo de 128 KiB de ranuras de uberblock: 128 ranuras con los sectores de 512 bytes de la figura, 32 en discos con sectores de 4K.

Las etiquetas solo se escriben para los vdevs cuya configuración ha cambiado en este txg. En la mayoría de los txg no ha cambiado ninguna: las fases 2 y 4 no escriben nada, y solo el uberblock es nuevo. Desactiva «La configuración de los vdevs cambia en este txg» y recorre de nuevo los pasos.

4 Desenchufar y después importar

Al importar, ZFS lee cada ranura de cada etiqueta. Una ranura solo cuenta si su suma de comprobación y su número mágico son correctos, así que una escritura rota se ignora; entre las demás, gana el ub_txg más alto. Después se lee la configuración de la etiqueta con la configuración más reciente que no sea más reciente que ese uberblock. Desenchufa en cada paso e importa. Antes de la fase 3 no existe ningún uberblock del txg nuevo: gana el antiguo, las etiquetas pares ya escritas son más recientes que él y se descartan, las impares aún le corresponden. En cuanto ha llegado una copia del uberblock nuevo, esta gana, y las etiquetas pares le corresponden. El árbol es el antiguo o el nuevo, nunca una mezcla. Elige cuántas escrituras de uberblock llegan antes de un corte en la fase 3: cero da el caso (1) del comentario del código, una o más el caso (2).

La idea, a tres profundidades

En una frase

Cada bloque del pool cuelga del único puntero del uberblock, una escritura copia su camino en lugar de sobrescribirlo, y el uberblock nuevo va a una ranura que el antiguo no ocupa, una vez que el árbol nuevo está en el disco: así, el pool es el árbol antiguo o el nuevo, sea cual sea el momento en que se va la corriente.

En un párrafo

ub_rootbp apunta al objset_phys_t del MOS; los bloques de su meta-dnode contienen dnodes; los dn_blkptr[] de un dnode llevan, a través de dn_nlevels − 1 niveles de bloques indirectos, hasta los datos. dbuf_dirty marca como modificado al padre de un bloque, recursivamente, de modo que una hoja modificada da, en la sincronización, copias nuevas de la hoja y de todos sus ancestros, cada una en una dirección nueva. vdev_config_sync vuelca entonces los discos escritos en el txg, escribe la configuración de las etiquetas pares, escribe el uberblock en la ranura ub_txg % VDEV_UBERBLOCK_COUNT de las cuatro etiquetas y lo vuelca, y escribe las etiquetas impares. Al importar, vdev_uberblock_load se queda con el uberblock válido de txg más alto, y vdev_label_read_config toma la etiqueta cuyo txg de configuración es el más alto que no lo supera. Un corte antes de que llegue ningún uberblock nuevo deja el uberblock antiguo y las etiquetas impares; un corte después de que llegue uno deja el uberblock nuevo y las etiquetas pares. Cualquiera de los dos pares es coherente.

En detalle

La cima del árbol. El uberblock nombra un txg y un puntero de bloque.

struct uberblock Extracto de include/sys/uberblock_impl.h, líneas 122–139, OpenZFS zfs-2.4.4. Ver en GitHub
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		*/

	/* highest SPA_VERSION supported by software that wrote this txg */
	uint64_t	ub_software_version;

	/* Maybe missing in uberblocks we read, but always written */
	uint64_t	ub_mmp_magic;	/* MMP_MAGIC			*/
	/*
	 * If ub_mmp_delay == 0 and ub_mmp_magic is valid, MMP is off.
	 * Otherwise, nanosec since last MMP write.
	 */
	uint64_t	ub_mmp_delay;
  1. L125 ub_txg: el txg que este uberblock confirma. Al importar, gana el más alto de los válidos.
  2. L127 ub_timestamp: el criterio de desempate entre dos uberblocks del mismo txg.
  3. L128 ub_rootbp: el único puntero. dsl_pool_sync_mos lo fija una vez escrito el MOS.
objset_phys_t Extracto de include/sys/dmu_objset.h, líneas 80–95, OpenZFS zfs-2.4.4. Ver en GitHub
typedef struct objset_phys {
	dnode_phys_t os_meta_dnode;
	zil_header_t os_zil_header;
	uint64_t os_type;
	uint64_t os_flags;
	uint8_t os_portable_mac[ZIO_OBJSET_MAC_LEN];
	uint8_t os_local_mac[ZIO_OBJSET_MAC_LEN];
	char os_pad0[OBJSET_PHYS_SIZE_V2 - sizeof (dnode_phys_t)*3 -
	    sizeof (zil_header_t) - sizeof (uint64_t)*2 -
	    2*ZIO_OBJSET_MAC_LEN];
	dnode_phys_t os_userused_dnode;
	dnode_phys_t os_groupused_dnode;
	dnode_phys_t os_projectused_dnode;
	char os_pad1[OBJSET_PHYS_SIZE_V3 - OBJSET_PHYS_SIZE_V2 -
	    sizeof (dnode_phys_t)];
} objset_phys_t;
  1. L81 Un objset empieza por un dnode entero, el meta-dnode. Sus bloques son los bloques de dnodes del objset: el objeto n es el dnode n. El objset propio de un sistema de archivos cuelga del MOS de la misma manera, por el ds_bp de su dataset.
dnode_phys_t Extracto de include/sys/dnode.h, líneas 213–229, OpenZFS zfs-2.4.4. Ver en GitHub
typedef struct dnode_phys {
	uint8_t dn_type;		/* dmu_object_type_t */
	uint8_t dn_indblkshift;		/* ln2(indirect block size) */
	uint8_t dn_nlevels;		/* 1=dn_blkptr->data blocks */
	uint8_t dn_nblkptr;		/* length of dn_blkptr */
	uint8_t dn_bonustype;		/* type of data in bonus buffer */
	uint8_t	dn_checksum;		/* ZIO_CHECKSUM type */
	uint8_t	dn_compress;		/* ZIO_COMPRESS type */
	uint8_t dn_flags;		/* DNODE_FLAG_* */
	uint16_t dn_datablkszsec;	/* data block size in 512b sectors */
	uint16_t dn_bonuslen;		/* length of dn_bonus */
	uint8_t dn_extra_slots;		/* # of subsequent slots consumed */
	uint8_t dn_pad2[3];

	/* accounting is protected by dn_dirty_mtx */
	uint64_t dn_maxblkid;		/* largest allocated block ID */
	uint64_t dn_used;		/* bytes (or sectors) of disk space */
  1. L215 El tamaño de los bloques indirectos del objeto, 128 KiB como máximo: 1024 punteros de bloque de 128 bytes.
  2. L216 La altura del árbol del objeto; 1 cuando el dnode apunta directamente a los datos.
  3. L217 Cuántos de los punteros raíz, dn_blkptr[], se usan.

Una escritura sube hasta la raíz.

dbuf_dirty Extracto de module/zfs/dbuf.c, líneas 2537–2554, OpenZFS zfs-2.4.4. Ver en GitHub
	if (db->db_level+1 < dn->dn_nlevels) {
		dmu_buf_impl_t *parent = db->db_parent;
		dbuf_dirty_record_t *di;
		int parent_held = FALSE;

		if (db->db_parent == NULL || db->db_parent == dn->dn_dbuf) {
			int epbs = dn->dn_indblkshift - SPA_BLKPTRSHIFT;
			parent = dbuf_hold_level(dn, db->db_level + 1,
			    db->db_blkid >> epbs, FTAG);
			ASSERT(parent != NULL);
			parent_held = TRUE;
		}
		if (drop_struct_rwlock)
			rw_exit(&dn->dn_struct_rwlock);
		ASSERT3U(db->db_level + 1, ==, parent->db_level);
		di = dbuf_dirty(parent, tx);
		if (parent_held)
			dbuf_rele(parent, FTAG);
  1. L2537 Un bloque por debajo del nivel superior de su objeto tiene un bloque indirecto padre.
  2. L2542–L2545 El padre está en el nivel + 1, bloque número blkid >> epbs, con 2epbs punteros por bloque indirecto (1024 para 128 KiB). La figura usa 2 para no crecer demasiado.
  3. L2552 Marcar un bloque como modificado marca a su padre, y así hacia arriba; el nivel superior lo contiene el dnode, marcado a su vez. Cada bloque modificado se escribe en un lugar nuevo en la sincronización.

Las cuatro fases. Primero el volcado.

vdev_config_sync Extracto de module/zfs/vdev_label.c, líneas 2085–2098, OpenZFS zfs-2.4.4. Ver en GitHub
	/*
	 * Flush the write cache of every disk that's been written to
	 * in this transaction group.  This ensures that all blocks
	 * written in this txg will be committed to stable storage
	 * before any uberblock that references them.
	 */
	zio_t *zio = zio_root(spa, NULL, NULL, flags);

	for (vdev_t *vd =
	    txg_list_head(&spa->spa_vdev_txg_list, TXG_CLEAN(txg)); vd != NULL;
	    vd = txg_list_next(&spa->spa_vdev_txg_list, vd, TXG_CLEAN(txg)))
		zio_flush(zio, vd);

	(void) zio_wait(zio);
  1. L2085–L2090 La razón del volcado, en palabras del código: los bloques de este txg llegan a almacenamiento estable «before any uberblock that references them», antes de cualquier uberblock que los referencie.
  2. L2093–L2098 Cada vdev escrito en este txg se vuelca; la espera los cubre a todos.
vdev_config_sync Extracto de module/zfs/vdev_label.c, líneas 2100–2139, OpenZFS zfs-2.4.4. Ver en GitHub
	/*
	 * Sync out the even labels (L0, L2) for every dirty vdev.  If the
	 * system dies in the middle of this process, that's OK: all of the
	 * even labels that made it to disk will be newer than any uberblock,
	 * and will therefore be considered invalid.  The odd labels (L1, L3),
	 * which have not yet been touched, will still be valid.  We flush
	 * the new labels to disk to ensure that all even-label updates
	 * are committed to stable storage before the uberblock update.
	 */
	if ((error = vdev_label_sync_list(spa, 0, txg, flags)) != 0) {
		if ((flags & ZIO_FLAG_IO_RETRY) != 0) {
			zfs_dbgmsg("vdev_label_sync_list() returned error %d "
			    "for pool '%s' when syncing out the even labels "
			    "of dirty vdevs", error, spa_name(spa));
		}
		goto retry;
	}

	/*
	 * Sync the uberblocks to all vdevs in svd[].
	 * If the system dies in the middle of this step, there are two cases
	 * to consider, and the on-disk state is consistent either way:
	 *
	 * (1)	If none of the new uberblocks made it to disk, then the
	 *	previous uberblock will be the newest, and the odd labels
	 *	(which had not yet been touched) will be valid with respect
	 *	to that uberblock.
	 *
	 * (2)	If one or more new uberblocks made it to disk, then they
	 *	will be the newest, and the even labels (which had all
	 *	been successfully committed) will be valid with respect
	 *	to the new uberblocks.
	 */
	if ((error = vdev_uberblock_sync_list(svd, svdcount, ub, flags)) != 0) {
		if ((flags & ZIO_FLAG_IO_RETRY) != 0) {
			zfs_dbgmsg("vdev_uberblock_sync_list() returned error "
			    "%d for pool '%s'", error, spa_name(spa));
		}
		goto retry;
	}
  1. L2100–L2108 La fase 2, y lo que significa un corte durante ella: las etiquetas pares más recientes que cualquier uberblock no son válidas, las impares siguen siéndolo.
  2. L2109 Las etiquetas pares: vdev_label_sync escribe las etiquetas l y l + 2, L0 y L2.
  3. L2118–L2131 La fase 3, y los dos casos de un corte durante ella: no llegó ningún uberblock nuevo, o llegó al menos uno. La figura reproduce los dos.
  4. L2133 Los uberblocks; vdev_uberblock_sync_list los vuelca después, y tiene éxito si al menos una escritura lo tuvo.
vdev_uberblock_sync Extracto de module/zfs/vdev_label.c, líneas 1768–1797, OpenZFS zfs-2.4.4. Ver en GitHub
	/*
	 * We chose a slot based on the txg.  If this uberblock has a special
	 * RAIDZ expansion state, then it is essentially an update of the
	 * current uberblock (it has the same txg).  However, the current
	 * state is committed, so we want to write it to a different slot. If
	 * we overwrote the same slot, and we lose power during the uberblock
	 * write, and the disk does not do single-sector overwrites
	 * atomically (even though it is required to - i.e. we should see
	 * either the old or the new uberblock), then we could lose this
	 * txg's uberblock. Rewinding to the previous txg's uberblock may not
	 * be possible because RAIDZ expansion may have already overwritten
	 * some of the data, so we need the progress indicator in the
	 * uberblock.
	 */
	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);

	/* Copy the uberblock_t into the ABD */
	abd_t *ub_abd = abd_alloc_for_io(VDEV_UBERBLOCK_SIZE(vd), B_TRUE);
	abd_copy_from_buf(ub_abd, ub, sizeof (uberblock_t));
	abd_zero_off(ub_abd, sizeof (uberblock_t),
	    VDEV_UBERBLOCK_SIZE(vd) - sizeof (uberblock_t));

	for (int l = 0; l < VDEV_LABELS; l++)
		vdev_label_write(zio, vd, l, ub_abd,
		    VDEV_UBERBLOCK_OFFSET(vd, n), VDEV_UBERBLOCK_SIZE(vd),
		    vdev_uberblock_sync_done, good_writes,
		    flags | ZIO_FLAG_DONT_PROPAGATE);
  1. L1768–L1781 Por qué la ranura debe ser distinta de la actual: una sobrescritura rota del uberblock confirmado podría perderlo. El comentario trata de la expansión de RAID-Z, que reutiliza un txg; la regla de la ranura, más abajo, sirve para todos los txg.
  2. L1782–L1784 La ranura: ub_txg módulo el número de ranuras (una menos con multihost). Los txg consecutivos ocupan ranuras consecutivas; el txg siguiente nunca cae en la ranura del anterior.
  3. L1792–L1796 La misma ranura en las cuatro etiquetas del disco.
vdev_config_sync Extracto de module/zfs/vdev_label.c, líneas 2144–2161, OpenZFS zfs-2.4.4. Ver en GitHub
	/*
	 * Sync out odd labels for every dirty vdev.  If the system dies
	 * in the middle of this process, the even labels and the new
	 * uberblocks will suffice to open the pool.  The next time
	 * the pool is opened, the first thing we'll do -- before any
	 * user data is modified -- is mark every vdev dirty so that
	 * all labels will be brought up to date.  We flush the new labels
	 * to disk to ensure that all odd-label updates are committed to
	 * stable storage before the next transaction group begins.
	 */
	if ((error = vdev_label_sync_list(spa, 1, txg, flags)) != 0) {
		if ((flags & ZIO_FLAG_IO_RETRY) != 0) {
			zfs_dbgmsg("vdev_label_sync_list() returned error %d "
			    "for pool '%s' when syncing out the odd labels of "
			    "dirty vdevs", error, spa_name(spa));
		}
		goto retry;
	}
  1. L2145–L2152 La fase 4: las etiquetas impares, al final. Un corte aquí deja las etiquetas pares y los uberblocks nuevos, que bastan.
  2. L2154 La misma función que en la fase 2, con l = 1: L1 y L3.

Qué discos reciben etiquetas y uberblocks: solo los vdevs de la lista de modificados reciben etiquetas; con esa lista vacía, el uberblock va a tres vdevs de primer nivel como máximo.

spa_sync_rewrite_vdev_config Extracto de module/zfs/spa.c, líneas 10792–10819, OpenZFS zfs-2.4.4. Ver en GitHub
		if (list_is_empty(&spa->spa_config_dirty_list)) {
			vdev_t *svd[SPA_SYNC_MIN_VDEVS] = { NULL };
			int svdcount = 0;
			int children = rvd->vdev_children;
			int c0 = random_in_range(children);

			for (int c = 0; c < children; c++) {
				vdev_t *vd =
				    rvd->vdev_child[(c0 + c) % children];

				/* Stop when revisiting the first vdev */
				if (c > 0 && svd[0] == vd)
					break;

				if (vd->vdev_ms_array == 0 ||
				    vd->vdev_islog ||
				    !vdev_is_concrete(vd))
					continue;

				svd[svdcount++] = vd;
				if (svdcount == SPA_SYNC_MIN_VDEVS)
					break;
			}
			error = vdev_config_sync(svd, svdcount, txg);
		} else {
			error = vdev_config_sync(rvd->vdev_child,
			    rvd->vdev_children, txg);
		}
  1. L10792–L10815 Sin cambio de configuración: tres vdevs de primer nivel como máximo, a partir de uno elegido al azar, saltando los vdevs log y los vdevs sin metaslabs. Un pool de un solo disco recibe cada uberblock en ese disco.
  2. L10816–L10818 Un cambio de configuración: cada vdev de primer nivel.

Dónde están las etiquetas.

vdev_label_offset Extracto de module/zfs/vdev_label.c, líneas 158–170, OpenZFS zfs-2.4.4. Ver en GitHub
/*
 * Basic routines to read and write from a vdev label.
 * Used throughout the rest of this file.
 */
uint64_t
vdev_label_offset(uint64_t psize, int l, uint64_t offset)
{
	ASSERT(offset < sizeof (vdev_label_t));
	ASSERT0(P2PHASE_TYPED(psize, sizeof (vdev_label_t), uint64_t));

	return (offset + l * sizeof (vdev_label_t) + (l < VDEV_LABELS / 2 ?
	    0 : psize - VDEV_LABELS * sizeof (vdev_label_t)));
}
  1. L165–L166 El desplazamiento cae dentro de una etiqueta, y el tamaño del dispositivo es múltiplo de una etiqueta.
  2. L168–L169 Las etiquetas 0 y 1 al principio, 2 y 3 en los últimos 512 KiB. En el disco de 1 GiB de la figura: 0, 256 KiB, 1023,5 MiB y 1023,75 MiB. Tras las dos etiquetas del principio viene una zona de arranque de 3,5 MiB.

La importación. Se lee cada ranura de cada etiqueta, y una válida se conserva si supera a la mejor hasta el momento.

vdev_uberblock_compare Extracto de module/zfs/vdev_label.c, líneas 1495–1505, OpenZFS zfs-2.4.4. Ver en GitHub
int
vdev_uberblock_compare(const uberblock_t *ub1, const uberblock_t *ub2)
{
	int cmp = TREE_CMP(ub1->ub_txg, ub2->ub_txg);

	if (likely(cmp))
		return (cmp);

	cmp = TREE_CMP(ub1->ub_timestamp, ub2->ub_timestamp);
	if (likely(cmp))
		return (cmp);
  1. L1498 Gana el txg más alto.
  2. L1503 Con el mismo txg, la marca de tiempo más reciente; después la secuencia multihost, que la figura deja fuera.
vdev_label_read_config Extracto de module/zfs/vdev_label.c, líneas 832–863, OpenZFS zfs-2.4.4. Ver en GitHub
	for (int l = 0; l < VDEV_LABELS; l++) {
		nvlist_t *label = NULL;

		if (zio_wait(zio[l]) == 0 &&
		    nvlist_unpack(vp[l]->vp_nvlist, sizeof (vp[l]->vp_nvlist),
		    &label, 0) == 0) {
			/*
			 * Auxiliary vdevs won't have txg values in their
			 * labels and newly added vdevs may not have been
			 * completely initialized so just return the
			 * configuration from the first valid label we
			 * encounter.
			 */
			error = nvlist_lookup_uint64(label,
			    ZPOOL_CONFIG_POOL_TXG, &label_txg);
			if ((error || label_txg == 0) && !config) {
				config = label;
				for (l++; l < VDEV_LABELS; l++)
					zio_wait(zio[l]);
				break;
			} else if (label_txg <= txg && label_txg > best_txg) {
				best_txg = label_txg;
				nvlist_free(config);
				config = fnvlist_dup(label);
			}
		}

		if (label != NULL) {
			nvlist_free(label);
			label = NULL;
		}
	}
  1. L835–L837 Una etiqueta solo cuenta si su lectura tiene éxito, y la lectura comprueba la suma de comprobación propia de la etiqueta: una etiqueta rota se ignora.
  2. L852–L856 La etiqueta con el txg de configuración más alto que no supere txg, el del uberblock ganador (la llamada). Así se descartan las etiquetas pares de un txg que no llegó a confirmarse.

Tras la importación, el txg siguiente es el del ganador + 1: cuando ganó el árbol antiguo, el txg perdido se vuelve a escribir, y su uberblock va a la misma ranura, encima de la rota. La importación también pide una actualización de la configuración, que marca cada vdev como modificado para ese txg: este reescribe las cuatro etiquetas, rotas incluidas, cambie lo que cambie además.

Lo que la figura deja fuera

Un solo dataset y muy pocos bloques: dos punteros por bloque indirecto en lugar de hasta 1024, y los niveles indirectos propios del meta-dnode y el paso del MOS al objset de un sistema de archivos, reducidos a un solo bloque de dnodes. Un solo disco, así que cada uberblock y cada etiqueta llegan a él; sus 128 ranuras son las de sectores de 512 bytes. En el código, las escrituras de una misma fase se emiten juntas; la figura las hace llegar en orden (L0 y después L2, L1 y después L3, los uberblocks de L0 a L3) y rompe la que está en curso al desenchufar. El contenido de la configuración, el reintento con ZIO_FLAG_IO_RETRY, multihost, la expansión de RAID-Z, varias pasadas de sincronización en un txg y la liberación de los bloques antiguos (la sala de las instantáneas) no se muestran. La vista 3D dibuja el mismo estado que la 2D.