Salle 5
Les vdevs
Un pool n’est pas une liste de disques. Ce sont des miroirs, des groupes RAID-Z, du dRAID, un périphérique de log, un périphérique special, des disques de secours et des disques en cours de remplacement, combinés comme l’administrateur l’a choisi, et chacun peut changer pendant que le pool tourne.
Si le chemin des E/S avait une branche pour chacun, et pour chaque combinaison (un disque en remplacement dans un
raidz, un disque de secours dans un miroir), il ne serait jamais fini. Le code n’a pas de telle branche. Il
construit un seul arbre de nœuds, vdev_t, accroché à spa_root_vdev,
et donne à chaque nœud une table de fonctions, vdev_ops_t. Une écriture dans le pool descend l’arbre,
et chaque nœud décide, avec son propre io_start, comment l’E/S continue vers ses enfants.
L’allocateur, lui, ne voit que les vdevs de premier niveau, rangés en classes selon leur usage.
1 Un arbre, une table par nœud
Le pool de la figure a trois vdevs de premier niveau pour les données, un miroir, un raidz1 de trois disques et un dRAID de cinq, et peut avoir un miroir pour le log, un miroir comme vdev special et un disque pour la dedup. Sélectionnez un nœud pour voir la table vers laquelle il pointe : son type n’est rien d’autre que cette table. « Log », « special » et « dedup » ne sont pas des types : ce sont des biais d’allocation d’un vdev de premier niveau. Les disques de secours et le périphérique de cache ne sont pas du tout dans l’arbre : ce sont deux listes à côté. Écrivez des blocs, et suivez chaque E/S jusqu’aux disques.
Faites glisser, ou utilisez les flèches, pour faire tourner l’arbre ; + et − pour zoomer. Cliquez sur un nœud pour le sélectionner.
Pas de WebGL dans ce navigateur : la figure reste en 2D.
À côté de l’arbre
spa_spares, disques de secours code
sdpdisk, disponibledraid1-2-0dspare, disponible, distribué : de la place sur chaque enfant du dRAID
spa_l2cache, périphériques de cache (L2ARC)
sdqdisk
Le dernier bloc
Bloc 2, un grand bloc de fichier, txg 101 : la classe normale.
- Un bloc de données de 128 Kio, plus grand que
special_small_blocks(0) : la classe normale. code - 1 DVA : un bloc de données de fichier, avec
copies=1. code DVA 0 surmirror-0. code
L’E/S, en descendant l’arbre
Le zio logique n’a pas de vdev : zio_vdev_io_start le confie à vdev_mirror_io_start, avec un enfant par DVA (1 DVA). code
- DVA 0
mirror-0mirrorvdev_mirror_io_start: vers chaque enfant codesdadiskvdev_disk_io_start: 32 secteurssdbdiskvdev_disk_io_start: 32 secteurs
Le nœud sélectionné
sdd code
| Champ | Valeur |
|---|---|
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 |
| Groupes de metaslabs | aucun : seuls les vdevs de premier niveau reçoivent des allocations |
Classes d’allocation
| Classe | Vdevs de premier niveau | Blocs |
|---|---|---|
| normale | mirror-0, raidz1-1, draid1:2d:5c:1s-2 | 1 |
| log | mirror-3 | 0 |
| special | mirror-4 | 1 |
| dedup | aucun | 0 |
| log embarqué | mirror-0, raidz1-1, draid1:2d:5c:1s-2 | 0 |
| log embarqué du special | mirror-4 | 0 |
Version texte de cette figure
L’arbre, depuis la racine :
rootrootmirror-0mirror, normalesdadisksdbdiskraidz1-1raidz, normalesdcdisksdddisksdediskdraid1:2d:5c:1s-2draid, normalesdfdisksdgdisksdhdisksdidisksdjdiskmirror-3mirror, logsdkdisksdldiskmirror-4mirror, specialsdmdisksdndisk
À côté de l’arbre : disques de secours sdp et draid1-2-0 ; cache sdq.
Le dernier bloc :
Bloc 2, un grand bloc de fichier, txg 101 : la classe normale.
- Un bloc de données de 128 Kio, plus grand que
special_small_blocks(0) : la classe normale. - 1 DVA : un bloc de données de fichier, avec
copies=1. DVA 0 surmirror-0.
L’E/S, en descendant l’arbre
Le zio logique n’a pas de vdev : zio_vdev_io_start le confie à vdev_mirror_io_start, avec un enfant par DVA (1 DVA).
- DVA 0
mirror-0mirrorvdev_mirror_io_start: vers chaque enfantsdadiskvdev_disk_io_start: 32 secteurssdbdiskvdev_disk_io_start: 32 secteurs
2 Où arrive un bloc : les classes d’allocation
Chaque vdev de premier niveau entre dans une classe de metaslabs, selon son biais :
normale, log, special ou dedup. Tout vdev qui n’est pas un log reçoit aussi un groupe dans une classe de
« log embarqué », de la place pour le ZIL sans périphérique de log, mais seul un vdev normal ou special de plus de 64 metaslabs (un réglage) y met un metaslab : le groupe d’un vdev de dedup reste vide, comme
celui d’un petit vdev. Pour chaque écriture,
spa_preferred_class choisit la classe. Un bloc de la DDT va en dedup, sinon en special, sinon en
normale. Les métadonnées et les blocs indirects des fichiers vont en special. Un bloc de données de fichier ne va
en special que s’il n’est pas plus grand que le special_small_blocks du dataset,
0 par défaut, et seulement tant que la classe special a utilisé moins de 75 % de sa
place : les derniers 25 % sont gardés pour les métadonnées. Tout le reste va en normale.
Un bloc du ZIL a son propre ordre, log, log embarqué du special, special, log embarqué,
normale. Quand une classe est pleine, l’allocation redemande, et la classe qui a échoué est sautée.
Un bloc peut aussi avoir plusieurs copies, un DVA chacune, sur des vdevs consécutifs de sa classe : les
métadonnées ont une copie de plus que le copies du dataset (avec
redundant_metadata=all, la valeur par défaut), et la DDT, qui vit dans le
MOS du pool, trois, le plafond. Dans la figure, écrivez chaque sorte de bloc avec et sans vdev
special, passez special_small_blocks à 16 Kio, puis remplissez la classe special.
3 Un remplacement est un miroir, pour un temps
zpool replace n’échange pas un disque contre un autre. Il met un nouveau nœud à la place de l’ancien
disque, un nœud replacing, avec l’ancien disque comme premier enfant et le neuf comme dernier. Un
disque de secours fait de même avec un nœud spare. Les deux tables reprennent les fonctions du
miroir, si bien que le chemin des E/S n’a besoin d’aucun code nouveau : une écriture va aux deux disques, et une
lecture saute le disque neuf pour chaque bloc qu’il n’a pas encore. Quand le resilver est terminé, l’ancien disque
est détaché et le nœud replacing disparaît. Un nœud spare reste jusqu’à ce que zed remette le disque d’origine en ligne ou que son défaut soit effacé, que le disque d’origine soit lui-même remplacé, ou que l’administrateur détache l’un des deux ; dans les deux premiers cas, c’est la fin d’un resilver qui détache le secours. Sélectionnez un disque dans la figure, remplacez-le,
écrivez et relisez, puis terminez le resilver. Remplacez aussi le disque de dedup : c’est un vdev de premier
niveau, et le nœud replacing prend son rôle.
4 dRAID : des permutations au lieu de groupes fixes
Un groupe raidz utilise toujours les mêmes disques. Un dRAID répartit des groupes de largeur fixe sur tous ses enfants, grâce à une table de permutations que le code génère à partir d’une graine fixe et vérifie contre une somme de contrôle qu’il conserve, parce que « existing pools depend on the same mapping always being generated » (les pools existants dépendent d’une table toujours générée à l’identique). Le draid1:2d:5c:1s de la figure a des groupes de trois (une parité, deux données) sur cinq enfants, et un secours distribué : dans chaque ligne de la table, un enfant n’y porte aucune colonne, seulement la place du secours. La figure calcule la même table que le code, et la même somme de contrôle. Écrivez plusieurs grands blocs et regardez chaque groupe arriver sur un autre ensemble d’enfants ; puis remplacez un enfant du dRAID par le secours distribué.
L’idée, à trois profondeurs
En une phrase
Chaque sorte de périphérique et chaque remplacement est un nœud d’un même arbre, avec sa propre table de fonctions, si bien qu’une E/S descend l’arbre et que chaque nœud la répartit à sa façon, tandis que l’allocateur choisit, selon la sorte du bloc, une classe de vdevs de premier niveau où le mettre.
En un paragraphe
Un vdev_t a un parent, des enfants, le vdev de premier niveau auquel il appartient, et un pointeur
vers un vdev_ops_t qui est son type. Le zio logique d’un bloc n’a pas de vdev :
zio_vdev_io_start le confie à l’io_start du miroir, avec les DVA du bloc comme enfants ;
chaque zio enfant atteint ensuite son vdev de premier niveau, dont l’io_start écrit chaque enfant
d’un miroir, les colonnes d’un raidz, ou un groupe de dRAID placé par une permutation fixe de ses enfants,
jusqu’aux feuilles qui émettent l’E/S disque. Seuls les vdevs de premier niveau possèdent des metaslabs, dans la
classe de leur biais ; spa_preferred_class envoie les blocs de la DDT en dedup, les métadonnées en
special, les petits blocs de données en special tant que la classe special est sous 75 %, et le reste en normale, et
zio_dva_allocate se replie sur une autre classe quand l’une est pleine. Un remplacement insère un
nœud replacing ou spare au-dessus de l’ancien disque, avec les fonctions du miroir,
jusqu’à la fin du resilver.
En détail
Un nœud de l’arbre.
struct vdev
Extrait de include/sys/vdev_impl.h, lignes
239–269, OpenZFS zfs-2.4.4.
Voir sur 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: le comportement du nœud, son type. - L243 L’état propre au type : pour un raidz, sa parité ; pour un dRAID, ses permutations et la taille de ses groupes.
- L244–L247 L’arbre : le vdev de premier niveau auquel appartient ce nœud (un vdev de premier niveau pointe sur lui-même), le parent, les enfants.
- L264–L269 Seuls les vdevs de premier niveau possèdent des metaslabs et un groupe de metaslabs : eux seuls reçoivent des allocations. Les nœuds en dessous ne font qu’acheminer les E/S.
vdev_ops_t
Extrait de include/sys/vdev_impl.h, lignes
101–126, OpenZFS zfs-2.4.4.
Voir sur 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 conversion des tailles : un raidz transforme la taille d’un bloc en une taille allouée plus grande.
- L110–L111 Le chemin des E/S. La racine n’a ni l’un ni l’autre : une E/S ne commence jamais à la racine.
- L113 Chaque type décide si un bloc touche un enfant endommagé.
- L124–L125 Le nom du type, et s’il s’agit d’une feuille.
Les types de la figure pointent vers vdev_root_ops, vdev_mirror_ops,
vdev_raidz_ops, vdev_draid_ops,
vdev_disk_ops (sous Linux), et les tables du remplacement, du secours et
du secours distribué, plus bas. La descente. Le zio logique d’un bloc n’a pas de vdev, et
zio_vdev_io_start le confie à la fonction du miroir, avec les DVA du bloc
comme enfants ; chaque zio enfant, qui a un vdev, va à l’io_start de ce
vdev.
vdev_mirror_io_start
Extrait de module/zfs/vdev_mirror.c, lignes
656–693, OpenZFS zfs-2.4.4.
Voir sur 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 Une lecture normale choisit un seul enfant : pas un enfant dont la DTL dit qu’il lui manque le txg du bloc ; un secours distribué d’abord ; sinon le moins chargé.
- L662–L669 Les écritures vont à tous les enfants.
- L671–L687 Une exception : les écritures de réparation d’une reconstruction séquentielle, quand les données sont spéculatives, ne vont qu’à l’enfant en cours de reconstruction.
- L689–L692 Un zio enfant par enfant, au même décalage et de même taille.
L’io_start d’un raidz construit sa carte et émet une colonne par enfant,
moins pour un petit bloc : la salle RAID-Z montre comment ; une lecture
normale ne lit que les colonnes de données. Le dRAID est plus bas, avec la partie 4.
Les classes. Un vdev de premier niveau entre dans une classe quand son groupe de metaslabs est créé.
vdev_metaslab_group_create
Extrait de module/zfs/vdev.c, lignes
1546–1579, OpenZFS zfs-2.4.4.
Voir sur 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 périphérique de log sans biais explicite reçoit le biais log.
- L1555–L1567 Le biais choisit la classe : log, special, dedup, ou normale. Quatre biais, six pointeurs de classe dans le pool.
- L1571–L1578 Tout vdev qui n’est pas un log reçoit un second groupe, dans la classe de log embarqué, ou dans celle du log embarqué du special pour un vdev special : de la place pour le ZIL sans périphérique de log. Un metaslab n’y est déplacé que plus tard, et seulement sur un vdev normal ou special de plus de 64 metaslabs.
spa_preferred_class
Extrait de module/zfs/spa_misc.c, lignes
2121–2156, OpenZFS zfs-2.4.4.
Voir sur 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 classe qu’une allocation a déjà essayée, s’il y en a une : au second appel, la fonction sait ce qui a échoué.
- L2144–L2147 Les blocs du ZIL ne passent jamais ici.
-
L2149–L2156
Un bloc de la DDT : dedup si le pool en a une et qu’elle n’a pas été essayée ; sinon special, si le pool en a une (
spa_special_has_ddt) et qu’elle n’a pas été essayée ; sinon normale. Un pool « a » une classe quand elle contient au moins un groupe.
spa_preferred_class
Extrait de module/zfs/spa_misc.c, lignes
2158–2179, OpenZFS zfs-2.4.4.
Voir sur 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 Pas de vdev special, ou special a déjà échoué : normale.
- L2161–L2163 Les métadonnées (un type d’objet marqué comme tel, les dnodes par exemple, pas le contenu des fichiers) et les blocs indirects des fichiers : special.
-
L2169–L2176
Un bloc de données pas plus grand que
special_small_blocks, tant que special a alloué moins de 75 % de sa place. Au-delà, les derniers 25 % sont réservés aux métadonnées.
zio_dva_allocate
Extrait de module/zfs/zio.c, lignes
4368–4407, OpenZFS zfs-2.4.4.
Voir sur 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 L’ordre : la classe habituelle, puis d’autres classes, puis un bloc gang. Pas la place du log embarqué, gardée pour le ZIL.
- L4380–L4382 Les copies du bloc, un DVA chacune, dans une seule classe.
- L4391–L4402 Quand dedup ou special déborde sur normale, le txg est noté, et les nouvelles entrées de la DDT reculent pendant quelques txg.
-
L4407
ENOSPC: redemander àspa_preferred_class; elle sait maintenant quelle classe a échoué, et l’allocation recommence dans la suivante.
Un bloc du ZIL ne passe pas ici : zio_alloc_zil essaie la classe log, le
log embarqué du special, special, le log embarqué des vdevs normaux, puis normale, avec
un seul DVA ; la salle ZIL et SLOG le suit. Les copies
ditto.
metaslab_alloc_dva_range
Extrait de module/zfs/metaslab.c, lignes
5401–5427, OpenZFS zfs-2.4.4.
Voir sur 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 Répartir les copies sur des vdevs consécutifs : seules deux pannes de vdevs de premier niveau voisins peuvent perdre un tel bloc.
- L5420–L5423 Le DVA d commence au groupe qui suit le vdev du DVA d − 1 : le vdev suivant de la classe, en faisant le tour. Une classe d’un seul vdev y met toutes les copies.
- L5424–L5427 Le premier DVA commence au rotor de la classe, qui avance selon la taille des blocs, la profondeur des files et la place libre : la salle des metaslabs.
Combien de copies : pour les métadonnées, une de plus que copies avec
redundant_metadata=all, la valeur par défaut ; pour le MOS, qui
contient la DDT, spa_max_replication, 3, et les copies d’un bloc sont plafonnées à ce même 3.
Un remplacement. Les tables du remplacement et du secours sont celles du miroir.
vdev_replacing_ops
Extrait de module/zfs/vdev_mirror.c, lignes
997–1021, OpenZFS zfs-2.4.4.
Voir sur 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
L’ouverture, la fermeture, la taille, l’
io_startet l’io_donedu miroir, comme dansvdev_mirror_opsetvdev_spare_ops. - L1019 Seul le nom change.
spa_vdev_attach
Extrait de module/zfs/spa.c, lignes
8324–8360, OpenZFS zfs-2.4.4.
Voir sur 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 disque de secours en service ne peut être remplacé que par un disque de secours libre.
-
L8356–L8359
Un disque de secours reçoit un nœud
spare, tout autre disque un nœudreplacing. Un secours distribué ne peut remplacer qu’un enfant de son propre dRAID.
vdev_add_parent
Extrait de module/zfs/vdev.c, lignes
1418–1449, OpenZFS zfs-2.4.4.
Voir sur 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
Le nouveau nœud prend le
vdev_idde l’ancien disque… - L1439–L1440 …et sa place parmi les enfants de son parent.
-
L1441–L1442
L’ancien disque devient son enfant 0 ;
spa_vdev_attachajoute ensuite le disque neuf après lui, comme dernier enfant. - L1445–L1446 Si l’ancien disque était un vdev de premier niveau, le nouveau nœud devient le vdev de premier niveau et reprend ses metaslabs, son groupe et son biais.
La DTL du disque neuf est marquée manquante de TXG_INITIAL jusqu’au txg
courant plus TXG_CONCURRENT_STATES (cette ligne, 3 et
4) : une lecture d’un bloc plus ancien le saute, et c’est ce que le resilver corrige.
spa_vdev_resilver_done_hunt
Extrait de module/zfs/spa.c, lignes
9438–9456, OpenZFS zfs-2.4.4.
Voir sur 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 Le premier enfant est le disque le plus ancien, le dernier le plus récent.
-
L9446–L9455
Quand il ne manque plus rien au disque neuf et que l’ancien n’est plus nécessaire, l’ancien disque est détaché ; le nœud
replacing, resté avec un seul enfant, est retiré.
Un nœud spare n’est pas défait par son propre resilver. Quand le disque d’origine est remis en ligne avec le drapeau unspare, que zed passe, ou que son défaut est effacé, il est marqué vdev_unspare, et la fin de son resilver détache le secours. Si le disque d’origine est lui-même remplacé, la fin de ce resilver
détache aussi le secours. Sinon l’administrateur détache l’un des deux, et un nœud
spare resté avec un seul enfant est retiré.
Le dRAID. La table des permutations est générée, pas stockée.
vdev_draid_generate_perms
Extrait de module/zfs/vdev_draid.c, lignes
506–526, OpenZFS zfs-2.4.4.
Voir sur 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
Avec pour graine
VDEV_DRAID_SEEDet la graine de la table pour ce nombre d’enfants : pour cinq, 256 permutations, graine0xf46733b7f4d47dfd. -
L514–L526
Chaque ligne est un mélange de Fisher-Yates de la précédente, avec xoroshiro128++. Le résultat est vérifié contre la somme de contrôle conservée,
0x2a1648d74pour cinq enfants ; la copie de la figure donne la même.
vdev_draid_map_alloc_row
Extrait de module/zfs/vdev_draid.c, lignes
1042–1064, OpenZFS zfs-2.4.4.
Voir sur 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 ligne de la table et sa rotation : chaque ligne sert une fois par enfant, décalée d’un cran à chaque fois.
- L1047 La colonne i est la position (groupstart + i) modulo ndisks ; ndisks est le nombre d’enfants moins les secours distribués.
- L1049–L1051 Un groupe qui dépasse la fin d’une rangée continue sur la suivante.
- L1053 L’enfant : l’entrée de la ligne à cette position, plus la rotation, modulo le nombre d’enfants.
- L1056–L1061 La taille des colonnes comme en RAID-Z ; les premières colonnes sont la parité.
Le groupe d’un bloc vient de son décalage, et zpool create fixe
ngroups pour que les groupes remplissent des rangées entières : quatre pour draid1:2d:5c:1s, dont
les groupes ont trois de large, sur l’équivalent de quatre disques parmi cinq enfants, en
rangées de 16 Mio. Le commentaire de conception dessine la
disposition. Une écriture remplit toutes les colonnes de son groupe, une colonne vide ou courte complétée
par un secteur de bourrage, puisque « dRAID always allocates a full stripe width » (le dRAID alloue
toujours une bande complète) ; une lecture normale lit les colonnes de données qui
contiennent des données. Les positions de ndisks à la dernière de chaque ligne ne reçoivent
aucune colonne : c’est la place des secours distribués. Un secours distribué est listé avec
les disques de secours, et c’est une feuille dont l’io_start envoie chaque
écriture à l’enfant qui porte la place du secours dans cette ligne.
Ce que la figure laisse de côté
Quel vdev d’une classe reçoit la première copie d’un bloc, c’est le rotor de l’allocateur qui le décide, selon la charge et la place libre : la figure avance le rotor d’un vdev par bloc, et place chaque copie sur un dRAID au début du groupe suivant, chaque copie sur un raidz après la précédente. Les tailles des blocs sont des entrées : 4 Kio pour le ZIL et les petits blocs de fichier, 16 Kio pour les métadonnées, 8 Kio pour la DDT, 128 Kio pour un grand bloc de fichier, après compression. La lecture d’un miroir prend le premier enfant possible, là où le code prend le moins chargé. Seule la classe special se remplit, et la place de son log embarqué reste libre. Les vdevs normaux et special sont supposés assez grands pour porter un metaslab de log embarqué. Un remplacement par disque à la fois ; le resilver lui-même, son ordre et la reconstruction séquentielle ne sont pas dessinés, seulement sa fin. Les vdevs retirés (indirects), les trous, l’extension de RAID-Z, les blocs gang et les erreurs sont laissés de côté. La vue 3D dessine le même état que la 2D ; aucune ne montre les décalages sur disque.