Sala 5
Los vdevs
Un pool no es una lista de discos. Son espejos, grupos RAID-Z, dRAID, un dispositivo log, un dispositivo special, discos de repuesto y discos en plena sustitución, combinados como el administrador decidió, y cualquiera de ellos puede cambiar mientras el pool funciona.
Si el camino de las E/S tuviera una rama para cada uno de ellos, y para cada combinación (un disco en sustitución
dentro de un raidz, un disco de repuesto dentro de un espejo), nunca estaría terminado. El código no tiene esa
rama. Construye un único árbol de nodos, vdev_t, que cuelga de
spa_root_vdev, y da a cada nodo una tabla de funciones,
vdev_ops_t. Una escritura en el pool desciende por el árbol, y cada nodo decide, con su propio
io_start, cómo continúa la E/S hacia sus hijos. El asignador, por su parte, solo ve los vdevs de
primer nivel, agrupados en clases según su función.
1 Un árbol, una tabla por nodo
El pool de la figura tiene tres vdevs de primer nivel para los datos, un espejo, un raidz1 de tres discos y un dRAID de cinco, y puede tener un espejo para el log, un espejo como vdev special y un disco para la dedup. Selecciona un nodo para ver la tabla a la que apunta: su tipo no es otra cosa que esa tabla. «Log», «special» y «dedup» no son tipos: son el sesgo de asignación de un vdev de primer nivel. Los discos de repuesto y el dispositivo de caché ni siquiera están en el árbol: son dos listas a su lado. Escribe bloques y sigue cada E/S hasta los discos.
Arrastra o usa las flechas para girar el árbol; + y − para acercar o alejar. Haz clic en un nodo para seleccionarlo.
Este navegador no tiene WebGL: la figura se queda en 2D.
Junto al árbol
spa_spares, discos de repuesto código
sdpdisk, disponibledraid1-2-0dspare, disponible, distribuido: espacio en cada hijo del dRAID
spa_l2cache, dispositivos de caché (L2ARC)
sdqdisk
El último bloque
Bloque 2, un bloque grande de archivo, txg 101: la clase normal.
- Un bloque de datos de 128 KiB, mayor que
special_small_blocks(0): la clase normal. código - 1 DVA: un bloque de datos de un archivo, con
copies=1. código DVA 0 enmirror-0. código
La E/S, descendiendo por el árbol
El zio lógico no tiene vdev: zio_vdev_io_start lo entrega a vdev_mirror_io_start, con un hijo por DVA (1 DVA). código
- DVA 0
mirror-0mirrorvdev_mirror_io_start: a todos los hijos códigosdadiskvdev_disk_io_start: 32 sectoressdbdiskvdev_disk_io_start: 32 sectores
El nodo seleccionado
sdd código
| Campo | Valor |
|---|---|
vdev_ops | &vdev_disk_ops |
vdev_op_type | "disk" |
vdev_op_io_start | vdev_disk_io_start |
vdev_op_io_done | vdev_disk_io_done |
vdev_op_psize_to_asize | vdev_default_asize |
vdev_op_need_resilver | NULL |
vdev_op_leaf | B_TRUE |
vdev_parent | raidz1-1 |
vdev_top | raidz1-1 |
vdev_children | 0 |
| Grupos de metaslabs | ninguno: solo los vdevs de primer nivel reciben asignaciones |
Clases de asignación
| Clase | Vdevs de primer nivel | Bloques |
|---|---|---|
| normal | mirror-0, raidz1-1, draid1:2d:5c:1s-2 | 1 |
| log | mirror-3 | 0 |
| special | mirror-4 | 1 |
| dedup | ninguno | 0 |
| log integrado | mirror-0, raidz1-1, draid1:2d:5c:1s-2 | 0 |
| log integrado de special | mirror-4 | 0 |
Versión en texto de esta figura
El árbol, desde la raíz:
rootrootmirror-0mirror, normalsdadisksdbdiskraidz1-1raidz, normalsdcdisksdddisksdediskdraid1:2d:5c:1s-2draid, normalsdfdisksdgdisksdhdisksdidisksdjdiskmirror-3mirror, logsdkdisksdldiskmirror-4mirror, specialsdmdisksdndisk
Junto al árbol: discos de repuesto sdp y draid1-2-0; caché sdq.
El último bloque:
Bloque 2, un bloque grande de archivo, txg 101: la clase normal.
- Un bloque de datos de 128 KiB, mayor que
special_small_blocks(0): la clase normal. - 1 DVA: un bloque de datos de un archivo, con
copies=1. DVA 0 enmirror-0.
La E/S, descendiendo por el árbol
El zio lógico no tiene vdev: zio_vdev_io_start lo entrega a vdev_mirror_io_start, con un hijo por DVA (1 DVA).
- DVA 0
mirror-0mirrorvdev_mirror_io_start: a todos los hijossdadiskvdev_disk_io_start: 32 sectoressdbdiskvdev_disk_io_start: 32 sectores
2 Dónde cae un bloque: las clases de asignación
Cada vdev de primer nivel entra en una clase de metaslabs, según su sesgo: normal, log,
special o dedup. Todo vdev que no sea un log recibe además un grupo en una clase de «log integrado», espacio para
el ZIL sin dispositivo log, pero solo un vdev normal o special con más de 64
metaslabs (un parámetro ajustable) pone en ella un metaslab: el grupo de un vdev de
dedup se queda vacío, y también el de un vdev pequeño. En cada escritura, spa_preferred_class elige la
clase. Un bloque de la DDT va a dedup; si no, a special; si no, a normal. Los metadatos y los bloques indirectos de
los archivos van a special. Un bloque de datos de un archivo solo va a special si no es mayor que el
special_small_blocks del dataset, 0 por defecto, y solo mientras special haya
usado menos del 75 % de su espacio: el último 25 % se reserva para los metadatos.
Todo lo demás va a normal. Un bloque del ZIL tiene su propio orden, log, log integrado de
special, special, log integrado, normal. Cuando una clase está llena, la asignación vuelve a preguntar y se
salta la clase que ha fallado.
Un bloque también puede tener varias copias, un DVA cada una, en vdevs consecutivos de su clase: los
metadatos tienen una copia más que el copies del dataset (con
redundant_metadata=all, el valor por defecto), y la DDT, que vive en el MOS
del pool, tres, el máximo. En la figura, escribe cada tipo de
bloque con y sin vdev special, pon special_small_blocks a 16 KiB y luego llena special.
3 Una sustitución es un espejo durante un tiempo
zpool replace no cambia un disco por otro. Pone un nodo nuevo en el lugar del disco antiguo, un nodo
replacing, con el disco antiguo como primer hijo y el nuevo como último. Un disco de repuesto hace lo
mismo con un nodo spare. Las dos tablas usan las funciones del espejo, así que el camino de las E/S no
necesita código nuevo: una escritura va a los dos discos, y una lectura se salta el disco nuevo para cada bloque
que todavía no tiene. Cuando termina el resilver (la resincronización de un disco), el disco antiguo se desconecta y el nodo
replacing desaparece. Un nodo spare permanece hasta que zed vuelve a poner en línea el
disco original o se borra su fallo, hasta que el propio disco original se sustituye, o hasta que el administrador
desconecta uno de los dos; en los dos primeros casos, es el final de un resilver lo que desconecta el repuesto.
Selecciona un disco en la figura, sustitúyelo, escribe y relee, y después termina el resilver. Sustituye también
el disco de dedup: es un vdev de primer nivel, y el nodo replacing asume su papel.
4 dRAID: permutaciones en lugar de grupos fijos
Un grupo raidz usa siempre los mismos discos. Un dRAID reparte grupos de anchura fija sobre todos sus hijos, mediante una tabla de permutaciones que el código genera a partir de una semilla fija y comprueba con una suma de comprobación que guarda, porque «existing pools depend on the same mapping always being generated» (los pools existentes dependen de que siempre se genere la misma tabla; traducción nuestra). El draid1:2d:5c:1s de la figura tiene grupos de tres (una paridad, dos de datos) sobre cinco hijos, y un repuesto distribuido: en cada fila de la tabla, un hijo no lleva ninguna columna, solo el espacio del repuesto. La figura calcula la misma tabla que el código, y la misma suma de comprobación. Escribe varios bloques grandes y observa cómo cada grupo cae en un conjunto distinto de hijos; después sustituye un hijo del dRAID por el repuesto distribuido.
La idea, a tres profundidades
En una frase
Cada tipo de dispositivo y cada sustitución es un nodo de un mismo árbol con su propia tabla de funciones, así que una E/S desciende por el árbol y cada nodo la reparte a su manera, mientras el asignador elige, según el tipo de bloque, una clase de vdevs de primer nivel donde ponerlo.
En un párrafo
Un vdev_t tiene un padre, hijos, el vdev de primer nivel al que pertenece y un puntero a un
vdev_ops_t que es su tipo. El zio lógico de un bloque no tiene vdev: zio_vdev_io_start
lo entrega al io_start del espejo con los DVA del bloque como hijos; cada zio hijo llega después a su
vdev de primer nivel, cuyo io_start escribe todos los hijos de un espejo, las columnas de un raidz o
un grupo de dRAID colocado por una permutación fija de sus hijos, hasta que las hojas emiten la E/S al disco. Solo
los vdevs de primer nivel poseen metaslabs, en la clase de su sesgo; spa_preferred_class envía los
bloques de la DDT a dedup, los metadatos a special, los bloques de datos pequeños a special mientras esté por
debajo del 75 %, y el resto a normal, y zio_dva_allocate recurre a otra clase cuando una está
llena. Una sustitución inserta un nodo replacing o spare por encima del disco antiguo,
con las funciones del espejo, hasta que termina el resilver.
En detalle
Un nodo del árbol.
struct vdev
Extracto de include/sys/vdev_impl.h, líneas
239–269, OpenZFS zfs-2.4.4.
Ver en GitHub
uint64_t vdev_state; /* see VDEV_STATE_* #defines */
uint64_t vdev_prevstate; /* used when reopening a vdev */
vdev_ops_t *vdev_ops; /* vdev operations */
spa_t *vdev_spa; /* spa for this vdev */
void *vdev_tsd; /* type-specific data */
vdev_t *vdev_top; /* top-level vdev */
vdev_t *vdev_parent; /* parent vdev */
vdev_t **vdev_child; /* array of children */
uint64_t vdev_children; /* number of children */
vdev_stat_t vdev_stat; /* virtual device statistics */
vdev_stat_ex_t vdev_stat_ex; /* extended statistics */
boolean_t vdev_expanding; /* expand the vdev? */
boolean_t vdev_reopening; /* reopen in progress? */
boolean_t vdev_nonrot; /* true if solid state */
int vdev_load_error; /* error on last load */
int vdev_open_error; /* error on last open */
int vdev_validate_error; /* error on last validate */
kthread_t *vdev_open_thread; /* thread opening children */
kthread_t *vdev_validate_thread; /* thread validating children */
uint64_t vdev_crtxg; /* txg when top-level was added */
uint64_t vdev_root_zap;
/*
* Top-level vdev state.
*/
uint64_t vdev_ms_array; /* metaslab array object */
uint64_t vdev_ms_shift; /* metaslab size shift */
uint64_t vdev_ms_count; /* number of metaslabs */
metaslab_group_t *vdev_mg; /* metaslab group */
metaslab_group_t *vdev_log_mg; /* embedded slog metaslab group */
metaslab_t **vdev_ms; /* metaslab array */-
L241
vdev_ops: el comportamiento del nodo, su tipo. - L243 El estado propio del tipo: para un raidz, su paridad; para un dRAID, sus permutaciones y el tamaño de sus grupos.
- L244–L247 El árbol: el vdev de primer nivel al que pertenece este nodo (un vdev de primer nivel apunta a sí mismo), el padre, los hijos.
- L264–L269 Solo los vdevs de primer nivel poseen metaslabs y un grupo de metaslabs: solo ellos reciben asignaciones. Los nodos por debajo solo encaminan las E/S.
vdev_ops_t
Extracto de include/sys/vdev_impl.h, líneas
101–126, OpenZFS zfs-2.4.4.
Ver en GitHub
typedef const struct vdev_ops {
vdev_init_func_t *vdev_op_init;
vdev_fini_func_t *vdev_op_fini;
vdev_open_func_t *vdev_op_open;
vdev_close_func_t *vdev_op_close;
vdev_asize_func_t *vdev_op_psize_to_asize;
vdev_asize_func_t *vdev_op_asize_to_psize;
vdev_min_asize_func_t *vdev_op_min_asize;
vdev_min_alloc_func_t *vdev_op_min_alloc;
vdev_io_start_func_t *vdev_op_io_start;
vdev_io_done_func_t *vdev_op_io_done;
vdev_state_change_func_t *vdev_op_state_change;
vdev_need_resilver_func_t *vdev_op_need_resilver;
vdev_hold_func_t *vdev_op_hold;
vdev_rele_func_t *vdev_op_rele;
vdev_remap_func_t *vdev_op_remap;
vdev_xlation_func_t *vdev_op_xlate;
vdev_rebuild_asize_func_t *vdev_op_rebuild_asize;
vdev_metaslab_init_func_t *vdev_op_metaslab_init;
vdev_config_generate_func_t *vdev_op_config_generate;
vdev_nparity_func_t *vdev_op_nparity;
vdev_ndisks_func_t *vdev_op_ndisks;
vdev_kobj_post_evt_func_t *vdev_op_kobj_evt_post;
char vdev_op_type[16];
boolean_t vdev_op_leaf;
} vdev_ops_t;- L106–L107 La conversión de tamaños: un raidz convierte el tamaño de un bloque en un tamaño asignado mayor.
- L110–L111 El camino de las E/S. La raíz no tiene ninguna de las dos: una E/S nunca empieza en la raíz.
- L113 Cada tipo decide si un bloque toca un hijo dañado.
- L124–L125 El nombre del tipo, y si es una hoja.
Los tipos de la figura apuntan a vdev_root_ops, vdev_mirror_ops,
vdev_raidz_ops, vdev_draid_ops,
vdev_disk_ops (en Linux), y a las tablas de la sustitución, del repuesto y
del repuesto distribuido, más abajo. El descenso. El zio lógico de un bloque no tiene vdev, y
zio_vdev_io_start lo entrega a la función del espejo, con los DVA del bloque
como hijos; cada zio hijo, que sí tiene un vdev, va al propio io_start de ese
vdev.
vdev_mirror_io_start
Extracto de module/zfs/vdev_mirror.c, líneas
656–693, OpenZFS zfs-2.4.4.
Ver en GitHub
/*
* For normal reads just pick one child.
*/
c = vdev_mirror_child_select(zio);
children = (c >= 0);
} else {
ASSERT(zio->io_type == ZIO_TYPE_WRITE);
/*
* Writes go to all children.
*/
c = 0;
children = mm->mm_children;
}
while (children--) {
mc = &mm->mm_child[c++];
/*
* When sequentially resilvering and the integrity of the data
* is speculative (ZIO_FLAG_SPECULATIVE), issue write repair IOs
* only to the vdev which is being rebuilt. Existing data on
* other children must never be overwritten with unconfirmed
* data to avoid unrecoverable damage to the pool.
*/
if ((zio->io_priority == ZIO_PRIORITY_REBUILD) &&
(zio->io_flags & ZIO_FLAG_IO_REPAIR) &&
!(zio->io_flags & ZIO_FLAG_SCRUB) &&
(zio->io_flags & ZIO_FLAG_SPECULATIVE) &&
mm->mm_rebuilding && !mc->mc_rebuilding) {
continue;
}
zio_nowait(zio_vdev_child_io(zio, zio->io_bp,
mc->mc_vd, mc->mc_offset, zio->io_abd, zio->io_size,
zio->io_type, zio->io_priority, 0,
vdev_mirror_child_done, mc));
}- L656–L660 Una lectura normal elige un solo hijo: no uno cuya DTL diga que le falta el txg del bloque; primero un repuesto distribuido; si no, el menos cargado.
- L662–L669 Las escrituras van a todos los hijos.
- L671–L687 Una excepción: las escrituras de reparación de una reconstrucción secuencial, cuando los datos son especulativos, solo van al hijo que se está reconstruyendo.
- L689–L692 Un zio hijo por hijo, en el mismo desplazamiento y con el mismo tamaño.
El io_start de un raidz construye su mapa y emite una columna por hijo,
menos para un bloque pequeño: la sala RAID-Z muestra cómo; una lectura
normal solo lee las columnas de datos. El dRAID se trata en la parte 4, más abajo.
Las clases. Un vdev de primer nivel entra en una clase cuando se crea su grupo de metaslabs.
vdev_metaslab_group_create
Extracto de module/zfs/vdev.c, líneas
1546–1579, OpenZFS zfs-2.4.4.
Ver en GitHub
if (vd->vdev_mg == NULL) {
metaslab_class_t *mc;
if (vd->vdev_islog && vd->vdev_alloc_bias == VDEV_BIAS_NONE)
vd->vdev_alloc_bias = VDEV_BIAS_LOG;
ASSERT3U(vd->vdev_islog, ==,
(vd->vdev_alloc_bias == VDEV_BIAS_LOG));
switch (vd->vdev_alloc_bias) {
case VDEV_BIAS_LOG:
mc = spa_log_class(spa);
break;
case VDEV_BIAS_SPECIAL:
mc = spa_special_class(spa);
break;
case VDEV_BIAS_DEDUP:
mc = spa_dedup_class(spa);
break;
default:
mc = spa_normal_class(spa);
}
vd->vdev_mg = metaslab_group_create(mc, vd);
if (!vd->vdev_islog) {
if (mc == spa_special_class(spa)) {
vd->vdev_log_mg = metaslab_group_create(
spa_special_embedded_log_class(spa), vd);
} else {
vd->vdev_log_mg = metaslab_group_create(
spa_embedded_log_class(spa), vd);
}
}- L1549–L1550 Un dispositivo log sin sesgo explícito recibe el sesgo log.
- L1555–L1567 El sesgo elige la clase: log, special, dedup o normal. Cuatro sesgos, seis punteros de clase en el pool.
- L1571–L1578 Todo vdev que no sea un log recibe un segundo grupo, en la clase de log integrado, o en la de log integrado de special para un vdev special: espacio para el ZIL sin dispositivo log. Un metaslab se traslada a ella más tarde, y solo en un vdev normal o special con más de 64 metaslabs.
spa_preferred_class
Extracto de module/zfs/spa_misc.c, líneas
2121–2156, OpenZFS zfs-2.4.4.
Ver en GitHub
spa_preferred_class(spa_t *spa, const zio_t *zio)
{
metaslab_class_t *mc = zio->io_metaslab_class;
boolean_t tried_dedup = (mc == spa_dedup_class(spa));
boolean_t tried_special = (mc == spa_special_class(spa));
const zio_prop_t *zp = &zio->io_prop;
/* Gang children should always use the class of their parents. */
if (zio->io_flags & ZIO_FLAG_GANG_CHILD) {
ASSERT(mc != NULL);
return (mc);
}
/*
* Override object type for the purposes of selecting a storage class.
* Primarily for DMU_OTN_ types where we can't explicitly control their
* storage class; instead, choose a static type most closely matches
* what we want.
*/
dmu_object_type_t objtype =
zp->zp_storage_type == DMU_OT_NONE ?
zp->zp_type : zp->zp_storage_type;
/*
* ZIL allocations determine their class in zio_alloc_zil().
*/
ASSERT(objtype != DMU_OT_INTENT_LOG);
if (DMU_OT_IS_DDT(objtype)) {
if (spa_has_dedup(spa) && !tried_dedup && !tried_special)
return (spa_dedup_class(spa));
else if (spa_special_has_ddt(spa) && !tried_special)
return (spa_special_class(spa));
else
return (spa_normal_class(spa));
}- L2123–L2125 La clase que una asignación ya ha probado, si la hay: en una segunda llamada, la función sabe qué ha fallado.
- L2144–L2147 Los bloques del ZIL nunca pasan por aquí.
-
L2149–L2156
Un bloque de la DDT: dedup si el pool tiene una y no se ha probado; si no, special, si el pool tiene una (
spa_special_has_ddt) y no se ha probado; si no, normal. Un pool «tiene» una clase cuando tiene al menos un grupo en ella.
spa_preferred_class
Extracto de module/zfs/spa_misc.c, líneas
2158–2179, OpenZFS zfs-2.4.4.
Ver en GitHub
if (!spa_has_special(spa) || tried_special)
return (spa_normal_class(spa));
if (DMU_OT_IS_METADATA(objtype) ||
(zfs_user_indirect_is_special && zp->zp_level > 0))
return (spa_special_class(spa));
/*
* Allow small blocks in special class. However, leave a reserve of
* zfs_special_class_metadata_reserve_pct exclusively for metadata.
*/
if (zio->io_size <= zp->zp_zpl_smallblk) {
metaslab_class_t *special = spa_special_class(spa);
uint64_t limit = metaslab_class_get_space(special) *
(100 - zfs_special_class_metadata_reserve_pct) / 100;
if (metaslab_class_get_alloc(special) < limit)
return (special);
}
return (spa_normal_class(spa));
}- L2158–L2159 Sin vdev special, o si special ya ha fallado: normal.
- L2161–L2163 Los metadatos (un tipo de objeto marcado como tal, entre ellos los dnodes, pero no el contenido de los archivos) y los bloques indirectos de los archivos: special.
-
L2169–L2176
Un bloque de datos no mayor que
special_small_blocks, mientras special haya asignado menos del 75 % de su espacio. Pasado ese punto, el último 25 % es solo para metadatos.
zio_dva_allocate
Extracto de module/zfs/zio.c, líneas
4368–4407, OpenZFS zfs-2.4.4.
Ver en GitHub
/*
* Try allocating the block in the usual metaslab class.
* If that's full, allocate it in some other class(es).
* If that's full, allocate as a gang block,
* and if all are full, the allocation fails (which shouldn't happen).
*
* Note that we do not fall back on embedded slog (ZIL) space, to
* preserve unfragmented slog space, which is critical for decent
* sync write performance. If a log allocation fails, we will fall
* back to spa_sync() which is abysmal for performance.
*/
ASSERT(ZIO_HAS_ALLOCATOR(zio));
error = metaslab_alloc(spa, mc, zio->io_size, bp,
zio->io_prop.zp_copies, zio->io_txg, NULL, flags,
&zio->io_alloc_list, zio->io_allocator, zio);
/*
* When the dedup or special class is spilling into the normal class,
* there can still be significant space available due to deferred
* frees that are in-flight. We track the txg when this occurred and
* back off adding new DDT entries for a few txgs to allow the free
* blocks to be processed.
*/
if (error == ENOSPC && spa->spa_dedup_class_full_txg != zio->io_txg &&
(mc == spa_dedup_class(spa) || (mc == spa_special_class(spa) &&
!spa_has_dedup(spa) && spa_special_has_ddt(spa)))) {
spa->spa_dedup_class_full_txg = zio->io_txg;
zfs_dbgmsg("%s[%llu]: %s class spilling, req size %llu, "
"%llu allocated of %llu",
spa_name(spa), (u_longlong_t)zio->io_txg,
metaslab_class_get_name(mc),
(u_longlong_t)zio->io_size,
(u_longlong_t)metaslab_class_get_alloc(mc),
(u_longlong_t)metaslab_class_get_space(mc));
}
/*
* Fall back to some other class when this one is full.
*/
if (error == ENOSPC && (newmc = spa_preferred_class(spa, zio)) != mc) {- L4368–L4378 El orden: la clase habitual, luego otras clases, luego un bloque gang. No el espacio de log integrado, reservado para el ZIL.
- L4380–L4382 Las copias del bloque, un DVA cada una, en una sola clase.
- L4391–L4402 Cuando dedup o special se desbordan hacia normal, se anota el txg, y las nuevas entradas de la DDT se contienen durante unos cuantos txg.
-
L4407
ENOSPC: volver a preguntar aspa_preferred_class; ahora sabe qué clase ha fallado, y la asignación vuelve a empezar en la siguiente.
Un bloque del ZIL no pasa por aquí: zio_alloc_zil prueba la clase log, el
log integrado de special, special, el log integrado de los vdevs normales y, por último, normal,
con un solo DVA; la sala del ZIL y el SLOG lo sigue.
Las copias ditto.
metaslab_alloc_dva_range
Extracto de module/zfs/metaslab.c, líneas
5401–5427, OpenZFS zfs-2.4.4.
Ver en GitHub
* If we are doing ditto or log blocks, try to spread them across
* consecutive vdevs. If we're forced to reuse a vdev before we've
* allocated all of our ditto blocks, then try and spread them out on
* that vdev as much as possible. If it turns out to not be possible,
* gradually lower our standards until anything becomes acceptable.
* Also, allocating on consecutive vdevs (as opposed to random vdevs)
* gives us hope of containing our fault domains to something we're
* able to reason about. Otherwise, any two top-level vdev failures
* will guarantee the loss of data. With consecutive allocation,
* only two adjacent top-level vdev failures will result in data loss.
*
* If we are doing gang blocks (hintdva is non-NULL), try to keep
* ourselves on the same vdev as our gang block header. It makes our
* fault domains something tractable.
*/
if (hintdva && DVA_IS_VALID(&hintdva[d])) {
vd = vdev_lookup_top(spa, DVA_GET_VDEV(&hintdva[d]));
mg = vdev_get_mg(vd, mc);
}
if (mg == NULL && d != 0) {
vd = vdev_lookup_top(spa, DVA_GET_VDEV(&dva[d - 1]));
mg = vdev_get_mg(vd, mc)->mg_next;
}
if (mg == NULL || mg->mg_class != mc || mg->mg_activation_count <= 0) {
ASSERT(mca->mca_rotor != NULL);
mg = mca->mca_rotor;
}- L5401–L5410 Repartir las copias entre vdevs consecutivos: solo el fallo de dos vdevs de primer nivel contiguos puede hacer perder un bloque así.
- L5420–L5423 El DVA d empieza en el grupo que sigue al vdev del DVA d − 1: el siguiente vdev de la clase, dando la vuelta a la clase. Una clase de un solo vdev pone en él todas las copias.
- L5424–L5427 El primer DVA empieza en el rotor de la clase, que avanza según el tamaño de los bloques, la profundidad de las colas y el espacio libre: la sala de los metaslabs.
Cuántas copias: para los metadatos, una más que copies con
redundant_metadata=all, el valor por defecto; para el MOS, que
contiene la DDT, spa_max_replication, 3, y las
copias de un bloque tienen un máximo de ese mismo 3.
Una sustitución. Las tablas de la sustitución y del repuesto son las del espejo.
vdev_replacing_ops
Extracto de module/zfs/vdev_mirror.c, líneas
997–1021, OpenZFS zfs-2.4.4.
Ver en GitHub
vdev_ops_t vdev_replacing_ops = {
.vdev_op_init = NULL,
.vdev_op_fini = NULL,
.vdev_op_open = vdev_mirror_open,
.vdev_op_close = vdev_mirror_close,
.vdev_op_psize_to_asize = vdev_default_asize,
.vdev_op_asize_to_psize = vdev_default_psize,
.vdev_op_min_asize = vdev_default_min_asize,
.vdev_op_min_alloc = NULL,
.vdev_op_io_start = vdev_mirror_io_start,
.vdev_op_io_done = vdev_mirror_io_done,
.vdev_op_state_change = vdev_mirror_state_change,
.vdev_op_need_resilver = vdev_default_need_resilver,
.vdev_op_hold = NULL,
.vdev_op_rele = NULL,
.vdev_op_remap = NULL,
.vdev_op_xlate = vdev_default_xlate,
.vdev_op_rebuild_asize = vdev_mirror_rebuild_asize,
.vdev_op_metaslab_init = NULL,
.vdev_op_config_generate = NULL,
.vdev_op_nparity = NULL,
.vdev_op_ndisks = NULL,
.vdev_op_type = VDEV_TYPE_REPLACING, /* name of this vdev type */
.vdev_op_leaf = B_FALSE /* not a leaf vdev */
};-
L1000–L1007
La apertura, el cierre, el tamaño, el
io_starty elio_donedel espejo, como envdev_mirror_opsyvdev_spare_ops. - L1019 Solo cambia el nombre.
spa_vdev_attach
Extracto de module/zfs/spa.c, líneas
8324–8360, OpenZFS zfs-2.4.4.
Ver en GitHub
} else {
/*
* Active hot spares can only be replaced by inactive hot
* spares.
*/
if (pvd->vdev_ops == &vdev_spare_ops &&
oldvd->vdev_isspare &&
!spa_has_spare(spa, newvd->vdev_guid))
return (spa_vdev_exit(spa, newrootvd, txg, ENOTSUP));
/*
* If the source is a hot spare, and the parent isn't already a
* spare, then we want to create a new hot spare. Otherwise, we
* want to create a replacing vdev. The user is not allowed to
* attach to a spared vdev child unless the 'isspare' state is
* the same (spare replaces spare, non-spare replaces
* non-spare).
*/
if (pvd->vdev_ops == &vdev_replacing_ops &&
spa_version(spa) < SPA_VERSION_MULTI_REPLACE) {
return (spa_vdev_exit(spa, newrootvd, txg, ENOTSUP));
} else if (pvd->vdev_ops == &vdev_spare_ops &&
newvd->vdev_isspare != oldvd->vdev_isspare) {
return (spa_vdev_exit(spa, newrootvd, txg, ENOTSUP));
}
if (spa_vdev_new_spare_would_cause_double_spares(newvd, pvd)) {
vdev_dbgmsg(newvd,
"disk would create double spares, ignore.");
return (spa_vdev_exit(spa, newrootvd, txg, EEXIST));
}
if (newvd->vdev_isspare)
pvops = &vdev_spare_ops;
else
pvops = &vdev_replacing_ops;
}- L8325–L8332 Un disco de repuesto activo solo puede sustituirse por uno inactivo.
-
L8356–L8359
Un disco de repuesto recibe un nodo
spare; cualquier otro disco, un nodoreplacing. Un repuesto distribuido solo puede sustituir a un hijo de su propio dRAID.
vdev_add_parent
Extracto de module/zfs/vdev.c, líneas
1418–1449, OpenZFS zfs-2.4.4.
Ver en GitHub
vdev_add_parent(vdev_t *cvd, vdev_ops_t *ops)
{
spa_t *spa = cvd->vdev_spa;
vdev_t *pvd = cvd->vdev_parent;
vdev_t *mvd;
ASSERT(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
mvd = vdev_alloc_common(spa, cvd->vdev_id, 0, ops);
mvd->vdev_asize = cvd->vdev_asize;
mvd->vdev_min_asize = cvd->vdev_min_asize;
mvd->vdev_max_asize = cvd->vdev_max_asize;
mvd->vdev_psize = cvd->vdev_psize;
mvd->vdev_ashift = cvd->vdev_ashift;
mvd->vdev_logical_ashift = cvd->vdev_logical_ashift;
mvd->vdev_physical_ashift = cvd->vdev_physical_ashift;
mvd->vdev_state = cvd->vdev_state;
mvd->vdev_crtxg = cvd->vdev_crtxg;
mvd->vdev_nonrot = cvd->vdev_nonrot;
vdev_remove_child(pvd, cvd);
vdev_add_child(pvd, mvd);
cvd->vdev_id = mvd->vdev_children;
vdev_add_child(mvd, cvd);
vdev_top_update(cvd->vdev_top, cvd->vdev_top);
if (mvd == mvd->vdev_top)
vdev_top_transfer(cvd, mvd);
return (mvd);
}-
L1426
El nodo nuevo toma el
vdev_iddel disco antiguo… - L1439–L1440 …y su lugar entre los hijos de su padre.
-
L1441–L1442
El disco antiguo pasa a ser su hijo 0; a continuación,
spa_vdev_attachañade el disco nuevo detrás de él, como último hijo. - L1445–L1446 Si el disco antiguo era un vdev de primer nivel, el nodo nuevo pasa a ser el vdev de primer nivel y se hace cargo de sus metaslabs, su grupo y su sesgo.
La DTL del disco nuevo se marca como ausente desde TXG_INITIAL hasta el txg
actual más TXG_CONCURRENT_STATES (esta línea, 3 y
4): una lectura de un bloque más antiguo se lo salta, y eso es lo que corrige el resilver.
spa_vdev_resilver_done_hunt
Extracto de module/zfs/spa.c, líneas
9438–9456, OpenZFS zfs-2.4.4.
Ver en GitHub
/*
* Check for a completed replacement. We always consider the first
* vdev in the list to be the oldest vdev, and the last one to be
* the newest (see spa_vdev_attach() for how that works). In
* the case where the newest vdev is faulted, we will not automatically
* remove it after a resilver completes. This is OK as it will require
* user intervention to determine which disk the admin wishes to keep.
*/
if (vd->vdev_ops == &vdev_replacing_ops) {
ASSERT(vd->vdev_children > 1);
newvd = vd->vdev_child[vd->vdev_children - 1];
oldvd = vd->vdev_child[0];
if (vdev_dtl_empty(newvd, DTL_MISSING) &&
vdev_dtl_empty(newvd, DTL_OUTAGE) &&
!vdev_dtl_required(oldvd))
return (oldvd);
}- L9439–L9441 El primer hijo es el disco más antiguo; el último, el más reciente.
-
L9446–L9455
Cuando al disco nuevo no le falta nada y el antiguo ya no es necesario, el disco antiguo se desconecta; el nodo
replacing, con un solo hijo, se elimina.
Un nodo spare no se deshace con su propio resilver. Cuando el disco original vuelve a ponerse en
línea con el indicador unspare, que zed pasa, o se
borra su fallo, se marca con vdev_unspare, y el final de su resilver
desconecta el repuesto. Si el propio disco original se sustituye, el final de ese
resilver también desconecta el repuesto. Si no, el administrador desconecta uno de
los dos, y un nodo spare que se queda con un solo hijo se elimina.
El dRAID. La tabla de permutaciones se genera, no se almacena.
vdev_draid_generate_perms
Extracto de module/zfs/vdev_draid.c, líneas
506–526, OpenZFS zfs-2.4.4.
Ver en GitHub
uint64_t draid_seed[2] = { VDEV_DRAID_SEED, map->dm_seed };
uint8_t *current_row, *previous_row = initial_row;
/*
* Perform a Fisher-Yates shuffle of each row using the previous
* row as the starting point. An initial_row with known pattern
* is used as the input for the first row.
*/
for (int i = 0; i < nperms; i++) {
current_row = &perms[i * children];
memcpy(current_row, previous_row, rowsz);
for (int j = children - 1; j > 0; j--) {
uint64_t k = vdev_draid_rand(draid_seed) % (j + 1);
uint8_t val = current_row[j];
current_row[j] = current_row[k];
current_row[k] = val;
}
previous_row = current_row;
}-
L506
Con
VDEV_DRAID_SEEDcomo semilla y la semilla de la tabla para ese número de hijos: para cinco, 256 permutaciones, semilla0xf46733b7f4d47dfd. -
L514–L526
Cada fila es una mezcla de Fisher-Yates de la anterior, con xoroshiro128++. El resultado se comprueba con la suma de comprobación guardada,
0x2a1648d74para cinco hijos; la copia de la figura da la misma.
vdev_draid_map_alloc_row
Extracto de module/zfs/vdev_draid.c, líneas
1042–1064, OpenZFS zfs-2.4.4.
Ver en GitHub
uint8_t *base;
uint64_t iter, asize = 0;
vdev_draid_get_perm(vdc, perm, &base, &iter);
for (uint64_t i = 0; i < groupwidth; i++) {
raidz_col_t *rc = &rr->rr_col[i];
uint64_t c = (groupstart + i) % ndisks;
/* increment the offset if we wrap to the next row */
if (i == wrap)
physical_offset += VDEV_DRAID_ROWHEIGHT;
rc->rc_devidx = vdev_draid_permute_id(vdc, base, iter, c);
rc->rc_offset = physical_offset;
if (q == 0 && i >= bc)
rc->rc_size = 0;
else if (i < bc)
rc->rc_size = (q + 1) << ashift;
else
rc->rc_size = q << ashift;
asize += rc->rc_size;
}- L1044 La fila de la tabla y su rotación: cada fila sirve una vez por hijo, desplazada una posición cada vez.
- L1047 La columna i es la posición (groupstart + i) módulo ndisks; ndisks es el número de hijos menos los repuestos distribuidos.
- L1049–L1051 Un grupo que sobrepasa el final de una fila continúa en la siguiente.
- L1053 El hijo: la entrada de la fila en esa posición, más la rotación, módulo el número de hijos.
- L1056–L1061 El tamaño de las columnas, como en RAID-Z; las primeras columnas son la paridad.
El grupo de un bloque se deduce de su desplazamiento, y zpool create fija
ngroups para que los grupos llenen filas enteras: cuatro para draid1:2d:5c:1s, cuyos grupos
tienen tres de ancho sobre el equivalente de cuatro discos de cinco hijos, en filas de
16 MiB. El comentario de diseño dibuja la
disposición. Una escritura llena todas las columnas de su grupo, con una columna vacía o corta completada
con un sector de relleno, ya que «dRAID always allocates a full
stripe width» (el dRAID siempre asigna una banda de anchura completa; traducción nuestra); una lectura normal
lee las columnas de datos que contienen datos. Las posiciones desde
ndisks hasta la última de cada fila no reciben ninguna columna: es el espacio de los repuestos
distribuidos. Un repuesto distribuido figura en la lista de los discos de repuesto, y
es una hoja cuyo io_start envía cada escritura al hijo que lleva el
espacio del repuesto en esa fila.
Lo que la figura deja fuera
Qué vdev de una clase recibe la primera copia de un bloque lo decide el rotor del asignador, según la carga y el espacio libre: la figura avanza el rotor un vdev por bloque, y coloca cada copia en un dRAID al principio del grupo siguiente y cada copia en un raidz tras la anterior. Los tamaños de los bloques son datos de entrada: 4 KiB para el ZIL y los bloques pequeños de archivo, 16 KiB para los metadatos, 8 KiB para la DDT, 128 KiB para un bloque grande de archivo, tras la compresión. La lectura de un espejo toma el primer hijo admisible, donde el código toma el menos cargado. Solo la clase special se llena, y su espacio de log integrado sigue libre. Se supone que los vdevs normales y special son lo bastante grandes para contener un metaslab de log integrado. Una sustitución por disco a la vez; el resilver en sí, su orden y la reconstrucción secuencial no se dibujan, solo su final. Los vdevs retirados (indirectos), los huecos, la expansión de RAID-Z, los bloques gang y los errores quedan fuera. La vista 3D dibuja el mismo estado que la 2D; ninguna de las dos muestra los desplazamientos en el disco.