Sommaire
- Btrfs et les quotas : intro
- Jouons avec les quotas
- Cas pratique avec snapper
- Btrfs et les quotas : outro
- Liens
« Btrfs et openSUSE » est une série de journaux sur le système de fichiers Btrfs, basée sur ma propre expérience d'utilisateur d'openSUSE. Au menu :
- des généralités sur Btrfs ☑
- des noms qui pètent : sous-volumes, snapshots, rollbacks ☑
- du snapper ☑
- du grub ☑
- de la mise à jour incrémentale ☑
- des quotas
- de la maintenance ☑
- des trucs spécifiques à openSUSE
- des tentatives désespérées pour rester applicable à d'autres distributions
- des erreurs (pas taper)
- des bugs
- des cris
- des larmes
- et bien plus ! ou bien moins
Aujourd'hui, l'épisode 5 : les quotas.
Btrfs et les quotas : intro
Les quotas, qu'est-ce que c'est ?
Un quota, dans le contexte d'un système de fichiers, c'est une limite imposée à un ou plusieurs utilisateurs sur la quantité de données qu'il(s) peu(ven)t conserver dans un ou plusieurs emplacements de l'arborescence.
Typiquement, cela est utile dans le cas d'un système multi-utilisateurs, afin de garantir un partage équitable des ressources.
Les qgroups
Btrfs n'utilise pas la définition traditionnelle des quotas. Pour lui, un quota ne s'applique pas directement à un utilisateur mais plutôt à un sous-volume (par exemple @/home/toto
) ou à un groupe de sous-volumes (par exemple @/home/toto/
+ @/home/toto/.snapshots
).
On parle alors de qgroup pour exprimer ces ensembles limités en taille. Ou plutôt en tailles.
En effet, l'utilisation du Copy-on-Write fait que des fichiers situés dans des sous-volumes différents – et donc comptabilisés dans des qgroups différents – peuvent partager la même donnée au niveau bloc, par exemple suite à la création d'un instantané.
Dans ce cas, à quel sous-volume appliquer une limite ? Au sous-volume d'origine ? À tous les sous-volumes ?
Btrfs résout ce problème en permettant de fixer des limites pour deux types de taille :
- la taille totale des données référencées par ce qgroup, potentiellement partagées avec d'autres qgroups ;
- la taille des données exclusives à ce qgroup, non partagées avec d'autres qgroups.
Jouons avec les quotas
Activer les quotas : btrfs quota
Sur un système de fichiers fraîchement créé, les quotas sont désactivés.
La commande btrfs quota
permet de les activer :
~# btrfs quota
usage: btrfs quota <command> [options] <path>
btrfs quota enable <path>
Enable subvolume quota support for a filesystem.
btrfs quota disable <path>
Disable subvolume quota support for a filesystem.
btrfs quota rescan [-sw] <path>
Trash all qgroup numbers and scan the metadata again with the current config.
manage filesystem quota settings
~# btrfs quota enable /
~#
Lors de la première activation, Btrfs va automatiquement lancer une réanalyse des quotas et créer des qgroups pour les sous-volumes existants.
Gérer les qgroups : btrfs qgroup
Afficher les qgroups : show
Tout simplement :
~# btrfs qgroup show /
qgroupid rfer excl
-------- ---- ----
0/5 16.00KiB 16.00KiB
0/257 2.36MiB 2.36MiB
0/258 16.00KiB 16.00KiB
0/259 16.00KiB 16.00KiB
0/260 1.23MiB 1.23MiB
0/261 12.58MiB 12.58MiB
0/262 16.00KiB 16.00KiB
0/263 16.00KiB 16.00KiB
0/264 16.00KiB 16.00KiB
0/265 16.00KiB 16.00KiB
0/266 16.00KiB 16.00KiB
0/267 403.54MiB 403.54MiB
0/268 16.00KiB 16.00KiB
0/269 340.00KiB 340.00KiB
0/270 2.16GiB 2.16GiB
0/275 16.00KiB 16.00KiB
0/2779 16.00KiB 16.00KiB
0/5692 1.23GiB 1.23GiB
0/6046 16.00KiB 16.00KiB
0/6047 8.03GiB 248.00KiB
0/6048 8.03GiB 276.00KiB
0/6049 8.03GiB 160.33MiB
0/6050 8.03GiB 424.00KiB
0/6051 8.03GiB 30.09MiB
~#
Ici, on voit les qgroups qui ont été créés automatiquement lors de l'activation des quotas. Chaque sous-volume est rangé dans un qgroup, 0/x
ou x
est l'identifiant du sous-volume. On parle de qgroup de niveau 0.
Deux autres colonnes sont présentes :
- rfer, qui représente la quantité de données que référence le qgroup.
- excl, qui représente la quantité de données exclusive à ce qgroup.
Par exemple, pour le sous-volume 6051 :
~# btrfs subvolume list / | grep 6051
ID 6051 gen 447643 top level 275 path @/.snapshots/101/snapshot # c'est un instantané
~# btrfs qgroup show / | awk 'NR <= 2 || /6051/'
qgroupid rfer excl
-------- ---- ----
0/6051 8.03GiB 30.11MiB
~#
Le qgroup associé 0/6051
référence 8,03 Gio de données dont 30,11 Mio ne sont pas partagées avec d'autres qgroups (donc d'autres sous-volumes).
C'est beaucoup plus rapide de connaître la taille d'un sous-volume entier avec btrfs qgroup show
, qui garde le compte de la quantité de données présentes, qu'avec btrfs filesystem du
, qui doit faire le compte à chaque appel.
Enfin, il est possible d'utiliser des options pour détailler l'affichage :
- Les options
-r
et-e
permettent de rajouter deux autres colonnes pour afficher les limites de tailles appliquées. Par défaut, aucune limite n'est appliquée. - Les options
-p
et-c
permettent d'afficher les liens de parenté entre les qgroups.
~# btrfs qgroup show -pcre / | awk 'NR <= 2 || /6051/'
qgroupid rfer excl max_rfer max_excl parent child
-------- ---- ---- -------- -------- ------ -----
0/6051 8.03GiB 30.11MiB none none --- ---
~#
Supprimer des qgroups : destroy
Si les quotas sont activés, un nouveau qgroup 0/x
est créé automatiquement à la création d'un sous-volume/snapshot avec l'ID x
. Cependant, aucun qgroup n'est détruit à la suppression d'un sous-volume. Cela se voit très bien après la suppression de quelques instantanés :
~# btrfs subvolume delete 6063 /
~# btrfs subvolume delete 6064 /
~# btrfs subvolume delete 6065 /
~# btrfs qgroup show / | awk 'NR <= 2 || /606/'
qgroupid rfer excl
-------- ---- ----
0/6063 0.00B 0.00B
0/6064 0.00B 0.00B
0/6065 0.00B 0.00B
~#
Il est possible de supprimer ces qgroups vides avec btrfs qgroup destroy
:
~# btrfs qgroup destroy 0/6063 /
~# btrfs qgroup destroy 0/6064 /
~# btrfs qgroup destroy 0/6065 /
~#
La suppression automatique de ces qgroups de niveau 0 sera peut-être implémentée prochainement. En attendant, on peut utiliser snapper qui se charge de ce nettoyage.
Créer des hiérarchies de qgroups : create
et assign
Un qgroup est un groupe de sous-volumes…
… ou un groupe de qgroups.
En effet, on n'est pas limité à un seul niveau on peut avoir un arbre de qgroups !
+---+
|2/1|
+---+
/ \
+---+/ \+---+
|1/1| |1/2|
+---+ +---+
/ \ / \
+---+/ \+---+/ \+---+
qgroups |0/1| |0/2| |0/3|
+-+-+ +---+ +---+
| / \ / \
| / \ / \
| / \ / \
extents 1 2 3 4
La commande pour assigner un qgroup à un qgroup parent est la suivante :
~# btrfs qgroup assign --help
usage: btrfs qgroup assign [options] <src> <dst> <path>
Assign SRC as the child qgroup of DST
--rescan schedule qutoa rescan if needed
--no-rescan don't schedule quota rescan
Du coup, pour créer un arbre similaire à celui représenté plus haut :
~# # qgroups de niveau 1
~# btrfs qgroup create 1/1 /
~# btrfs qgroup create 1/2 /
~#
~# # qgroup de niveau 2
~# btrfs qgroup create 2/1 /
~#
~# # Assignation
~# btrfs qgroup assign 1/1 2/1 /
~# btrfs qgroup assign 1/2 2/1 /
Cela n'a pas de sens de créer les qgroups de niveau 0 à la main, vu qu'ils correspondent directement à un sous-volume et que Btrfs les crée pour nous.
Il est cependant possible de combiner la création d'un sous-volume à l'assignation à un qgroup parent :
~# # L'option -i ("inherit") est aussi disponible sur btrfs subvolume snapshot
~# btrfs subvolume create -i 1/1 /a
~# btrfs subvolume create -i 1/1 -i 1/2 /b
~# btrfs subvolume create -i 1/2 /c
Et voilà le résultat !
~# btrfs qgroup show -pc / | awk 'NR <= 2 || /1\/[12]/'
qgroupid rfer excl parent child
-------- ---- ---- ------ -----
0/6119 16.00KiB 16.00KiB 1/1 ---
0/6120 16.00KiB 16.00KiB 1/1,1/2 ---
0/6121 16.00KiB 16.00KiB 1/2 ---
1/1 32.00KiB 32.00KiB 2/1 0/6119,0/6120
1/2 32.00KiB 32.00KiB 2/1 0/6120,0/6121
2/1 48.00KiB 48.00KiB --- 1/1,1/2
~#
Fixer des limites à un qgroup : limit
Voilà, voilà, on y arrive : il est possible de mettre des limites aux tailles d'un qgroup. Et c'est heureux parce que c'est un peu le but de toute cette affaire.
~# btrfs qgroup limit --help
usage: btrfs qgroup limit [options] <size>|none [<qgroupid>] <path>
Set the limits a subvolume quota group.
-c limit amount of data after compression. This is the default,
it is currently not possible to turn off this option.
-e limit space exclusively assigned to this qgroup
~#
Exemple :
~# # Créons les sous-volumes '/test', '/test/a' et '/test/b'
~# btrfs subvolume create /test/
Create subvolume '//test'
~# btrfs subvolume create /test/a
Create subvolume '//test/a'
~# btrfs subvolume create /test/b
Create subvolume '//test/b'
~# btrfs qgroup show -erF /test/a/
qgroupid rfer excl max_rfer max_excl
-------- ---- ---- -------- --------
0/6067 16.00KiB 16.00KiB none none
~#
~# # Assignons des limites à '/test/a'
~# btrfs qgroup limit 10m 0/6067 /
~# btrfs qgroup limit -e 5m 0/6067 /
~# btrfs qgroup show -erF /test/a/
qgroupid rfer excl max_rfer max_excl
-------- ---- ---- -------- --------
0/6067 16.00KiB 16.00KiB 10.00MiB 5.00MiB
~#
On a créé nos limites. Maintenant, testons si elles marchent :
~# dd if=/dev/urandom of=/test/a/bigfile bs=1M count=50
dd: erreur d´écriture de '/test/a/bigfile': Débordement du quota d´espace disque
5+0 enregistrements lus
4+0 enregistrements écrits
5046272 bytes (5,0 MB, 4,8 MiB) copied, 1,04639 s, 4,8 MB/s
~# btrfs qgroup show --sync -erF /test/a/
qgroupid rfer excl max_rfer max_excl
-------- ---- ---- -------- --------
0/6067 4.83MiB 4.83MiB 10.00MiB 5.00MiB
~#
Pour la taille exclusive, cela semble marcher. On a essayé de créer un fichier de 50 Mio en faisant 50 blocs de 1 Mio, mais une erreur quota est survenue au moment de l'écriture du 5e Mio.
Mais essayons d'aller plus loin, pour la taille rfer
:
~# rm /test/a
~# dd if=/dev/urandom of=/test/b/bigfile bs=1M count=50
50+0 enregistrements lus
50+0 enregistrements écrits
52428800 bytes (52 MB, 50 MiB) copied, 0,251852 s, 208 MB/s
~# cp --reflink /test/b/bigfile /test/a/
~# btrfs qgroup show --sync -erF /test/a/
qgroupid rfer excl max_rfer max_excl
-------- ---- ---- -------- --------
0/6067 50.02MiB 16.00KiB 10.00MiB 5.00MiB
~# touch /test/b/nothing
~# cp --reflink /test/b/nothing /test/a/
cp: impossible de créer le fichier standard '/test/a/nothing': Débordement du quota d´espace disque
~#
On remarque truc bizarre : sur un premier cp --reflink
, le quota est allègrement dépassé, sans message d'erreur. Une fois le quota dépassé, une erreur est effectivement levée sur le second cp --reflink
. Mais pourquoi ?
Transactions, estimations, reflink
et quotas
tl;dr C'est un bug (boo#1057962).
Chaque opération sur le système de fichiers est encapsulée dans une transaction. La transaction n'est pas écrite immédiatement : elle est d'abord construire en mémoire. L'espace disque est d'abord réservé.
Avec Btrfs, les données et métadonnées sont mises à des adresses distinctes des données et métadonnées précédentes (Copy-on-Write) ; des arbres (B) distincts les référençant sont créés. Au bout de 30 secondes (durée réglable via l'option de montage commit
) ou sur une synchronisation demandée (par exemple avec la commande btrfs filesystem sync
), ce qui était en mémoire est écrit sur le disque. En tout dernier, le superblock est réécrit de façon atomique pour pointer vers les nouveaux arbres. Ce mécanisme garantit que le système de fichiers est toujours consistant à un instant t.
Bref, toute cette digression inutile pour dire que pour les quotas, c'est pareil : l'espace par rapport aux quotas est d'abord réservé. Le seul moment où une erreur de dépassement de quota peut être levée, c'est au début de l'opération, pas au milieu. Le système de fichiers – ce n'est pas spécifique à Btrfs – doit donc estimer l'impact que va avoir l'opération sur la quantité de données référencées par les sous-volumes.
Et c'est là que le bât blesse.
Pour l'opération cp --reflink
, c'est-à-dire l'opération de clonage de fichier clone_file_range
implémentée par btrfs_clone_file_range
, l'estimation n'est pas faite. Plus exactement : elle semble faite pour les métadonnées mais pas pour les données. D'où le comportement observé. Pour le moment du moins ☺
Cas pratique avec snapper
Note : pour cette partie, je réutilise des bouts d'un article que j'ai écrit sur le forum Alionet.
Un problème de taille
On en avait parlé dans l'épisode 2 : la configuration par défaut de snapper est aussi agressive sur la prise d'instantanés qu'elle est laxiste sur leur nettoyage. D'où des problèmes de systèmes de fichiers saturés par des instantanés.
Depuis la version 0.3, snapper permet d'utiliser les quotas pour nettoyer les instantanés en fonction de l'espace disque utilisé.
Il est à noter qu'il n'y a pas d'utilisation des limites telles qu'on peut les définir avec btrfs qgroup limit
: snapper n'utilise les quotas que pour récupérer rapidement la taille des snapshots.
En outre, snapper peut avoir avoir plusieurs configurations, chacune gérant les instantané d'un sous-volume. Par exemple, on peut imaginer la configuration toto
pour @/home/toto
, tata
pour @/home/tata
, etc. Chaque configuration aurait un qgroup distinct (1/1
, 1/2
, …) dans le rôle de parent, permettant de fixer à chaque utilisateur des quotas pour ses snapshots.
La commande magique : snapper setup-quota
snapper propose une commande pour activer les quotas comme il faut : snapper setup-quota
. Le principe est simple :
- Créer un qgroup de niveau 1,
1/0
(btrfs qgroup create 1/0 /
). - Définir le qgroup de tout nouvel instantané comme enfant de
1/0
(btrfs subvolume snapshot -i 1/0 /.snapshots/<id>/snapshot
). - Supprimer les instantanés les plus anciens quand la taille exclusive de
1/0
dépasse un seuil prédéfini.
La commande va donc créer les paramètres QGROUP
et SPACE_LIMIT
dans la configuration de snapper (/etc/snapper/configs/root
) :
# QGROUP est l'identifiant de votre groupe d'instantanés ; par défaut 1/0
# Les instantanés seront enregistrés comme des qgroups enfants de $QGROUP
QGROUP="1/0"
# Fraction maximale d’espace disque que snapper peut utiliser pour stocker les instantanés
# 50 % ici, soit tout de même 20 Go pour une partition racine de 40 Go ; vous pouvez l'ajuster comme vous le voulez
SPACE_LIMIT="0.5"
50% c'est beaucoup quand même… mettons 10% plutôt :
~# snapper set-config SPACE_LIMIT=0.1
~#
Pour le reste de la configuration, soyons fous et laissons les valeurs par défaut.
On laisse tourner quelques jours :
~# btrfs subvolume list -s /
ID 258 gen 12588 top level 257 path .snapshots/1/snapshot
ID 366 gen 12102 top level 257 path .snapshots/3/snapshot
ID 369 gen 12102 top level 257 path .snapshots/6/snapshot
ID 370 gen 12102 top level 257 path .snapshots/7/snapshot
ID 371 gen 12102 top level 257 path .snapshots/8/snapshot
ID 372 gen 12102 top level 257 path .snapshots/9/snapshot
ID 373 gen 12102 top level 257 path .snapshots/10/snapshot
ID 379 gen 12102 top level 257 path .snapshots/15/snapshot
ID 382 gen 12102 top level 257 path .snapshots/18/snapshot
ID 383 gen 12102 top level 257 path .snapshots/19/snapshot
ID 389 gen 12102 top level 257 path .snapshots/25/snapshot
ID 394 gen 12102 top level 257 path .snapshots/30/snapshot
ID 396 gen 12102 top level 257 path .snapshots/31/snapshot
ID 401 gen 12102 top level 257 path .snapshots/35/snapshot
ID 402 gen 12102 top level 257 path .snapshots/36/snapshot
ID 403 gen 12102 top level 257 path .snapshots/37/snapshot
ID 404 gen 12102 top level 257 path .snapshots/38/snapshot
ID 406 gen 12102 top level 257 path .snapshots/39/snapshot
ID 411 gen 12102 top level 257 path .snapshots/44/snapshot
ID 416 gen 12102 top level 257 path .snapshots/49/snapshot
ID 417 gen 12102 top level 257 path .snapshots/50/snapshot
ID 418 gen 12102 top level 257 path .snapshots/51/snapshot
ID 419 gen 12102 top level 257 path .snapshots/52/snapshot
ID 421 gen 12102 top level 257 path .snapshots/54/snapshot
ID 426 gen 12102 top level 257 path .snapshots/59/snapshot
ID 428 gen 12120 top level 257 path .snapshots/60/snapshot
ID 429 gen 12120 top level 257 path .snapshots/61/snapshot
ID 430 gen 12120 top level 257 path .snapshots/62/snapshot
ID 431 gen 12120 top level 257 path .snapshots/63/snapshot
ID 432 gen 12120 top level 257 path .snapshots/64/snapshot
ID 433 gen 12120 top level 257 path .snapshots/65/snapshot
ID 434 gen 12120 top level 257 path .snapshots/66/snapshot
ID 435 gen 12120 top level 257 path .snapshots/67/snapshot
ID 436 gen 12120 top level 257 path .snapshots/68/snapshot
ID 437 gen 12120 top level 257 path .snapshots/69/snapshot
ID 438 gen 12120 top level 257 path .snapshots/70/snapshot
ID 439 gen 12120 top level 257 path .snapshots/71/snapshot
ID 441 gen 12161 top level 257 path .snapshots/72/snapshot
ID 442 gen 12243 top level 257 path .snapshots/73/snapshot
ID 443 gen 12319 top level 257 path .snapshots/74/snapshot
ID 444 gen 12394 top level 257 path .snapshots/75/snapshot
ID 445 gen 12495 top level 257 path .snapshots/76/snapshot
ID 446 gen 12587 top level 257 path .snapshots/77/snapshot
~#
Je vous l'avais dit, les réglages de snapper sont barbares ! Et là les quotas sont activés, je vous laisse imaginer sans ! Bref, ça passe :
~# btrfs qgroup show / | awk 'NR <= 2 || /1\/0/'
qgroupid rfer excl
-------- ---- ----
1/0 10.20GiB 4.16GiB
~#
1/0
est le qgroup parent de tous les instantanés pris par snapper. Il référence 10,20 Gio de données mais seulement 4,16 Gio ne sont pas partagés avec d'autres qgroups (hors enfants), c'est-à-dire avec le sous-volume /
actuel.
Ainsi, tous les instantanés « pèsent » 4,16 Gio, soit environ 10% de mon espace disque
Btrfs et les quotas : outro
Statut de la fonctionnalité : Mostly OK
Le wiki Btrfs indique que la fonctionnalité des quotas est mostly ok. Des bugs et des limitations subsistent.
En voici une liste non-exhaustive, en plus de ce que l'on a vu plus haut :
- La commande
btrfs qgroup show
n'affiche pas certaines informations : par exemple, il n'est pas possible pour un qgroup parent de voir les sous-volumes des qgroups enfants. - Créer un qgroup à partir d'un dossier existant affichera une utilisation de 0 jusqu'à une réanalyse complète ne soit effectué (avec
btrfs quota rescan
). - Combiner les quotas avec beaucoup d'instantanés peut causer des problèmes de performances, notamment au moment de supprimer des instantanés (boo#1017461, a priori résolu maintenant).
- Il est possible que le flag indiquant une réanalyse des quotas en cours se bloque (boo#1047152) ; le correctif appliqué sur openSUSE Leap a été mergé dans le noyau 4.14.
Sur openSUSE
Malgré ces soucis et limitations, openSUSE active les quotas Btrfs par défaut depuis la sortie de Leap 42.2.
À cette époque, la fonctionnalité avait une implémentation encore plus fragile. Son activation – avant tout pour exploiter les fonctionnalités de snapper 0.3 – avait alors entraîné de vives discussions sur la liste de diffusion opensuse-factory, alors que la distribution n'était qu'en beta. Cette dernière est tout de même sortie comme prévue, moyennant des backports et même quelques correctifs en avance de l'upstream.
Au final, malgré quelques bugs gênants, notamment ces cas de forts ralentissements en cas de btrfs balance
ou de suppression d'instantanés, j'ai l'impression que cela aurait pu être pire.
openSUSE continue d'utiliser la fonctionnalité, uniquement pour snapper et les instantanés de /
et toujours avec quelques patchs en avance sur l'upstream.
That's all folks!
Liens
- Sur le wiki Btrfs, la page sur les quotas.
- Btrfs Subvolume Quota Groups, par Arne Jansen : un aperçu du concept des qgroups et de leur implémentation originelle.
- Dans la série LWN's guide to Btrfs par Jonathan Corbet, il y a une partie sur les quotas dans l'épisode Btrfs: Subvolumes and snapshots.
-
man btrfs-quota
.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.