Le stockage multi-tenant échoue de la manière la moins poétique qui soit : le pool atteint 100 %, les mises à jour des métadonnées se bloquent,
et votre « petit problème » devient une panne accompagnée d’une invitation à une réunion. Un locataire bruyant n’a pas besoin de malveillance ;
un cache de build qui déraille ou une boucle de logs suffisent.
ZFS vous donne les outils pour garder chaque locataire dans sa voie. L’astuce consiste à choisir le bon type de quota, à le placer
sur la bonne frontière, et à comprendre comment les instantanés et les reservations modifient votre modèle mental.
Si vous vous trompez sur l’un d’eux, vous n’avez pas appliqué l’équité — vous venez d’inventer un nouveau mode de panne.
Objectifs de conception : ce que signifie réellement « multi-tenant sûr »
La sécurité multi-tenant sur ZFS n’est pas « tout le monde a un quota ». Il s’agit d’un ensemble de résultats explicites :
- Un locataire ne peut pas remplir le pool (ou s’il le peut, vous le remarquez tôt et le rayon d’impact est limité).
- L’espace libre du pool reste au-dessus d’un plancher de sécurité pour que ZFS puisse allouer, vider et maintenir une latence raisonnable.
- Les locataires reçoivent des erreurs prévisibles : idéalement EDQUOT (quota dépassé), pas ENOSPC (pool plein), et pas « tout est lent ».
- Les opérations peuvent expliquer l’utilisation d’espace sans gymnastique interprétative : « ce dataset est volumineux à cause des instantanés » est une réponse valable.
- La suppression fonctionne quand c’est nécessaire. « Disque plein et on ne peut pas supprimer » est un classique cauchemar du stockage.
Vous concevez des frontières. Les datasets sont ces frontières. Les quotas les font respecter. Les reservations les garantissent.
Et les instantanés sont les gremlins qui traversent les frontières — il faut en tenir compte sinon vous faites juste de l’art de performance.
Conseil d’opinion : utilisez des datasets comme conteneurs de locataire, pas seulement des répertoires. Si vous ne pouvez pas poser
une propriété ZFS dessus, vous ne pouvez pas la gouverner de façon fiable.
Faits intéressants et contexte historique
- ZFS a été créé chez Sun au milieu des années 2000 avec l’intégrité des données de bout en bout et le stockage en pool comme priorités, pas comme rustines.
- Les quotas sont arrivés tôt parce que ZFS anticipait la consolidation : plusieurs consommateurs partageant un pool, chacun ayant besoin de limites prévisibles.
- Les instantanés sont bon marché à créer car ils sont uniquement métadonnées à leur création ; le coût apparaît plus tard via les blocs référencés.
- « Referenced » vs « used » dans les rapports ZFS existe précisément parce que les instantanés compliquent la question « combien d’espace m’appartient ? »
- Les reservations ont été conçues pour la justice et la disponibilité : elles gardent des datasets critiques en vie même quand le pool est sous pression.
- Les zvols et les filesystems sont gouvernés différemment : les quotas sur les filesystems ne correspondent pas directement aux consommateurs de zvol ; les stratégies de provisioning comptent.
- Historiquement, ZFS souhaitait un espace libre de sécurité (souvent 10–20 %) pour garder l’allocation efficace et éviter la fragmentation pathologique et les pics de latence.
- OpenZFS a fait évoluer les outils (comme des rapports de quota étendus) à mesure que les opérateurs le déployaient dans des environnements multi-tenant plus grands et plus bruyants.
Primitives de quota : quota, refquota, reservations, et pourquoi les noms comptent
Les frontières de dataset sont la frontière de politique
ZFS ne fait pas de « quotas sur un arbre de répertoires » de la même manière native que les systèmes de fichiers traditionnels. Il applique des propriétés sur des datasets.
C’est une fonctionnalité. Elle vous force à définir de vrais locataires. Un locataire est un dataset. Tout le reste est détail d’implémentation.
quota : limite le dataset et ses descendants
quota plafonne l’espace total qu’un dataset peut consommer, y compris l’espace utilisé par les descendants (datasets enfants).
C’est l’outil adapté quand le locataire possède un sous-arbre de datasets.
Mais c’est aussi l’outil qui surprend les gens car il interagit avec les instantanés. Si le dataset du locataire a des instantanés,
les blocs détenus par les instantanés comptent dans l’utilisation d’une manière qui peut être contre-intuitive. Si vous voulez « les données
actives du locataire » plafonnées, vous voulez probablement refquota.
refquota : limite l’espace référencé (données actives), pas les instantanés
refquota plafonne l’espace référencé du dataset : les blocs actuellement atteignables depuis la tête du dataset.
Les instantanés ne font pas partie du « referenced », donc les locataires ne peuvent pas se retrouver coincés parce que la rétention retient de l’espace en otage.
Cela ressemble à de la magie. Ce n’en est pas. Le pool peut quand même se remplir parce que les instantanés consomment toujours l’espace du pool.
Vous avez simplement déplacé le rayon d’impact : vous empêchez le locataire d’obtenir aléatoirement EDQUOT à cause de la rétention,
mais vous n’empêchez pas l’ENOSPC au niveau du pool.
reservation et refreservation : espace garanti, mais pas gratuit
Les reservations réservent de l’espace qui ne peut pas être utilisé par les autres. C’est votre levier « garder ce service en vie ».
reservation inclut les descendants. refreservation s’applique à l’espace référencé.
Les reservations peuvent vous sauver lors d’un événement de pression sur le pool. Elles peuvent aussi transformer « nous sommes bas » en « nous sommes morts »
si elles sont surutilisées, car elles font paraître l’espace libre disponible au pool mais indisponible pour la plupart des datasets.
Pourquoi « un utilisateur qui tue le pool » arrive encore malgré les quotas
Les quotas empêchent un locataire d’écrire au-delà d’une limite. Ils n’imposent pas automatiquement un plancher de sécurité au niveau du pool.
Si vous définissez des quotas qui totalisent 200 % du pool, vous avez créé une sursouscription. Cela peut être acceptable pour certains workloads.
C’est peut-être aussi ainsi que vous apprendrez rapidement ce que « comptabilité d’espace avec instantanés » signifie.
Idée paraphrasée, attribuée : Quand vous construisez des systèmes, vous échangez des problèmes faciles contre des problèmes difficiles ; le travail de fiabilité est de choisir les problèmes difficiles que vous pouvez surveiller.
— Charity Majors (idée paraphrasée)
Aussi : les quotas ne réduisent pas l’amplification d’écriture. Un locataire peut rester sous quota et quand même détruire la latence en provoquant de la fragmentation,
des workloads sync-heavy, ou un churn en petits blocs. Les quotas gouvernent la capacité, pas la performance. Vous avez besoin des deux.
Blague #1 : Un quota, c’est comme un régime — efficace jusqu’à ce que vous découvriez que les instantanés sont les snacks de minuit que vous n’avez pas notés.
Modèles de layout de datasets qui ne vous haïront pas
Modèle A : un dataset par locataire (le gagnant par défaut)
Créez pool/tenants/$tenant comme dataset filesystem. Mettez tout pour ce locataire là-dedans.
Appliquez quotas, compression, choix de recordsize, politiques d’instantanés et points de montage par locataire.
Avantages : gouvernance claire, reporting facile, faible charge cognitive. Inconvénients : plus de datasets (ce qui va tant que vous ne devenez pas ridicule),
et vous aurez besoin d’automatisation.
Modèle B : dataset parent avec enfants par service
Exemple : pool/tenants/acme/home, pool/tenants/acme/db, pool/tenants/acme/cache.
Mettez un quota sur le parent pour limiter l’empreinte totale du locataire, et des refquota sur des enfants spécifiques
pour garder les données actives raisonnables.
Ce modèle vous permet d’ajuster les propriétés par workload (recordsize pour base de données, logbias, compression) tout en appliquant un plafond par locataire.
C’est un design mature quand vous opérez des services plateforme.
Modèle C : répertoire par locataire dans un dataset unique (à éviter)
Les admins UNIX traditionnels adorent cela car c’est simple : /srv/tenants/acme, /srv/tenants/zenith.
Sur ZFS, c’est la mauvaise abstraction. Vous perdez la gouvernance native et vous vous retrouvez à greffer des quotas utilisateur/groupe, project quotas,
ou des outils externes.
Il y a des raisons valides — comme des millions de locataires où le nombre de datasets devient un problème de gestion — mais prenez cette décision les yeux ouverts.
Pour la plupart des systèmes multi-tenant d’entreprise (des dizaines à milliers), un dataset par locataire est plus sûr et plus simple.
Modèle D : zvol par locataire (seulement si nécessaire)
Si les locataires ont besoin de périphériques blocs (disques VM, LUN iSCSI), vous utiliserez des zvols. Les quotas sur zvols sont volsize.
Le thin provisioning peut sursouscrire un pool sévèrement si vous n’y prenez pas garde. Pour le multi-tenant, associez cela à une surveillance stricte
et à un plancher de sécurité du pool.
Instantanés : le contournement silencieux des quotas
Les deux surprises de quota les plus courantes sont :
- Le locataire atteint son quota même après avoir supprimé beaucoup de fichiers.
- Le locataire reste sous quota mais le pool se remplit et tout le monde en pâtit.
Comment les instantanés compliquent la suppression
Si un instantané référence des blocs qu’un fichier utilisait, la suppression du fichier dans le dataset actif ne libère pas ces blocs.
L’instantané en est toujours propriétaire. C’est pourquoi les opérateurs disent « l’espace est coincé dans les instantanés ».
Ce n’est pas coincé ; c’est correctement imputé à l’historique.
Si vous avez utilisé quota (pas refquota), les blocs détenus par les instantanés contribuent au « used » et peuvent maintenir un locataire plafondé.
Le locataire jurera qu’il a supprimé des choses. Il l’a fait. Votre politique de rétention n’est pas d’accord.
Pourquoi refquota aide les utilisateurs mais peut nuire aux pools
refquota améliore l’expérience utilisateur : il aligne l’application du quota sur la tête du dataset.
Mais il déplace le risque : les instantanés peuvent croître jusqu’à mettre le pool sous pression. Si vous choisissez refquota,
vous devez aussi choisir : limites d’instantanés, discipline de rétention et alerting au niveau du pool.
La rétention d’instantanés est une politique, pas une stratégie de sauvegarde
Les instantanés sont excellents pour le rollback court terme, les flux de réplication et la récupération forensic. Ils ne sont pas une licence
pour tout conserver indéfiniment sur votre pool le plus chaud. Traitez la rétention comme un budget : définissez-la, appliquez-la,
et révisez-la quand le comportement des locataires change.
Blague #2 : Les instantanés sont comme les tiroirs à bazar du bureau — personne n’en veut, mais tout le monde panique quand vous essayez de les vider.
Tâches pratiques (commandes, sorties, décisions)
La manière la plus rapide de bien configurer les quotas est d’exécuter le même petit ensemble de commandes à chaque fois et d’interpréter leurs résultats de façon cohérente.
Ci‑dessous se trouvent des tâches réelles à exécuter sur un hôte ZFS. Chaque tâche inclut : la commande, ce que signifie la sortie, et la décision à prendre.
Task 1: Confirm pool health and whether you’re already in trouble
cr0x@server:~$ zpool status -x
all pools are healthy
Signification : pas d’erreurs connues du pool. Cela ne signifie pas que vous avez de l’espace libre, ni que la performance est bonne.
Décision : si ceci n’est pas « healthy », corrigez d’abord les erreurs matérielles / du pool. Les quotas ne sauveront pas un pool dégradé d’une mauvaise latence.
Task 2: Check pool capacity, fragmentation, and headroom
cr0x@server:~$ zpool list -o name,size,alloc,free,cap,frag,health
NAME SIZE ALLOC FREE CAP FRAG HEALTH
tank 21.8T 18.9T 2.9T 86% 42% ONLINE
Signification : 86 % utilisé, fragmentation en hausse. Beaucoup de pools ZFS deviennent désagréables au-delà de ~85–90 %, selon le workload.
Décision : si cap > 85 %, considérez les quotas comme secondaires ; vous avez besoin d’un plan de capacité (supprimer des instantanés, ajouter des vdevs, migrer des locataires).
Task 3: Identify the biggest datasets first (the usual suspects)
cr0x@server:~$ zfs list -o name,used,refer,avail,mountpoint -S used | head -n 10
NAME USED REFER AVAIL MOUNTPOINT
tank/tenants/zenith 6.21T 1.02T 1.48T /srv/tenants/zenith
tank/tenants/acme 3.88T 3.62T 2.11T /srv/tenants/acme
tank/tenants/blue 2.45T 2.40T 1.90T /srv/tenants/blue
tank/backups 1.91T 1.88T 4.05T /tank/backups
tank/tenants 512K 192K 2.90T /srv/tenants
Signification : remarquez USED vs REFER. zenith a un grand USED mais un petit REFER : des instantanés ou des descendants détiennent la différence.
Décision : si USED ≫ REFER, investiguez les instantanés/enfants avant d’engueuler le locataire.
Task 4: See quotas and reservations applied across tenants
cr0x@server:~$ zfs get -r -o name,property,value,source quota,refquota,reservation,refreservation tank/tenants | head -n 25
NAME PROPERTY VALUE SOURCE
tank/tenants quota none default
tank/tenants refquota none default
tank/tenants reservation none default
tank/tenants refreservation none default
tank/tenants/acme quota 5T local
tank/tenants/acme refquota none default
tank/tenants/acme reservation none default
tank/tenants/acme refreservation none default
tank/tenants/blue quota 3T local
tank/tenants/blue refquota 2500G local
tank/tenants/blue reservation none default
tank/tenants/blue refreservation none default
tank/tenants/zenith quota 7T local
tank/tenants/zenith refquota 1500G local
tank/tenants/zenith reservation 500G local
tank/tenants/zenith refreservation none default
Signification : vous pouvez auditer rapidement la gouvernance. Une stratégie mixte est acceptable, mais elle doit être intentionnelle.
Décision : si les locataires comptent sur « les suppressions libèrent de l’espace », favorisez refquota plus contrôles d’instantanés.
Si vous voulez « tout compris », utilisez quota.
Task 5: Set a tenant quota (hard cap) and immediately verify
cr0x@server:~$ sudo zfs set quota=2T tank/tenants/acme
cr0x@server:~$ zfs get -o name,property,value tank/tenants/acme quota
NAME PROPERTY VALUE
tank/tenants/acme quota 2T
Signification : les écritures qui dépasseraient 2T pour cet arbre de dataset échoueront avec des erreurs de quota.
Décision : si acme a des datasets enfants, souvenez-vous que quota les inclut. Si vous voulez plafonner uniquement le dataset principal, utilisez refquota.
Task 6: Set refquota for “live data” and confirm refer behavior
cr0x@server:~$ sudo zfs set refquota=1500G tank/tenants/acme
cr0x@server:~$ zfs get -o name,property,value tank/tenants/acme refquota
NAME PROPERTY VALUE
tank/tenants/acme refquota 1500G
Signification : la tête du dataset ne peut pas dépasser 1.5T référencé. Les instantanés peuvent toujours croître.
Décision : associez cela à une rétention/limitation des instantanés sinon vous ne faites que repousser le débat jusqu’au remplissage du pool.
Task 7: Guarantee headroom for a critical service using reservation
cr0x@server:~$ sudo zfs set reservation=200G tank/tenants/platform
cr0x@server:~$ zfs get -o name,property,value tank/tenants/platform reservation
NAME PROPERTY VALUE
tank/tenants/platform reservation 200G
Signification : 200G est réservé pour cet arbre de dataset. Les autres locataires ne peuvent pas le consommer.
Décision : utilisez les reservations parcimonieusement. Elles servent aux datasets « must keep running », pas au confort politique.
Task 8: Spot snapshot-driven usage growth on a dataset
cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation -S used tank/tenants/zenith | head -n 8
NAME USED REFER CREATION
tank/tenants/zenith@daily-2025-12-25 210G 1.02T Thu Dec 25 01:00 2025
tank/tenants/zenith@daily-2025-12-24 198G 1.01T Wed Dec 24 01:00 2025
tank/tenants/zenith@daily-2025-12-23 176G 1.00T Tue Dec 23 01:00 2025
tank/tenants/zenith@daily-2025-12-22 165G 1008G Mon Dec 22 01:00 2025
tank/tenants/zenith@daily-2025-12-21 152G 1004G Sun Dec 21 01:00 2025
tank/tenants/zenith@daily-2025-12-20 141G 1001G Sat Dec 20 01:00 2025
tank/tenants/zenith@daily-2025-12-19 135G 999G Fri Dec 19 01:00 2025
Signification : le USED de chaque instantané est les blocs uniques détenus par cet instantané. La croissance ici signifie souvent du churn (réécritures)
dans le dataset actif.
Décision : si le USED des instantanés gonfle, raccourcissez la rétention, déplacez les workloads qui churnent, ou ajustez le workload (par ex. arrêter de réécrire de gros fichiers).
Task 9: Confirm what space is actually available to a tenant under quota
cr0x@server:~$ zfs get -o name,avail,used,quota,refquota tank/tenants/acme
NAME AVAIL USED QUOTA REFQUOTA
tank/tenants/acme 320G 1.68T 2T 1500G
Signification : AVAIL reflète la contrainte la plus stricte entre l’espace libre du pool et l’application de quota/refquota. Ici refquota est probablement le limiteur.
Décision : si AVAIL est étonnamment petit, vérifiez si refquota est plus bas que prévu, ou si des instantanés/descendants sont comptés via quota.
Task 10: Find which children are consuming a parent tenant quota
cr0x@server:~$ zfs list -r -o name,used,refer,quota,refquota -S used tank/tenants/acme
NAME USED REFER QUOTA REFQUOTA
tank/tenants/acme 1.68T 1.45T 2T 1500G
tank/tenants/acme/cache 220G 210G none 250G
tank/tenants/acme/db 110G 108G none none
tank/tenants/acme/home 35G 34G none none
Signification : le cache est volumineux et proche de son refquota. Souvent c’est voulu : les caches doivent être bornés.
Décision : si le cache est non borné, définissez un refquota. Si la db est spiky, considérez des quotas séparés et une reservation pour la maintenir en service.
Task 11: Identify whether “space not freeing” is snapshots vs open files
cr0x@server:~$ sudo zfs destroy -n tank/tenants/zenith@daily-2025-12-19
would destroy tank/tenants/zenith@daily-2025-12-19
would reclaim 135G
Signification : un destroy en mode dry-run vous indique l’espace récupérable si vous supprimez un instantané. C’est de l’or pour la prise de décision.
Décision : si l’espace récupérable est important et que vous êtes en difficulté, supprimez des instantanés (en commençant par les plus anciens) selon la politique.
Si le reclaim est petit, vous ne poursuivez pas les instantanés — cherchez des fichiers ouverts-supprimés ou d’autres datasets.
Task 12: Check for open-but-deleted files pinning space (classic ENOSPC bait)
cr0x@server:~$ sudo lsof +L1 /srv/tenants/acme | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 2714 acme 12w REG 0,118 2147483648 0 553211 /srv/tenants/acme/logs/app.log (deleted)
Signification : le processus détient toujours un descripteur de fichier vers un fichier supprimé. L’espace ne sera pas libéré tant que le processus ne le ferme pas.
Décision : redémarrez ou signalez le processus pour qu’il réouvre les logs. Ne supprimez pas plus de fichiers ; vous créerez d’autres « supprimés mais ouverts ».
Task 13: Confirm mountpoints and avoid “writing outside the dataset you think”
cr0x@server:~$ zfs get -o name,mountpoint,canmount tank/tenants/acme
NAME MOUNTPOINT CANMOUNT
tank/tenants/acme /srv/tenants/acme on
Signification : si les mountpoints sont erronés, les locataires peuvent écrire dans le dataset parent (non limité) et contourner les quotas.
Décision : vérifiez le point de montage de chaque locataire et assurez-vous que les datasets parents ne sont pas inscriptibles par les locataires.
Task 14: Apply “boring guardrails” on the parent dataset
cr0x@server:~$ sudo zfs set readonly=on tank/tenants
cr0x@server:~$ zfs get -o name,readonly tank/tenants
NAME PROPERTY VALUE
tank/tenants readonly on
Signification : les locataires ne peuvent pas écrire accidentellement dans le parent (s’il est monté), ce qui force les écritures dans les datasets enfants.
Décision : pour le multi-tenant, rendez les parents non inscriptibles et utilisez des points de montage enfants explicites. Cela empêche les contournements accidentels.
Task 15: Monitor per-dataset logical space pressure (quota nearing)
cr0x@server:~$ zfs list -o name,used,quota,refquota,available -r tank/tenants | awk 'NR==1 || $3!="none" || $4!="none"{print}'
NAME USED QUOTA REFQUOTA AVAIL
tank/tenants/acme 1.68T 2T 1500G 320G
tank/tenants/blue 2.45T 3T 2500G 550G
tank/tenants/zenith 6.21T 7T 1500G 790G
Signification : vue rapide des datasets gouvernés. AVAIL vous donne un indicateur proche-terme « les écritures vont-elles bientôt échouer ? ».
Décision : alertez sur le % utilisé du quota et aussi sur le cap du pool. Un locataire peut être correct pendant que le pool ne l’est pas.
Task 16: For zvol tenants, verify thin provisioning risk
cr0x@server:~$ zfs list -t volume -o name,volsize,used,refer,logicalused,logicalrefer -S logicalused
NAME VOLSIZE USED REFER LOGICALUSED LOGICALREFER
tank/vm/tenant01 800G 120G 120G 640G 640G
tank/vm/tenant02 800G 160G 160G 790G 790G
Signification : logicalused montre ce que l’invité pense avoir utilisé ; USED est ce que le pool a réellement alloué.
Le thin provisioning cache le risque jusqu’à ce qu’il ne le fasse plus.
Décision : si logicalused approche volsize sur de nombreux locataires, considérez cela comme une pression de capacité réelle et budgétez l’espace en conséquence.
Playbook de diagnostic rapide
Quand un pool multi-tenant est en détresse, vous n’avez pas le temps pour la pureté philosophique. Vous avez besoin d’un chemin rapide vers : « qu’est-ce qui remplit quoi ? »
et « est-ce de la capacité ou de la performance ? »
First: confirm whether you have a pool-wide emergency
- Pool capacity :
zpool list -o name,alloc,free,cap,frag. Si cap > 90 %, supposez que tout va devenir étrange. - Pool health :
zpool status. Si dégradé, attendez-vous à une latence pire et à des suppressions plus lentes. - Immediate reclaim candidates :
zfs list -t snapshot -o name,used -S used.
Second: identify whether the pain is “quota hit” or “pool full”
- Si les locataires voient des erreurs comme « Disk quota exceeded », vous avez affaire à une gouvernance au niveau dataset.
- Si tout le monde voit « No space left on device », vous êtes face à une exhaustion au niveau pool ou à une famine liée aux reservations.
- Vérifiez
zfs get avail,quota,refquotasur le dataset impacté et comparez au libre du pool.
Third: decide snapshots vs open files vs a different dataset
- Instantanés : si USED ≫ REFER sur le dataset, listez les instantanés et faites un destroy dry-run pour estimer le reclaim.
- Fichiers ouverts-supprimés : lancez
lsof +L1sur le mount. Si présents, redémarrez l’élément fautif. - Mauvais point de montage / contournement : vérifiez les mountpoints et si les écritures ont atterri dans un dataset parent sans quota.
Fourth: if performance is the symptom, don’t confuse it with capacity
- La haute fragmentation + haut cap peut ressembler à des « problèmes de quota » car les écritures timeout ou se bloquent.
- Mesurez la pression IO avec
zpool iostat -v 1et cherchez des vdevs saturés. - Si vous êtes proche du plein, votre meilleur « tuning performance » est de libérer de l’espace.
Trois mini-histoires d’entreprise issues des tranchées des quotas
Mini-histoire 1 : la panne causée par une mauvaise hypothèse
Une entreprise de taille moyenne gérait un pool ZFS partagé pour des équipes internes : analytics, systèmes de build, quelques sites web. Ils firent la chose sensée :
un dataset par équipe, des quotas sur chaque dataset. Ils en étaient fiers. Le pool était stable. Puis un lundi, la moitié des jobs CI échoua avec ENOSPC.
L’astreinte supposa qu’une équipe avait dépassé son quota. Mais les quotas étaient corrects. Chaque dataset d’équipe avait encore de la marge.
Le pool, cependant, était à 98 %, et ZFS se comportait comme un système de stockage à 98 % : l’allocation devint coûteuse et les mises à jour de métadonnées ralentirent.
La mauvaise hypothèse était subtile : « Si chaque équipe a un quota, le pool ne peut pas se remplir. » Les quotas ne s’agrègent pas automatiquement en sécurité.
Ils avaient sursouscrit — silencieusement — parce que les quotas avaient été définis selon les attentes business, pas selon la capacité réelle du pool, et la rétention n’était pas bornée.
Le vrai coupable : des instantanés automatiques conservés « pendant un certain temps », devenus « pour toujours » parce que personne ne voulait supprimer l’historique.
Une seule équipe avec un workload à fort churn (gros artefacts réécrits quotidiennement) causa la croissance des instantanés. Leurs données actives restaient sous refquota,
mais les instantanés dévoraient progressivement le pool.
La correction ne fut pas héroïque. Ils définissent la rétention d’instantanés par classe de locataire, ajoutèrent des limites de nombre d’instantanés,
et configurèrent une alerte de sécurité du pool à 80/85/90 %. Ils commencèrent aussi une revue mensuelle des datasets où USED-REFER dépassait un seuil.
Banal, cohérent, efficace.
Mini-histoire 2 : l’optimisation qui a mal tourné
Une autre entreprise proposait des « sandboxes développeurs » sur ZFS. Pour une meilleure expérience, ils passèrent beaucoup de datasets locataires
de quota à refquota. L’objectif : arrêter les plaintes des devs selon lesquelles la suppression de fichiers ne leur rendait pas la capacité
car des instantanés retenaient l’espace.
Ça marcha. Les tickets chutèrent. L’équipe plateforme se félicita. Et puis le pool se mit à se remplir plus vite que prévu, mais personne ne le remarqua tout de suite
parce que les tableaux de bord par locataire restaient verts.
Le retour de bâton vint de la visibilité. Avec refquota, les locataires ne frappaient jamais leur « limite » parce que leurs données actives restaient bornées,
tandis que les instantanés pouvaient croître sous le radar. Le système avait déplacé l’échec de « le locataire ne peut pas écrire » à « le pool est plein »,
ce qui est beaucoup pire en multi-tenant.
L’incident se termina comme d’habitude : suppression d’instantanés sous pression, lag de réplication qui grimpe, et quelques restores impossibles.
Pas catastrophique, mais douloureux et évitable.
La correction fut de traiter la rétention d’instantanés comme partie intégrante de la gouvernance des quotas. Ils mirent en place :
plafonds d’instantanés par dataset, calendriers d’instantanés par locataire, et un rapport classant les locataires par « espace snapshot-only ».
Refquota resta — mais seulement avec des garde-fous et un plancher d’espace libre au niveau du pool.
Mini-histoire 3 : la pratique ennuyeuse mais correcte qui a sauvé la situation
Une organisation régulée gérait un cluster ZFS multi-tenant pour des équipes applicatives. Les ingénieurs stockage étaient allergiques aux surprises,
alors ils firent deux choses peu sexy : ils conservèrent 20 % d’espace libre par politique, et ils réservèrent une petite tranche pour des datasets plateforme
(logging, auth, spools de monitoring).
À la fin d’un trimestre, le job batch d’une application produisit bien plus de sortie que d’habitude. Le dataset du locataire atteignit son quota.
L’application échoua bruyamment — exactement ce qu’on souhaite. Le pool resta sain, le monitoring resta en ligne, et les autres équipes ne remarquèrent rien.
L’astreinte reçut une alerte claire : « tenant quota exceeded ». Pas « pool full ». Pas « latence IO x10 ». Pas « tout est en feu ».
Ils augmentèrent temporairement le quota du locataire, mais seulement après avoir déplacé d’anciens instantanés vers un pool froid et réduit la rétention.
La clé n’était pas le quota seul. C’était la combinaison : un plancher de sécurité du pool, des reservations pour services essentiels, et du reporting cohérent.
L’incident resta cantonné au locataire. C’est tout l’intérêt du multi-tenant engineering.
Erreurs courantes : symptômes → cause racine → correction
1) Symptom: “I deleted 500GB but I’m still at quota”
Cause racine : des instantanés référencent encore les blocs supprimés ; quota les compte.
Correction : soit supprimez/expirez des instantanés, soit passez à refquota pour ce dataset et gérez les instantanés séparément.
2) Symptom: tenant is under quota, but pool hits 100% anyway
Cause racine : refquota limite seulement les données actives ; les instantanés, d’autres datasets et le thin provisioning zvol consomment toujours l’espace du pool.
Correction : appliquez la rétention/limitation des instantanés, surveillez la croissance « snapshot-only » (USED-REFER), et gardez un plancher de sécurité pour le pool.
3) Symptom: random ENOSPC even though zpool list shows free space
Cause racine : des reservations ou contraintes d’allocation spéciales signifient que l’espace libre n’est pas utilisable pour ce dataset.
Correction : auditez reservation/refreservation ; réduisez ou supprimez les reservations non critiques ; assurez-vous que les datasets critiques ont les reservations, pas tout le monde.
4) Symptom: tenant can write outside quota somehow
Cause racine : des écritures arrivent dans un dataset parent (mauvais mountpoint, confusion de bind-mount, ou permissions sur le mount parent).
Correction : verrouillez les datasets parents (readonly=on, canmount=off si approprié), vérifiez les mountpoints, et restreignez les permissions.
5) Symptom: pool is not full, but latency is awful and writes crawl
Cause racine : forte fragmentation, churn en petits blocs, workload sync-heavy, ou un vdev dégradé ; la gouvernance de capacité ne résout pas la saturation IO.
Correction : gardez de la marge, isolez les workloads churny dans leurs propres vdevs/pools, et mesurez avec zpool iostat. Considérez SLOG/special vdevs si pertinent.
6) Symptom: “space not freeing” after deleting big files, no snapshots found
Cause racine : fichiers ouverts-supprimés détenus par des processus.
Correction : lsof +L1 pour trouver les coupables ; redémarrez ou signalez la rotation des logs correctement.
7) Symptom: tenant replication grows without obvious live growth
Cause racine : réécritures fréquentes créent beaucoup de deltas d’instantanés ; les streams send grossissent même si les données actives restent stables.
Correction : réduisez le churn (changement applicatif), ajustez la fréquence d’instantanés, ou migrez ce locataire vers un pool conçu pour le churn.
Checklists / plan pas-à-pas
Step-by-step: set up a new tenant safely
- Créez un dataset par locataire (ou par locataire/service si vous avez besoin de propriétés différentes).
- Définissez explicitement le mountpoint et assurez-vous que les datasets parents ne sont pas inscriptibles par les locataires.
- Choisissez le modèle de quota :
- Utilisez
quotasi les instantanés comptent comme « leur problème » et que vous voulez un plafond total strict. - Utilisez
refquotasi vous voulez que l’UX reflète les données actives et que vous gérez les instantanés centralement.
- Utilisez
- Décidez de la politique d’instantanés : fréquence et rétention. Mettez-la en code, pas en mémoire tribale.
- Ajoutez de l’alerting : %used de quota, seuils de cap du pool, et croissance snapshot-only.
- Documentez le mode de panne que verra le locataire : EDQUOT vs ENOSPC et ce qu’il doit faire.
Step-by-step: enforce pool safety floor (the “don’t page me” plan)
- Choisissez un plancher d’espace libre cible (commument 10–20 % selon workload et layout des vdevs).
- Alertez tôt à plusieurs seuils (ex. 80/85/90 %), pas seulement à 95 % quand c’est déjà misérable.
- Auditez la sursouscription : somme des quotas vs taille du pool ; acceptez la sursouscription seulement si vous pouvez expliquer pourquoi c’est sûr.
- Limitez la croissance des instantanés : limites de rétention et, quand vos outils le permettent, plafonds de nombre/espace par locataire.
- Réservez l’espace pour les datasets plateforme : monitoring, logs, et metadata d’auth ne doivent pas rivaliser avec les locataires pendant un incident.
Step-by-step: respond when the pool is near full
- Arrêtez l’hémorragie : identifiez le reclaim le plus rapide (généralement des instantanés) et confirmez le reclaim avec
zfs destroy -n. - Si des locataires écrivent en dehors des quotas, corrigez immédiatement les mountpoints et permissions.
- Vérifiez les fichiers ouverts-supprimés et redémarrez les processus fautifs.
- Réduisez temporairement la rétention des instantanés, puis restaurez une politique saine avec approbations.
- Planifiez une expansion de capacité ou un déplacement de données ; « nous ferons attention » n’est pas un plan de capacité.
FAQ
1) Should I use quota or refquota for tenants?
Si les locataires gèrent leurs propres instantanés ou si vous voulez plafonner « l’empreinte totale incluant l’historique », utilisez quota.
Si vous gérez centralement les instantanés et voulez que l’expérience utilisateur reflète les données actives, utilisez refquota, mais vous devrez gouverner
la croissance des instantanés séparément.
2) Can quotas prevent a pool from hitting 100%?
Pas à eux seuls. Les quotas limitent les datasets. L’exhaustion du pool survient toujours via les instantanés, d’autres datasets, le thin provisioning des zvols,
les reservations et la sursouscription. Vous avez toujours besoin d’une politique de marge et d’un alerting du pool.
3) Why does USED differ so much from REFER?
REFER est l’espace référencé par la tête du dataset (vue active). USED inclut les blocs détenus par les instantanés et les descendants.
Un grand écart signifie généralement des instantanés ou des datasets enfants.
4) What error will applications see when a quota is hit?
Typiquement « Disk quota exceeded » (EDQUOT). Si le pool lui‑même est à court d’espace, elles verront « No space left on device » (ENOSPC),
ce qui affecte tout le monde et est bien pire opérationnellement.
5) If I delete snapshots, will I always get space back immediately?
Généralement oui, mais la quantité récupérée dépend du partage des blocs. Utilisez zfs destroy -n snapshot pour estimer le reclaim.
Si le reclaim est faible, l’instantané n’est pas votre problème principal.
6) Are reservations a good way to “protect” each tenant?
Non. Les reservations servent à protéger des services critiques, pas à sécuriser tout le monde. L’abus des reservations peut priver le pool
et provoquer un comportement ENOSPC déroutant même quand le pool affiche de l’espace libre.
7) How do I stop tenants from bypassing quotas by writing elsewhere?
Utilisez un dataset par locataire avec des mountpoints dédiés, rendez les datasets parents non inscriptibles, vérifiez mountpoint et canmount,
et assurez-vous que les permissions n’autorisent pas les écritures vers des parents partagés.
8) Do snapshots count against refquota?
Non, c’est le but. Les instantanés comptent toujours contre le pool cependant. Refquota est un plafond par dataset sur les données actives, pas un mécanisme de sécurité du pool.
9) What’s the simplest multi-tenant pattern that works in production?
Un dataset par locataire, un modèle de quota clair (quota ou refquota), des instantanés automatisés avec rétention stricte,
et des alertes sur les limites locataires et la marge du pool. Restez ennuyeux.
Conclusion : prochaines étapes pour éviter la page à 2h du matin
Les quotas ZFS ne sont pas un simple gadget ; ils sont la façon d’empêcher qu’un locataire transforme un stockage partagé en incident partagé.
Mais les quotas ne fonctionnent que lorsque votre layout de datasets correspond à votre modèle de tenancy, et quand les instantanés et les reservations
sont traités comme des politiques de première classe.
Prochaines étapes pratiques :
- Auditez vos frontières de locataire : si les locataires sont des répertoires, planifiez une migration vers un dataset par locataire.
- Choisissez sémantiquement vos quotas :
quotapour l’empreinte totale,refquotapour l’UX des données actives — puis implémentez les garde-fous manquants. - Implémentez des limites de rétention d’instantanés et un rapport pour la croissance « snapshot-only ».
- Définissez un plancher d’espace libre du pool et alertez avant 85 % d’utilisation ; n’attendez pas 95 % pour découvrir la physique.
- Réservez de l’espace uniquement pour les datasets critiques plateforme afin de pouvoir encore opérer quand les locataires se comportent mal.
Faites cela correctement et « un utilisateur a tué le pool » deviendra une histoire que vous racontez aux nouvelles recrues comme avertissement, pas comme une tradition trimestrielle.