Salle 9

Metaslabs et space maps

La copie sur écriture n’écrase jamais un bloc : chaque écriture alloue de l’espace neuf et chaque réécriture libère l’ancien bloc. L’allocation est sur le chemin de chaque écriture, elle doit donc être rapide, et ce qui est libre doit être noté sur le disque, pour que le pool le sache encore après un redémarrage.

La façon évidente de le noter est un bitmap de tout le pool, un bit par secteur. Un pool de 16 Tio à secteurs de 4 Kio en a 232 : 512 Mio de bitmap à garder en mémoire, qu’il faudrait modifier sur place à chaque changement, exactement ce que la copie sur écriture interdit. ZFS fait autrement. Il découpe chaque vdev en quelques centaines de metaslabs, garde l’espace libre d’un metaslab en mémoire sous forme d’arbre de segments libres, et n’écrit sur le disque que ce qui a changé, dans une space map : un journal d’extents ALLOC et FREE auquel on ne fait qu’ajouter, et qu’on réécrit en plus court quand il est devenu trop long.

1 Écrire, supprimer, synchroniser

Ci-dessous, un vdev de 8 Gio à secteurs de 4 Kio, découpé en 16 metaslabs de 512 Mio ; toutes les écritures vont dans le metaslab 2, dessiné en dessous. Deux fichiers y sont déjà : A, 32 Mio, écrit dans le txg 10, et B, 4 Mio, dans le txg 11. La bande est la space map du metaslab, des mots de 64 bits sur le disque ; choisissez une entrée pour voir ses bits.

Supprimez A et synchronisez : une entrée FREE rejoint la bande, mais l’espace de A est différé, pas libre, pendant deux txg encore. Synchronisez deux fois pour le récupérer, puis écrivez 160 Mio : le dynamic fit ne revient pas dans le trou de A, et une longueur pareille demande une entrée de deux mots. Appuyez sur Charger pour reconstruire l’arbre libre depuis la bande, et sur Condenser : c’est refusé tant que la map est courte. Réécrivez la base de données jusqu’à ce que le test dise oui, puis condensez.

Txg ouvert 12. Libre : 476 Mio en 1 segment. Space map : 32 octets, 2 entrées. Condensation : non.

  • libre (ms_allocatable)
  • alloué, dans la map
  • alloué dans le txg ouvert
  • libéré dans le txg ouvert
  • libéré, différé
  • curseur du dynamic fit

En mémoire

  • ms_allocatable 476 Mio, 1 segment
  • ms_allocating[0] vide
  • ms_freeing vide
  • ms_defer[] vide
  • ms_lbas[17] 0x42400000 où le prochain bloc de 128 Kio est cherché

Fichiers

  • A 32 Mio · 1 extent · dans la map
  • B 4 Mio · 1 extent · dans la map

Sur le disque : la space map du metaslab 2

smp_length 32 octets (4 mots, en 1 bloc de 16 Kio) · smp_alloc 36 Mio

  1. txg 10 ALLOC
  2. ALLOC 32 Mio
  3. txg 11 ALLOC
  4. ALLOC 4 Mio

ALLOC 4 Mio, au mot 3, 1 mot

mot 3 0x00000000200003FF

préfixebits 63 (1)0offsetbits 62–16 (47)0x2000typebits 15 (1)0longueur − 1bits 14–0 (15)0x3FF

ALLOC : décalage 0x2000, en unités de 4 Kio depuis le début du metaslab (32 Mio, décalage 0x42000000 dans le vdev), longueur 1 024 unités (4 Mio). La longueur tient sur 15 bits (au plus 32 768 unités, 128 Mio) : un mot. code

Condenser ?

  • non Au moins deux fois la taille optimale, 24 octets (zfs_condense_pct 200) : 32 octets pour 48
  • non Plus de quatre blocs de 16 Kio (zfs_metaslab_condense_block_threshold) : 32 octets pour 65 536

metaslab_should_condense : non La map continue de grandir. code

Version texte de cette figure

Un vdev de 16 metaslabs ; le metaslab 2 commence à 0x40000000. Txg ouvert : 12.

  • Arbre libre (ms_allocatable), 476 Mio : [0x42400000, 0x60000000).
  • Txg ouvert 12 : alloué aucun ; libéré aucun.
  • Rien de différé.
  • Fichiers : A (32 Mio : [0x40000000, 0x42000000)) ; B (4 Mio : [0x42000000, 0x42400000)).
  • Space map : smp_length 32 octets (4 mots, en 1 bloc de 16 Kio) · smp_alloc 36 Mio. Derniers mots : 0x800400000000000A (txg 10 ALLOC, mot de débogage au mot 0 : des entrées ALLOC suivent) ; 0x0000000000001FFF (ALLOC 32 Mio, au mot 1, 1 mot) ; 0x800400000000000B (txg 11 ALLOC, mot de débogage au mot 2 : des entrées ALLOC suivent) ; 0x00000000200003FF (ALLOC 4 Mio, au mot 3, 1 mot).

Txg ouvert 12. Libre : 476 Mio en 1 segment. Space map : 32 octets, 2 entrées. Condensation : non.

L’idée, à trois profondeurs

En une phrase

ZFS garde l’espace libre par metaslab, sous forme d’arbre en mémoire et de journal en ajout seul sur le disque qui, rejoué, reconstruit l’arbre, et il réécrit le journal depuis l’arbre quand le journal est devenu bien plus long que l’arbre.

En un paragraphe

Un vdev de premier niveau est découpé en metaslabs, environ 200 par défaut, de 512 Mio à 16 Gio chacun (moins nombreux et plus petits sur les petits vdevs). Un metaslab chargé a ses segments libres dans un arbre d’intervalles, ms_allocatable. Une allocation prend un segment dans cet arbre, par défaut avec l’allocateur dynamic fit, qui cherche en avant depuis la fin de la dernière allocation de cette taille, et le met dans un arbre propre au txg, ms_allocating ; une libération va dans ms_freeing. Rien n’atteint le disque avant la synchronisation du txg : alors les allocations et libérations du txg sont ajoutées à la space map du metaslab, un mot de débogage et des entrées ALLOC, un mot de débogage et des entrées FREE, un ou deux mots de 64 bits par extent. L’espace libéré ne redevient allouable que deux txg plus tard, pour que le pool puisse revenir à un uberblock plus ancien sans trouver ses blocs réutilisés. Charger un metaslab rejoue sa map, en ajoutant les extents FREE et en retirant les ALLOC. Et quand la map fait au moins deux fois ce que l’arbre libre ferait écrit seul, et plus de quatre blocs, la synchronisation suivante la condense si le metaslab est chargé : la map est tronquée et réécrite depuis l’arbre.

En détail

Les metaslabs. Un metaslab, struct metaslab, est une plage d’un vdev avec ses arbres en mémoire : ms_allocatable, l’arbre libre, ms_allocating[], un par txg en cours, ms_freeing, ms_freed et ms_defer[] ; un commentaire de l’en-tête dessine leur cycle de vie. Leur taille vient de vdev_metaslab_set_size : il vise 200 metaslabs, entre 512 Mio et 16 Gio, et au moins 16 sur un petit vdev. Le vdev de la figure, 8 Gio, en reçoit 16 de 512 Mio. La map d’un metaslab compte en secteurs : son décalage est l’ashift du vdev, une unité vaut donc ici 4 Kio.

Prendre un bloc. Allouer dans un metaslab :

metaslab_block_alloc Extrait de module/zfs/metaslab.c, lignes 4863–4890, OpenZFS zfs-2.4.4. Voir sur 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);
	}
  1. L4864 Aucune allocation dans un metaslab dont la map est en cours de condensation : son arbre libre est en train d’être écrit.
  2. L4868 L’allocateur de la classe, dynamic fit par défaut, renvoie un décalage dans ce metaslab.
  3. L4877 L’extent sort de l’arbre libre.
  4. L4880–L4881 La première allocation d’un txg marque le metaslab comme sale : la synchronisation passera par lui.
  5. L4883–L4884 L’extent entre dans ms_allocating pour ce txg. Rien n’est encore écrit sur le disque. Dans la figure, les extents orange.

Le dynamic fit. Quel décalage l’allocateur renvoie :

metaslab_df_alloc Extrait de module/zfs/metaslab.c, lignes 1905–1931, OpenZFS zfs-2.4.4. Voir sur 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);
		}
	}
  1. L1905–L1906 Un curseur par alignement en puissance de deux, ms_lbas[]. Tous les blocs de la figure font 128 Kio, donc un seul curseur, ms_lbas[17], la marque noire au-dessus du metaslab.
  2. L1917–L1921 Sous 4 % libre, ou quand aucun segment libre n’atteint 128 Kio, pas de recherche en avant.
  3. L1923–L1924 Sinon metaslab_block_picker : le premier segment libre au curseur ou après qui convient, cherché au plus 16 Mio au-delà du premier candidat, et toujours sur au moins 100 segments ; le curseur passe à la fin de ce qui a été pris.

Si cela ne trouve rien, le repli prend, dans un arbre des mêmes segments libres triés par taille, un segment de la taille demandée exactement ou le suivant plus grand, et y déplace le curseur. L’espace libéré derrière le curseur n’est donc pas réutilisé avant que le curseur atteigne la fin du metaslab, ou que le metaslab vienne à manquer : dans la figure, le fichier de 160 Mio se place après B, pas dans le trou de A. Des blocs pris l’un après l’autre sont contigus, et l’arbre d’intervalles fusionne les segments adjacents : un fichier écrit dans un txg forme un seul extent, et une seule entrée dans la map.

Libérer. Une libération va dans ms_freeing, dans le contexte de synchronisation. Le bloc reste alloué jusqu’à la fin de la synchronisation.

Synchroniser : ajouter à la map. Dans metaslab_sync :

metaslab_sync Extrait de module/zfs/metaslab.c, lignes 4367–4405, OpenZFS zfs-2.4.4. Voir sur 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);
	}
  1. L4373–L4396 Avec une log space map, les allocations et libérations du txg vont à la place dans un seul journal pour tout le pool, avec l’identifiant du vdev. Voir plus bas ; la figure prend l’autre branche.
  2. L4400–L4403 L’arbre d’allocation du txg est ajouté en entrées ALLOC, puis ms_freeing en entrées FREE, à la map du metaslab lui-même, sans identifiant de vdev. Rien de ce qui est déjà sur le disque n’est réécrit.

Chaque ajout commence par un mot de débogage, qui note le txg et la passe, et un arbre vide n’ajoute rien ; ALLOC augmente le total alloué de la map, smp_alloc, et FREE le diminue (L780–L783). Un metaslab sans allocation ni libération dans un txg est laissé tel quel. Puis ms_freeing devient ms_freed. Combien de mots prend un extent :

space_map_write_impl Extrait de module/zfs/space_map.c, lignes 704–739, OpenZFS zfs-2.4.4. Voir sur 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);
	}
  1. L708–L711 Décalage et longueur en unités de 4 Kio, depuis le début du metaslab.
  2. L728–L734 Deux mots quand le décalage ne tient pas sur 47 bits, que la longueur dépasse SM_RUN_MAX, 215 unités (128 Mio ici), ou qu’un identifiant de vdev doit être stocké, et que la fonctionnalité spacemap_v2 est active. Elle est activée dès qu’elle est autorisée, ce que fait un nouveau pool.
  3. L732–L733 Un réglage de test, désactivé par défaut.

Les mots. La disposition, telle que l’en-tête la dessine :

space map entries Extrait de include/sys/space_map.h, lignes 104–140, OpenZFS zfs-2.4.4. Voir sur 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).
 */
  1. L104–L111 Un mot de débogage : préfixe 10, l’action (0 ALLOC, 1 FREE), la passe, le txg.
  2. L114–L120 Une entrée d’un mot : bit de poids fort à 0, un décalage de 47 bits, un bit de type et une longueur de 15 bits. La longueur est stockée moins un, elle va donc de 1 à 215.
  3. L123–L135 Une entrée de deux mots : préfixe 11, une longueur de 36 bits et un identifiant de vdev de 24 bits, puis le type et un décalage de 63 bits.
  4. L137–L139 Dans la figure, un mot de remplissage apparaît quand une entrée de deux mots commencerait sur le dernier mot d’un bloc de 16 Kio.

Les trois sortes se distinguent par leurs bits de poids fort. La figure encode chaque mot avec les macros de l’en-tête, BF64_ENCODE masquant puis décalant : l’entrée ALLOC de A, décalage 0 et 8192 unités, vaut 0x0000000000001FFF ; l’extent de 160 Mio, décalage 0x2400 et 40960 unités, vaut 0xC000009FFF000000 puis 0x0000000000002400. Le champ vdev de ce premier mot vaut 0 : SM_NO_VDEVID vaut 1 << 24, et le masque de 24 bits le fait disparaître. Les mots sont écrits ici :

space_map_write_seg Extrait de module/zfs/space_map.c, lignes 633–663, OpenZFS zfs-2.4.4. Voir sur 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;
	}
  1. L633 Une longueur au-delà de ce que le format permet est coupée ; en deux mots, c’est 236 unités, jamais atteint dans un metaslab.
  2. L636–L638 Un mot : décalage, type, longueur.
  3. L643–L651 Deux mots : préfixe, longueur et vdev ; puis type et décalage.
  4. L659 La map grandit d’un ou deux mots. Elle ne fait que grandir, jusqu’à sa condensation.

Avant une entrée de deux mots, le dernier mot d’un bloc est rempli d’un mot de débogage vide, que le chargement ignore.

Deux txg de grâce. À la fin de la synchronisation de chaque txg :

metaslab_sync_done Extrait de module/zfs/metaslab.c, lignes 4624–4638, OpenZFS zfs-2.4.4. Voir sur 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);
	}
  1. L4630–L4631 Les libérations différées il y a deux txg reviennent dans l’arbre libre : defer_tree est ms_defer[txg % TXG_DEFER_SIZE].
  2. L4632–L4633 Les libérations de ce txg prennent leur place.
  3. L4634–L4637 Pas de report seulement quand le pool est presque plein, ou qu’un vdev est retiré ou agrandi.

TXG_DEFER_SIZE vaut 2 : un bloc libéré dans le txg T est FREE dans la map après la synchronisation de T, mais allouable seulement après celle de T + 2. L’en-tête dit pourquoi : « a safety net for uberblock rollback », un filet de sécurité en cas de retour à un uberblock antérieur. Le pool peut reculer de deux txg sans trouver aucun de ses blocs réutilisé. Un metaslab qui a de l’espace différé reste sale jusqu’au retour des libérations.

Charger : rejouer le journal. Chaque entrée de la map, dans l’ordre :

space_map_load_callback Extrait de module/zfs/space_map.c, lignes 401–416, OpenZFS zfs-2.4.4. Voir sur 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);
}
  1. L405–L409 On charge l’espace libre : une entrée FREE est ajoutée à l’arbre.
  2. L410–L413 Une entrée ALLOC en est retirée.

L’arbre part de tout le metaslab : rejouer le journal depuis le début laisse donc exactement l’espace libre. metaslab_load rejoue jusqu’à la longueur de la dernière synchronisation, puis retire les arbres différés, FREE dans la map mais pas encore allouables. Un metaslab dont la map n’a jamais été écrite se charge libre. Le bouton Charger de la figure fait exactement cela et compare le résultat à l’arbre qu’il avait.

Quand condenser. À chaque synchronisation, à sa première passe, si le metaslab est chargé, metaslab_sync demande :

metaslab_should_condense Extrait de module/zfs/metaslab.c, lignes 3835–3861, OpenZFS zfs-2.4.4. Voir sur 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);
  1. L3851–L3853 Un arbre libre vide, ou une condensation demandée : toujours.
  2. L3855 Un enregistrement vaut le plus grand de la taille de bloc de la map et d’un secteur.
  3. L3857–L3858 La taille optimale : une estimation de l’arbre libre écrit seul, d’après son histogramme des tailles de segments, un mot par segment sous 128 Mio, deux au-dessus, plus le remplissage (space_map.c L1033–L1085).
  4. L3860–L3861 Condenser quand la map fait au moins 200 % de cela, et plus de 4 enregistrements. Sans log space map, un bloc fait 16 Kio : 64 Kio. Dans la figure, les deux tests sous la bande.

Dans la figure, A, B et un C supprimé font une map de 96 octets pour un optimum de 32 : deux fois plus longue, mais loin de 64 Kio, donc non. Les réécritures d’une base de données de 64 Mio, 128 blocs au hasard par txg, ajoutent une entrée FREE pour presque chaque ancien bloc ; après environ 70 txg la map dépasse 64 Kio, alors que l’arbre libre, environ deux cents segments, tiendrait en moins de 2 Kio.

Condenser. La map est réécrite depuis l’arbre :

metaslab_condense Extrait de module/zfs/metaslab.c, lignes 3994–4013, OpenZFS zfs-2.4.4. Voir sur 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);
  1. L3994–L4003 Les entrées propres à ce txg sont encore ajoutées après la map condensée, comme d’habitude.
  2. L4009–L4010 Une entrée ALLOC pour tout le metaslab : 217 unités, une entrée de deux mots.
  3. L4011 Une entrée FREE par segment libre.
  4. L4012 Puis l’arbre de condensation : les libérations différées et les allocations des txg en cours, libres pour ce que sait l’ancienne map.

Avant d’écrire, le metaslab est marqué en condensation, pour que rien n’y soit alloué entre-temps, et la map est tronquée à la longueur 0. Rejouer la map courte donne le même arbre que rejouer la longue : le bouton Charger de la figure, après une condensation, le dit.

Coda : la log space map

zpool create autorise toutes les fonctionnalités sauf indication contraire, dont log_spacemap, et metaslab_sync prend alors l’autre branche : les allocations et libérations de chaque txg, pour tous les metaslabs, sont ajoutées à un seul journal pour tout le pool, en entrées de deux mots qui portent l’identifiant du vdev, et chaque metaslab les garde en mémoire comme changements non vidés. À chaque txg, un groupe choisi de metaslabs est vidé : leurs changements sont écrits dans leurs propres maps, les mêmes entrées que dans la figure, en blocs de 128 Kio au lieu de 16. Le commentaire de conception nomme la charge visée : les écritures aléatoires, qui libèrent un peu partout et toucheraient sinon les maps de nombreux metaslabs à chaque txg. La figure montre la map propre d’un metaslab, écrite à chaque synchronisation, parce que l’idée est là : le journal retarde les mêmes entrées et les regroupe.

Ce que la figure laisse de côté

La figure exécute les fonctions ci-dessus sur un metaslab d’un vdev, à secteurs de 4 Kio et sans log space map. Tous les blocs font 128 Kio, donc un seul curseur ; un seul txg est en cours, synchronisé aussitôt en une passe, sans clôture, si bien que ms_allocating n’a qu’un arbre utilisé. Le choix du metaslab où va une allocation, par poids et par le rotor sur les vdevs, est laissé de côté : tout va dans le metaslab 2, et une écriture qui ne tient pas est refusée au lieu de passer ailleurs. De même la régulation des allocations, les blocs gang, les copies ditto, les checkpoints, le trim et l’histogramme de la space map. La condensation suit metaslab_should_condense, à la synchronisation qui écrit dans la map, comme le fait metaslab_sync ; les réécritures s’arrêtent juste avant une telle synchronisation, pour qu’on puisse la regarder. Le chargement se fait sur un txg sans rien en attente. La suite de la visite est dans Groupes de transactions pour le moment où un txg se synchronise, et dans Instantanés et clones pour le moment où un bloc est libéré tout court.