Sala 9
Metaslabs y space maps
La copia en escritura nunca sobrescribe un bloque: cada escritura asigna espacio nuevo y cada sobrescritura libera el bloque antiguo. La asignación está en el camino de cada escritura, así que tiene que ser rápida, y lo que está libre tiene que quedar registrado en disco, para que el pool lo siga sabiendo después de un reinicio.
El registro evidente es un mapa de bits de todo el pool, un bit por sector. Un pool de 16 TiB con sectores de 4 KiB tiene 232: 512 MiB de mapa de bits que mantener en memoria, y habría que actualizarlo en su sitio con cada cambio, justo lo que la copia en escritura prohíbe. ZFS hace otra cosa. Divide cada vdev en unos cientos de metaslabs, mantiene el espacio libre de un metaslab en memoria como un árbol de segmentos libres, y solo registra en disco lo que ha cambiado, en un space map: un registro de extents ALLOC y FREE al que solo se añade, y que se reescribe más corto cuando se ha vuelto demasiado largo.
1 Escribir, borrar, sincronizar
Abajo, un vdev de 8 GiB con sectores de 4 KiB, dividido en 16 metaslabs de 512 MiB; todas las escrituras van al metaslab 2, dibujado debajo. Ya hay dos archivos: A, de 32 MiB, escrito en el txg 10, y B, de 4 MiB, en el txg 11. La cinta es el space map del metaslab, palabras de 64 bits en disco; selecciona una entrada para ver sus bits.
Borra A y sincroniza: una entrada FREE se suma a la cinta, pero el espacio de A queda diferido, no libre, durante dos txg más. Sincroniza dos veces para recuperarlo y luego escribe 160 MiB: el dynamic fit no vuelve al hueco de A, y una longitud así necesita una entrada de dos palabras. Pulsa Cargar para reconstruir el árbol libre a partir de la cinta, y Condensar: se rechaza mientras el space map es corto. Reescribe la base de datos hasta que la prueba diga que sí, y entonces condensa.
Txg abierto: 12. Libre: 476 MiB en 1 segmento. Space map: 32 bytes, 2 entradas. Condensación: no.
- libre (ms_allocatable)
- asignado, en el space map
- asignado en el txg abierto
- liberado en el txg abierto
- liberado, diferido
- cursor del dynamic fit
En memoria
ms_allocatable476 MiB, 1 segmentoms_allocating[0]vacíoms_freeingvacíoms_defer[]vacíoms_lbas[17]0x42400000 donde se busca el próximo bloque de 128 KiB
Archivos
- A
- B
En disco: el space map del metaslab 2
- txg 10 ALLOC
- ALLOC 32 MiB
- txg 11 ALLOC
- ALLOC 4 MiB
ALLOC 4 MiB, en la palabra 3, 1 palabra
palabra 3 0x00000000200003FF
ALLOC: desplazamiento 0x2000, en unidades de 4 KiB desde el inicio del metaslab (32 MiB, desplazamiento 0x42000000 en el vdev), longitud 1024 unidades (4 MiB). La longitud cabe en 15 bits (como máximo 32 768 unidades, 128 MiB): una palabra. código
¿Condensar?
- no Al menos el doble del tamaño óptimo, 24 bytes (zfs_condense_pct 200): 32 bytes frente a 48
- no Más de cuatro bloques de 16 KiB (zfs_metaslab_condense_block_threshold): 32 bytes frente a 65 536
metaslab_should_condense: no El space map sigue creciendo. código
Lo que hizo la última acción
- Nada todavía. El estado inicial: A, 32 MiB, escrito y sincronizado en el txg 10, y luego B, 4 MiB, en el txg 11.
Versión en texto de esta figura
Un vdev de 16 metaslabs; el metaslab 2 empieza en 0x40000000. Txg abierto: 12.
- Árbol libre (ms_allocatable), 476 MiB: [0x42400000, 0x60000000).
- Txg abierto 12: asignado ninguno; liberado ninguno.
- Nada diferido.
- Archivos: A (32 MiB: [0x40000000, 0x42000000)); B (4 MiB: [0x42000000, 0x42400000)).
- Space map: smp_length 32 bytes (4 palabras, en 1 bloque de 16 KiB) · smp_alloc 36 MiB. Últimas palabras: 0x800400000000000A (txg 10 ALLOC, palabra de depuración en la palabra 0: siguen entradas ALLOC); 0x0000000000001FFF (ALLOC 32 MiB, en la palabra 1, 1 palabra); 0x800400000000000B (txg 11 ALLOC, palabra de depuración en la palabra 2: siguen entradas ALLOC); 0x00000000200003FF (ALLOC 4 MiB, en la palabra 3, 1 palabra).
Txg abierto: 12. Libre: 476 MiB en 1 segmento. Space map: 32 bytes, 2 entradas. Condensación: no.
La idea, a tres profundidades
En una frase
ZFS guarda el espacio libre por metaslab, como un árbol en memoria y un registro en disco al que solo se añade y que, reproducido, reconstruye el árbol, y reescribe el registro a partir del árbol cuando el registro se ha vuelto mucho más largo que el árbol.
En un párrafo
Un vdev de primer nivel se divide en metaslabs, unos 200 por defecto, de 512 MiB a 16 GiB cada uno (menos y más
pequeños en los vdevs pequeños). Un metaslab cargado tiene sus segmentos libres en un árbol de rangos,
ms_allocatable. Una asignación toma un segmento de ese árbol, por defecto con el asignador dynamic
fit, que busca hacia delante desde donde terminó la última asignación de ese tamaño, y lo pone en un árbol propio
del txg, ms_allocating; una liberación va a ms_freeing. Nada llega al disco hasta que el
txg se sincroniza: entonces las asignaciones y liberaciones del txg se añaden al space map del metaslab, una
palabra de depuración y entradas ALLOC, una palabra de depuración y entradas FREE, una o dos palabras de 64 bits
por extent. El espacio liberado solo vuelve a ser asignable dos txg más tarde, para que el pool pueda volver a un
uberblock anterior sin encontrar sus bloques reutilizados. Cargar un metaslab reproduce su space map, añadiendo los
extents FREE y quitando los ALLOC. Y cuando el space map mide al menos el doble de lo que mediría el árbol libre escrito
solo, y más de cuatro bloques, la siguiente sincronización lo condensa si el metaslab está cargado: el space map se
trunca y se reescribe a partir del árbol.
En detalle
Los metaslabs. Un metaslab, struct metaslab, es un rango de
un vdev con sus árboles en memoria: ms_allocatable, el árbol libre,
ms_allocating[], uno por txg en curso, ms_freeing, ms_freed y
ms_defer[]; un comentario de la cabecera dibuja su ciclo de vida. Su
tamaño lo da vdev_metaslab_set_size: apunta a
200 metaslabs, entre 512 MiB y 16 GiB, y al menos 16 en un
vdev pequeño. El vdev de la figura, de 8 GiB, recibe 16 metaslabs de 512 MiB. El space map de un metaslab cuenta en
sectores: su desplazamiento de bits (shift) es el ashift del vdev, así que aquí una unidad
son 4 KiB.
Tomar un bloque. Asignar en un metaslab:
metaslab_block_alloc
Extracto de module/zfs/metaslab.c, líneas
4863–4890, OpenZFS zfs-2.4.4.
Ver en GitHub
ASSERT(MUTEX_HELD(&msp->ms_lock));
VERIFY(!msp->ms_condensing);
VERIFY0(msp->ms_disabled);
VERIFY0(msp->ms_new);
start = mc->mc_ops->msop_alloc(msp, size, max_size, actual_size);
if (start != -1ULL) {
size = *actual_size;
metaslab_group_t *mg = msp->ms_group;
vdev_t *vd = mg->mg_vd;
VERIFY0(P2PHASE(start, 1ULL << vd->vdev_ashift));
VERIFY0(P2PHASE(size, 1ULL << vd->vdev_ashift));
VERIFY3U(zfs_range_tree_space(rt) - size, <=, msp->ms_size);
zfs_range_tree_remove(rt, start, size);
zfs_range_tree_clear(msp->ms_trim, start, size);
if (zfs_range_tree_is_empty(msp->ms_allocating[txg & TXG_MASK]))
vdev_dirty(mg->mg_vd, VDD_METASLAB, msp, txg);
zfs_range_tree_add(msp->ms_allocating[txg & TXG_MASK], start,
size);
msp->ms_allocating_total += size;
/* Track the last successful allocation */
msp->ms_alloc_txg = txg;
metaslab_verify_space(msp, txg);
}- L4864 Ninguna asignación en un metaslab cuyo space map se está condensando: su árbol libre se está escribiendo.
- L4868 El asignador de la clase, dynamic fit por defecto, devuelve un desplazamiento en este metaslab.
- L4877 El extent sale del árbol libre.
- L4880–L4881 La primera asignación de un txg marca el metaslab como sucio, de modo que la sincronización pasará por él.
-
L4883–L4884
El extent entra en
ms_allocatingpara este txg. Aún no se escribe nada en disco. En la figura, los extents naranjas.
El dynamic fit. Qué desplazamiento devuelve el asignador:
metaslab_df_alloc
Extracto de module/zfs/metaslab.c, líneas
1905–1931, OpenZFS zfs-2.4.4.
Ver en GitHub
uint64_t align = max_size & -max_size;
uint64_t *cursor = &msp->ms_lbas[highbit64(align) - 1];
zfs_range_tree_t *rt = msp->ms_allocatable;
uint_t free_pct = zfs_range_tree_space(rt) * 100 / msp->ms_size;
uint64_t offset;
ASSERT(MUTEX_HELD(&msp->ms_lock));
/*
* If we're running low on space, find a segment based on size,
* rather than iterating based on offset.
*/
if (metaslab_largest_allocatable(msp) < metaslab_df_alloc_threshold ||
free_pct < metaslab_df_free_pct) {
align = size & -size;
cursor = &msp->ms_lbas[highbit64(align) - 1];
offset = -1;
} else {
offset = metaslab_block_picker(rt, cursor, size, max_size,
metaslab_df_max_search, found_size);
if (max_size != size && offset == -1) {
align = size & -size;
cursor = &msp->ms_lbas[highbit64(align) - 1];
offset = metaslab_block_picker(rt, cursor, size,
max_size, metaslab_df_max_search, found_size);
}
}-
L1905–L1906
Un cursor por alineación en potencia de dos,
ms_lbas[]. Todos los bloques de la figura son de 128 KiB, así que un solo cursor,ms_lbas[17], la marca negra encima del metaslab. - L1917–L1921 Por debajo del 4 % libre, o cuando ningún segmento libre llega a 128 KiB, no hay búsqueda hacia delante.
-
L1923–L1924
Si no,
metaslab_block_picker: el primer segmento libre en el cursor o después que quepa, buscado como mucho 16 MiB más allá del primer candidato, y siempre en al menos 100 segmentos; el cursor pasa al final de lo que se tomó.
Si así no encuentra nada, el recurso alternativo toma, de un árbol de los mismos segmentos libres ordenados por tamaño, un segmento del tamaño pedido exactamente o el siguiente más grande, y mueve allí el cursor. Así, el espacio liberado detrás del cursor no se reutiliza hasta que el cursor llega al final del metaslab, o hasta que al metaslab le queda poco espacio: en la figura, el archivo de 160 MiB va después de B, no al hueco de A. Los bloques tomados uno tras otro son contiguos, y el árbol de rangos fusiona los segmentos adyacentes, de modo que un archivo escrito en un txg es un solo extent, y una sola entrada en el space map.
Liberar. Una liberación va a ms_freeing, en contexto de
sincronización. El bloque sigue asignado hasta que termina la sincronización.
Sincronizar: añadir al space map. En metaslab_sync:
metaslab_sync
Extracto de module/zfs/metaslab.c, líneas
4367–4405, OpenZFS zfs-2.4.4.
Ver en GitHub
/*
* We'll be going to disk to sync our space accounting, thus we
* drop the ms_lock during that time so allocations coming from
* open-context (ZIL) for future TXGs do not block.
*/
mutex_exit(&msp->ms_lock);
space_map_t *log_sm = spa_syncing_log_sm(spa);
if (log_sm != NULL) {
ASSERT(spa_feature_is_enabled(spa, SPA_FEATURE_LOG_SPACEMAP));
if (metaslab_unflushed_txg(msp) == 0)
metaslab_unflushed_add(msp, tx);
else if (!metaslab_unflushed_dirty(msp))
metaslab_unflushed_bump(msp, tx, B_TRUE);
space_map_write(log_sm, alloctree, SM_ALLOC,
vd->vdev_id, tx);
space_map_write(log_sm, msp->ms_freeing, SM_FREE,
vd->vdev_id, tx);
mutex_enter(&msp->ms_lock);
ASSERT3U(spa->spa_unflushed_stats.sus_memused, >=,
metaslab_unflushed_changes_memused(msp));
spa->spa_unflushed_stats.sus_memused -=
metaslab_unflushed_changes_memused(msp);
zfs_range_tree_remove_xor_add(alloctree,
msp->ms_unflushed_frees, msp->ms_unflushed_allocs);
zfs_range_tree_remove_xor_add(msp->ms_freeing,
msp->ms_unflushed_allocs, msp->ms_unflushed_frees);
spa->spa_unflushed_stats.sus_memused +=
metaslab_unflushed_changes_memused(msp);
} else {
ASSERT(!spa_feature_is_enabled(spa, SPA_FEATURE_LOG_SPACEMAP));
space_map_write(msp->ms_sm, alloctree, SM_ALLOC,
SM_NO_VDEVID, tx);
space_map_write(msp->ms_sm, msp->ms_freeing, SM_FREE,
SM_NO_VDEVID, tx);
mutex_enter(&msp->ms_lock);
}- L4373–L4396 Con un log space map, las asignaciones y liberaciones del txg van en cambio a un único registro para todo el pool, con el identificador del vdev. Véase más abajo; la figura toma la otra rama.
-
L4400–L4403
El árbol de asignación del txg se añade como entradas ALLOC, y luego
ms_freeingcomo entradas FREE, al space map propio del metaslab, sin identificador de vdev. Nada de lo que ya está en disco se reescribe.
Cada adición empieza con una palabra de depuración, que registra el txg y la pasada de
sincronización, y un árbol vacío no añade nada; ALLOC suma al total asignado del space map,
smp_alloc, y FREE le resta (L780–L783). Un metaslab sin ninguna asignación
ni liberación en un txg se deja como está. Después, ms_freeing
pasa a ser ms_freed. Cuántas palabras ocupa un extent:
space_map_write_impl
Extracto de module/zfs/space_map.c, líneas
704–739, OpenZFS zfs-2.4.4.
Ver en GitHub
zfs_btree_t *t = &rt->rt_root;
zfs_btree_index_t where;
for (zfs_range_seg_t *rs = zfs_btree_first(t, &where); rs != NULL;
rs = zfs_btree_next(t, &where, &where)) {
uint64_t offset = (zfs_rs_get_start(rs, rt) - sm->sm_start) >>
sm->sm_shift;
uint64_t length = (zfs_rs_get_end(rs, rt) -
zfs_rs_get_start(rs, rt)) >> sm->sm_shift;
uint8_t words = 1;
/*
* We only write two-word entries when both of the following
* are true:
*
* [1] The feature is enabled.
* [2] The offset or run is too big for a single-word entry,
* or the vdev_id is set (meaning not equal to
* SM_NO_VDEVID).
*
* Note that for purposes of testing we've added the case that
* we write two-word entries occasionally when the feature is
* enabled and zfs_force_some_double_word_sm_entries has been
* set.
*/
if (spa_feature_is_active(spa, SPA_FEATURE_SPACEMAP_V2) &&
(offset >= (1ULL << SM_OFFSET_BITS) ||
length > SM_RUN_MAX ||
vdev_id != SM_NO_VDEVID ||
(zfs_force_some_double_word_sm_entries &&
random_in_range(100) == 0)))
words = 2;
space_map_write_seg(sm, zfs_rs_get_start(rs, rt),
zfs_rs_get_end(rs, rt), maptype, vdev_id, words, &db,
FTAG, tx);
}- L708–L711 Desplazamiento y longitud en unidades de 4 KiB, desde el inicio del metaslab.
-
L728–L734
Dos palabras cuando el desplazamiento no cabe en 47 bits, la longitud supera
SM_RUN_MAX, 215 unidades (128 MiB aquí), o hay que guardar un identificador de vdev, y la funcionalidadspacemap_v2está activa. Se activa en cuanto se habilita, cosa que hace un pool nuevo. - L732–L733 Un parámetro de prueba, desactivado por defecto.
Las palabras. La disposición, tal como la dibuja la cabecera:
space map entries
Extracto de include/sys/space_map.h, líneas
104–140, OpenZFS zfs-2.4.4.
Ver en GitHub
/*
* debug entry
*
* 2 2 10 50
* +-----+-----+------------+----------------------------------+
* | 1 0 | act | syncpass | txg (lower bits) |
* +-----+-----+------------+----------------------------------+
* 63 62 61 60 59 50 49 0
*
*
* one-word entry
*
* 1 47 1 15
* +-----------------------------------------------------------+
* | 0 | offset (sm_shift units) | type | run |
* +-----------------------------------------------------------+
* 63 62 16 15 14 0
*
*
* two-word entry
*
* 2 2 36 24
* +-----+-----+---------------------------+-------------------+
* | 1 1 | pad | run | vdev |
* +-----+-----+---------------------------+-------------------+
* 63 62 61 60 59 24 23 0
*
* 1 63
* +------+----------------------------------------------------+
* | type | offset |
* +------+----------------------------------------------------+
* 63 62 0
*
* Note that a two-word entry will not straddle a block boundary.
* If necessary, the last word of a block will be padded with a
* debug entry (with act = syncpass = txg = 0).
*/- L104–L111 Una palabra de depuración: prefijo 10, la acción (0 ALLOC, 1 FREE), la pasada de sincronización, el txg.
- L114–L120 Una entrada de una palabra: bit más alto a 0, un desplazamiento de 47 bits, un bit de tipo y una longitud de 15 bits. La longitud se guarda menos uno, así que va de 1 a 215.
- L123–L135 Una entrada de dos palabras: prefijo 11, una longitud de 36 bits y un identificador de vdev de 24 bits, y luego el tipo y un desplazamiento de 63 bits.
- L137–L139 En la figura, aparece una palabra de relleno cuando una entrada de dos palabras empezaría en la última palabra de un bloque de 16 KiB.
Los tres tipos se distinguen por sus bits más altos. La figura codifica cada palabra con
las macros de la cabecera, BF64_ENCODE enmascarando y luego
desplazando: la entrada ALLOC de A, desplazamiento 0 y 8192 unidades, es 0x0000000000001FFF; el
extent de 160 MiB, desplazamiento 0x2400 y 40 960 unidades, es 0xC000009FFF000000 y luego
0x0000000000002400. El campo vdev de esa primera palabra vale 0:
SM_NO_VDEVID es 1 << 24, y la máscara de 24 bits lo
descarta. Las palabras se escriben aquí:
space_map_write_seg
Extracto de module/zfs/space_map.c, líneas
633–663, OpenZFS zfs-2.4.4.
Ver en GitHub
uint64_t run_len = MIN(size, run_max);
switch (words) {
case 1:
*block_cursor = SM_OFFSET_ENCODE(start) |
SM_TYPE_ENCODE(maptype) |
SM_RUN_ENCODE(run_len);
block_cursor++;
break;
case 2:
/* write the first word of the entry */
*block_cursor = SM_PREFIX_ENCODE(SM2_PREFIX) |
SM2_RUN_ENCODE(run_len) |
SM2_VDEV_ENCODE(vdev_id);
block_cursor++;
/* move on to the second word of the entry */
ASSERT3P(block_cursor, <, block_end);
*block_cursor = SM2_TYPE_ENCODE(maptype) |
SM2_OFFSET_ENCODE(start);
block_cursor++;
break;
default:
panic("%d-word space map entries are not supported",
words);
break;
}
sm->sm_phys->smp_length += words * sizeof (uint64_t);
start += run_len;
size -= run_len;
}- L633 Una longitud mayor de lo que permite el formato se corta; con entradas de dos palabras son 236 unidades, nunca alcanzadas en un metaslab.
- L636–L638 Una palabra: desplazamiento, tipo, longitud.
- L643–L651 Dos palabras: prefijo, longitud y vdev; luego tipo y desplazamiento.
- L659 El space map crece una o dos palabras. Solo crece, hasta que se condensa.
Antes de una entrada de dos palabras, la última palabra de un bloque se rellena con una palabra de depuración vacía, que la carga se salta.
Dos txg de gracia. Al final de la sincronización de cada txg:
metaslab_sync_done
Extracto de module/zfs/metaslab.c, líneas
4624–4638, OpenZFS zfs-2.4.4.
Ver en GitHub
/*
* Move the frees from the defer_tree back to the free
* range tree (if it's loaded). Swap the freed_tree and
* the defer_tree -- this is safe to do because we've
* just emptied out the defer_tree.
*/
zfs_range_tree_vacate(*defer_tree,
msp->ms_loaded ? zfs_range_tree_add : NULL, msp->ms_allocatable);
if (defer_allowed) {
zfs_range_tree_swap(&msp->ms_freed, defer_tree);
} else {
zfs_range_tree_vacate(msp->ms_freed,
msp->ms_loaded ? zfs_range_tree_add : NULL,
msp->ms_allocatable);
}-
L4630–L4631
Las liberaciones diferidas hace dos txg vuelven al árbol libre:
defer_treeesms_defer[txg % TXG_DEFER_SIZE]. - L4632–L4633 Las liberaciones de este txg ocupan su lugar.
- L4634–L4637 Solo se liberan sin diferir cuando el pool está casi lleno, o se está retirando o ampliando un vdev.
TXG_DEFER_SIZE es 2: un bloque liberado en el txg T está en el space map como
FREE tras la sincronización de T, pero solo es asignable tras la de T + 2. La cabecera dice por qué:
«a safety net for uberblock rollback», una red de seguridad para volver a un uberblock
anterior. El pool puede retroceder dos txg sin encontrar ninguno de sus bloques reutilizado. Un metaslab con
espacio diferido sigue sucio hasta que vuelven las liberaciones.
Cargar: reproducir el registro. Cada entrada del space map, en orden:
space_map_load_callback
Extracto de module/zfs/space_map.c, líneas
401–416, OpenZFS zfs-2.4.4.
Ver en GitHub
static int
space_map_load_callback(space_map_entry_t *sme, void *arg)
{
space_map_load_arg_t *smla = arg;
if (sme->sme_type == smla->smla_type) {
VERIFY3U(zfs_range_tree_space(smla->smla_rt) + sme->sme_run, <=,
smla->smla_sm->sm_size);
zfs_range_tree_add(smla->smla_rt, sme->sme_offset,
sme->sme_run);
} else {
zfs_range_tree_remove(smla->smla_rt, sme->sme_offset,
sme->sme_run);
}
return (0);
}- L405–L409 Se carga el espacio libre: una entrada FREE se añade al árbol.
- L410–L413 Una entrada ALLOC se quita de él.
El árbol empieza siendo todo el metaslab, así que reproducir el registro desde el
principio deja exactamente el espacio libre. metaslab_load reproduce hasta la
longitud de la última sincronización y después quita los árboles diferidos, que son
FREE en el space map pero aún no asignables. Un metaslab cuyo space map nunca se escribió se carga
libre. El botón Cargar de la figura hace exactamente esto y compara el resultado con el árbol que tenía.
Cuándo condensar. En cada sincronización, en su primera pasada, si el metaslab está cargado, metaslab_sync pregunta:
metaslab_should_condense
Extracto de module/zfs/metaslab.c, líneas
3835–3861, OpenZFS zfs-2.4.4.
Ver en GitHub
static boolean_t
metaslab_should_condense(metaslab_t *msp)
{
space_map_t *sm = msp->ms_sm;
vdev_t *vd = msp->ms_group->mg_vd;
uint64_t vdev_blocksize = 1ULL << vd->vdev_ashift;
ASSERT(MUTEX_HELD(&msp->ms_lock));
ASSERT(msp->ms_loaded);
ASSERT(sm != NULL);
ASSERT3U(spa_sync_pass(vd->vdev_spa), ==, 1);
/*
* We always condense metaslabs that are empty and metaslabs for
* which a condense request has been made.
*/
if (zfs_range_tree_numsegs(msp->ms_allocatable) == 0 ||
msp->ms_condense_wanted)
return (B_TRUE);
uint64_t record_size = MAX(sm->sm_blksz, vdev_blocksize);
uint64_t object_size = space_map_length(sm);
uint64_t optimal_size = space_map_estimate_optimal_size(sm,
msp->ms_allocatable, SM_NO_VDEVID);
return (object_size >= (optimal_size * zfs_condense_pct / 100) &&
object_size > zfs_metaslab_condense_block_threshold * record_size);- L3851–L3853 Un árbol libre vacío, o una condensación solicitada: siempre.
- L3855 Un registro es el mayor de dos tamaños: el tamaño de bloque del space map y un sector.
- L3857–L3858 El tamaño óptimo: una estimación del árbol libre escrito solo, a partir de su histograma de tamaños de segmento, una palabra por segmento por debajo de 128 MiB, dos por encima, más el relleno (space_map.c L1033–L1085).
- L3860–L3861 Condensar cuando el space map mide al menos el 200 % de eso, y más de 4 registros. Sin log space map, un bloque mide 16 KiB: 64 KiB. En la figura, las dos pruebas bajo la cinta.
En la figura, A, B y un C borrado dan un space map de 96 bytes frente a un óptimo de 32: el doble de largo, pero lejos de 64 KiB, así que no. Las reescrituras de una base de datos de 64 MiB, 128 bloques al azar por txg, añaden una entrada FREE por casi cada bloque antiguo; tras unos 70 txg el space map supera los 64 KiB, mientras que el árbol libre, unos doscientos segmentos, ocuparía menos de 2 KiB.
Condensar. El space map se reescribe a partir del árbol:
metaslab_condense
Extracto de module/zfs/metaslab.c, líneas
3994–4013, OpenZFS zfs-2.4.4.
Ver en GitHub
/*
* Note:
* When the log space map feature is enabled, each space map will
* always have ALLOCS followed by FREES for each sync pass. This is
* typically true even when the log space map feature is disabled,
* except from the case where a metaslab goes through metaslab_sync()
* and gets condensed. In that case the metaslab's space map will have
* ALLOCS followed by FREES (due to condensing) followed by ALLOCS
* followed by FREES (due to space_map_write() in metaslab_sync()) for
* sync pass 1.
*/
zfs_range_tree_t *tmp_tree = zfs_range_tree_create_flags(
NULL, type, NULL, start, shift,
ZFS_RT_F_DYN_NAME,
metaslab_rt_name(msp->ms_group, msp, "tmp_tree"));
zfs_range_tree_add(tmp_tree, msp->ms_start, msp->ms_size);
space_map_write(sm, tmp_tree, SM_ALLOC, SM_NO_VDEVID, tx);
space_map_write(sm, msp->ms_allocatable, SM_FREE, SM_NO_VDEVID, tx);
space_map_write(sm, condense_tree, SM_FREE, SM_NO_VDEVID, tx);
- L3994–L4003 Las entradas propias de este txg se siguen añadiendo después del space map condensado, como de costumbre.
- L4009–L4010 Una entrada ALLOC para todo el metaslab: 217 unidades, una entrada de dos palabras.
- L4011 Una entrada FREE por segmento libre.
- L4012 Después, el árbol de condensación: las liberaciones diferidas y las asignaciones de los txg en curso, que están libres según lo que sabe el space map antiguo.
Antes de escribir, el metaslab se marca como en condensación, para que no se asigne nada en él mientras tanto, y el space map se trunca a longitud 0. Reproducir el space map corto da el mismo árbol que reproducir el largo: el botón Cargar de la figura, tras una condensación, lo confirma.
Coda: el log space map
zpool create habilita todas las funcionalidades salvo que se indique lo
contrario, entre ellas log_spacemap, y entonces metaslab_sync
toma la otra rama: las asignaciones y liberaciones de cada txg, de todos los metaslabs, se
añaden a un único registro para todo el pool, como entradas de dos palabras que llevan el identificador del vdev, y
cada metaslab las mantiene en memoria como cambios sin volcar. En cada txg,
se vuelca un grupo seleccionado de metaslabs: sus cambios se escriben en sus propios space maps,
las mismas entradas que en la figura, en bloques de 128 KiB en lugar de 16 KiB. El
comentario de diseño nombra la carga de trabajo a la que se dirige: las escrituras aleatorias, que liberan un poco
en todas partes y que de otro modo tocarían los space maps de muchos metaslabs en cada txg. La
figura muestra el space map propio de un metaslab, escrito en cada sincronización, porque ahí está la idea: el registro
retrasa las mismas entradas y las agrupa.
Lo que la figura deja fuera
La figura ejecuta las funciones de arriba sobre un metaslab de un vdev, con sectores de 4 KiB y sin log space map.
Todos los bloques son de 128 KiB, así que hay un solo cursor; hay un solo txg en curso, sincronizado al instante en una
pasada, sin fase de cierre, así que ms_allocating tiene un solo árbol en uso. A qué metaslab va una
asignación, por peso y a través de el rotor sobre los vdevs, queda
fuera: todo va al metaslab 2, y una escritura que no cabe se rechaza en lugar de pasar a otro. También quedan fuera
la limitación de asignaciones, los bloques gang, las copias ditto, los checkpoints, el trim y el histograma del space
map. La condensación sigue a metaslab_should_condense, en la sincronización que escribe en el space map, como
hace metaslab_sync; las reescrituras se detienen justo antes de una sincronización así, para que se
pueda observar. La carga se muestra en un txg sin nada pendiente. El resto de la visita está en
Grupos de transacciones, para cuándo se sincroniza un txg, y en
Instantáneas y clones, para cuándo se libera un bloque.