Vous répliquez ZFS comme un adulte responsable. Snapshots, incrémentiels, peut‑être chiffrement, peut‑être un peu de compression.
Puis une équipe lance un « petit nettoyage » : renommage d’un dataset, déplacement d’un sous‑arbre ou réagencement des points de montage.
La réplication, qui fonctionnait depuis des mois, meurt alors avec un message cryptique « does not match incremental source » ou reçoit dans un endroit étrange.
La bonne nouvelle : ZFS peut gérer les renommages et déplacements sans drame. La mauvaise nouvelle : il ne le fera que si vous l’avez conçu ainsi,
et si vous cessez de considérer les noms de datasets comme des identités. Ils ne le sont pas. Ce sont des étiquettes.
Le modèle mental : ce qui casse et pourquoi
La réplication ZFS n’est pas « copier un dossier via SSH ». C’est rejouer des transactions de système de fichiers d’un graphe de stockage
vers un autre, en utilisant un flux créé par zfs send et appliqué par zfs receive.
Un snapshot est un point nommé dans le temps pour un dataset. Un flux incrémentiel est « changements depuis le snapshot A vers le snapshot B ».
Si le récepteur n’a pas le snapshot exact A sur le dataset exact que vous ciblez, il ne peut pas appliquer le diff.
Voici le piège : les gens traitent les noms de datasets comme des identifiants stables. Ils scriptent des pipelines de réplication comme :
« envoyer pool/app vers la sauvegarde comme backup/app. » Puis quelqu’un renomme pool/app en
pool/apps/app. Côté source, la lignée des snapshots existe toujours, mais elle vit maintenant sous un nouveau nom.
Côté cible, votre pipeline essaie toujours d’appliquer les incrémentiels sur l’ancien dataset de destination. Boom.
ZFS suit bien les datasets via des GUID internes, mais le flux send/receive dépend toujours de la correspondance des noms de snapshots et
du choix de la bonne cible de réception. C’est pourquoi « renommer ou déplacer » n’est pas intrinsèquement dangereux — ce qui est dangereux,
c’est concevoir la réplication autour des noms et des points de montage plutôt qu’autour de racines contrôlées et d’un mappage prévisible.
Blague n°1 : renommer un dataset, c’est comme changer son nom d’affichage Slack en s’attendant à ce que la paie suive. Ce n’est pas ce genre de système.
Ce que « renommage » et « déplacement » signifient en termes ZFS
- Renommage change le nom du dataset (par ex.
tank/app→tank/apps/app), en préservant snapshots et enfants. - Déplacement est généralement la même opération : vous renommez un sous‑arbre sous un nouveau parent.
- Changement de mountpoint modifie l’emplacement dans le système de fichiers de l’OS (
mountpoint), pas l’identité du dataset. - Promotion modifie l’ascendance d’un clone. Cela peut invalider les hypothèses sur quel snapshot est « la base » pour les incrémentiels.
Quand la réplication casse réellement
La réplication casse lorsque au moins une des situations suivantes se produit :
- Vous envoyez des incrémentiels depuis le snapshot X, mais le dataset récepteur n’a pas le snapshot X.
- Vous recevez dans un dataset différent de celui de la fois précédente (souvent dû à des scripts qui mappent mal après un renommage).
- Le récepteur a divergé par des écritures (quelqu’un l’a monté en lecture‑écriture et l’a modifié), donc un receive en fast‑forward est impossible sans rollback.
- Vous avez changé le mode de réplication (par ex. de « plain » à « raw encrypted ») en cours de route sans reseed.
- Vous avez utilisé
zfs recvsans-uet des propriétés de mountpoint ont causé des conflits, donc la réception échoue ou monte dans des chemins de production.
Thème : ZFS est cohérent. Les humains sont créatifs.
Faits et contexte historique pertinents en pratique
- Les snapshots ZFS sont peu coûteux car ce ne sont que des références métadonnées vers des blocs existants ; seuls les blocs modifiés consomment de l’espace nouveau.
- L’envoi incrémentiel dépend d’une ascendance commune ; ce n’est pas un « diff par nom », c’est un « diff par lignée de snapshots ». L’absence de snapshot de base est fatale.
- Le renommage de dataset préserve snapshots et enfants ; les objets snapshot ne disparaissent pas parce que l’étiquette du dataset a changé.
- Les bookmarks existent pour conserver une base incrémentielle sans garder un snapshot ; ce sont des pointeurs légers vers un groupe de transactions.
- Le receive ZFS est transactionnel ; un receive raté n’applique pas les données en partie dans le cas normal. C’est pourquoi c’est un fondement solide pour l’automatisation.
- Les propriétés peuvent être incluses ou exclues lors de la réplication (par ex. via
zfs send -p), ce qui affecte les mountpoints et peut vous surprendre. - Les envois chiffrés raw préservent le chiffrement et n’exposent pas de plaintext à l’userspace de l’hôte source, mais ils requièrent un support de fonctionnalités cohérent.
- La réplication un‑vers‑plusieurs scale mieux que le bricolage plusieurs‑vers‑un ; les flux ZFS sont déterministes, mais votre logique de mappage ad hoc ne l’est pas.
Une citation opérationnelle qui a bien vieilli : Espérer n’est pas une stratégie.
— Gene Kranz.
Règles de conception : réplication qui survit aux renommages
Règle 1 : Choisissez une racine de réplication et ne la réutilisez pas
Créez un dataset « racine de réplication » stable des deux côtés, et traitez‑le comme une frontière de namespace.
Exemple : tout ce que vous répliquez vit sous tank/replica-src côté source et bkp/replica côté cible.
Les datasets applicatifs peuvent bouger en production, mais la réplication cible toujours le chemin racine stable sur chaque système.
Concrètement, cela signifie que vous répliquez les datasets dans un sous‑arbre dédié et les exposez aux consommateurs via des clones, des overrides de mountpoint,
ou des datasets « publiés » séparés. Ne laissez pas le chemin cible de réplication être identique aux points de montage de production.
Sinon, vous êtes à une faute de frappe de monter un dataset de sauvegarde sur un répertoire vivant. Ce n’est pas une stratégie de sauvegarde ; c’est de l’art performance.
Règle 2 : Séparez « quoi répliquer » de « où ça vit aujourd’hui »
Le nom du dataset source peut changer. Ce qui ne devrait pas changer, c’est l’intention : « ceci est le dataset qui soutient le service X ».
Encodez l’intention avec une propriété ou un fichier de mappage, pas en parsant les noms de datasets.
Un bon modèle est le tagging :
org.example:replicate=true et org.example:replica-name=svc-x.
Votre automatisation découvre les datasets par propriété, puis les réplique vers un chemin de destination fixe comme
bkp/replica/svc-x. Renommez la source autant que vous voulez ; l’identité de réplication reste stable.
Règle 3 : Gardez un schéma de nommage de snapshots cohérent lors des déplacements
Les noms de snapshots font partie de votre contrat. Si vous répliquez des incrémentiels, les deux côtés ont besoin du même nom de snapshot
pour la base et la cible. Choisissez un schéma et ne faites pas de fantaisie :
replica-YYYYMMDD-HHMM ou replica-auto-###.
Si vous devez changer de schéma, reseedez avec un envoi complet une fois et redémarrez les incrémentiels. N’essayez pas de « faire le pont » avec de la magie.
Règle 4 : Utilisez holds et bookmarks intentionnellement
Si vous comptez sur un snapshot de base pour les incrémentiels, protégez‑le. Utilisez zfs hold pour empêcher le pruning de le supprimer
avant que le récepteur n’ait rattrapé son retard. Si vous voulez éviter de garder d’anciens snapshots indéfiniment, utilisez des bookmarks :
ils permettent de faire des incrémentiels depuis un « point mémorisé » sans conserver le snapshot complet.
Règle 5 : Ne laissez jamais la cible diverger (sauf si c’est volontaire)
Traitez votre cible de réplication comme lecture seule. Faites‑le respecter socialement (permissions), opérationnellement (ne pas monter automatiquement),
et techniquement (considérez readonly=on sur les datasets reçus). La divergence est la raison n°1 qui vous pousse à utiliser
zfs recv -F sous pression, ce qui est l’équivalent ZFS de « je vais juste redémarrer la prod, qu’est‑ce qui pourrait mal se passer ? »
Règle 6 : Décidez à l’avance de votre stratégie de mappage de réception
Vous avez trois schémas courants :
- Mirroir des noms :
tank/app→bkp/tank/app. Simple jusqu’au renommage. Ensuite il faut une logique de gestion des renommages. - Noms de destination stables : tagger les sources et mapper vers
bkp/replica/<service>. Idéal pour tolérer renommages/déplacements. - Recevoir sous un parent avec
zfs recv -d: préserver la structure du sous‑arbre sous une racine dédiée. Bien pour migration en masse, mais toujours lié aux noms.
Pour « sans drame », les noms de destination stables gagnent. Ils demandent un peu de configuration et vous évitent beaucoup de nuits blanches.
Carnet de tâches : commandes, sorties et décisions (12+)
Voici les tâches que j’exécute réellement quand la réplication devient étrange — plus ce que la sortie signifie et quelle décision en découle.
Les noms d’hôtes sont des exemples : src1 est la source, bkp1 est la sauvegarde.
Task 1: Identifier ce qui a changé (indices de renommage/déplacement)
cr0x@server:~$ zfs list -t filesystem -o name,creation -r tank | head
NAME CREATION
tank Mon Jan 8 09:12 2024
tank/apps Tue Apr 2 13:41 2024
tank/apps/app1 Tue Apr 2 13:44 2024
tank/oldapp Wed Mar 6 10:18 2024
Ce que cela signifie : Cela vous donne les noms actuels et une chronologie approximative.
Si un dataset a « bougé », vous verrez un nouveau chemin parent ; l’heure de création reste généralement la même pour le dataset lui‑même.
Décision : Si le nom du dataset a changé, arrêtez de compter sur un mappage basé sur le nom. Passez à un mappage par propriété ou renommez explicitement la cible.
Task 2: Confirmer la présence et le nommage des snapshots sur la source
cr0x@server:~$ zfs list -t snapshot -o name,creation -s creation -r tank/apps/app1 | tail -5
tank/apps/app1@replica-20240130-0200 Tue Jan 30 02:00 2024
tank/apps/app1@replica-20240131-0200 Wed Jan 31 02:00 2024
tank/apps/app1@replica-20240201-0200 Thu Feb 1 02:00 2024
tank/apps/app1@replica-20240202-0200 Fri Feb 2 02:00 2024
tank/apps/app1@replica-20240203-0200 Sat Feb 3 02:00 2024
Ce que cela signifie : Vous avez une série de snapshots cohérente.
Décision : Choisissez le dernier snapshot répliqué avec succès comme base incrémentielle. Si vous ne pouvez pas l’identifier, inspectez la cible.
Task 3: Confirmer la présence des snapshots sur le dataset cible que vous pensez correct
cr0x@server:~$ ssh bkp1 zfs list -t snapshot -o name -r bkp/replica/app1 | tail -5
bkp/replica/app1@replica-20240130-0200
bkp/replica/app1@replica-20240131-0200
bkp/replica/app1@replica-20240201-0200
bkp/replica/app1@replica-20240202-0200
bkp/replica/app1@replica-20240203-0200
Ce que cela signifie : La cible a les mêmes snapshots, donc les incrémentiels devraient fonctionner.
Décision : Si les snapshots ne correspondent pas, soit vous recevez dans le mauvais dataset, soit vous devez reseeder/envoi complet.
Task 4: Vérifier la divergence (écritures sur la cible)
cr0x@server:~$ ssh bkp1 zfs get -H -o property,value,source readonly bkp/replica/app1
readonly off local
Ce que cela signifie : La cible est modifiable. Ce n’est pas la preuve qu’elle a divergé, mais c’est une invitation.
Décision : Mettez readonly=on sur les cibles de réplication sauf si vous avez un workflow de basculement volontaire.
Task 5: Trouver rapidement le dernier snapshot commun
cr0x@server:~$ comm -12 \
<(zfs list -H -t snapshot -o name -r tank/apps/app1 | sed 's/^.*@/@/' | sort) \
<(ssh bkp1 zfs list -H -t snapshot -o name -r bkp/replica/app1 | sed 's/^.*@/@/' | sort) | tail -3
@replica-20240201-0200
@replica-20240202-0200
@replica-20240203-0200
Ce que cela signifie : Les noms de snapshots correspondent entre source et cible. La dernière ligne est votre candidat pour la base incrémentielle.
Décision : Utilisez cette base pour zfs send -i (ou -I si vous envoyez une plage incluant des snapshots intermédiaires).
Task 6: Estimation en simulation de la taille d’envoi (vérification de cohérence)
cr0x@server:~$ zfs send -nPv -i tank/apps/app1@replica-20240203-0200 tank/apps/app1@replica-20240204-0200
send from @replica-20240203-0200 to tank/apps/app1@replica-20240204-0200 estimate: 1.42G
total estimated size is 1.42G
Ce que cela signifie : Vous allez transférer ~1,4 GiB. Si vous attendiez 10 MiB, quelque chose cloche (ou votre application a été active).
Décision : Si l’estimation est soudainement énorme, vérifiez le churn (logs, tmp, bases de données) et envisagez d’exclure ou de repenser la disposition des datasets.
Task 7: Effectuer la réplication incrémentielle avec comportement de réception explicite
cr0x@server:~$ zfs send -w -i tank/apps/app1@replica-20240203-0200 tank/apps/app1@replica-20240204-0200 | \
ssh bkp1 zfs recv -u bkp/replica/app1
Ce que cela signifie : -w envoie raw (chiffré si le dataset est chiffré). -u empêche l’auto‑montage sur l’hôte de sauvegarde.
Si cela échoue, vous aurez une erreur spécifique ; ne la « corrigez » pas avec -F tant que vous n’avez pas compris le décalage.
Décision : Si le receive échoue à cause d’un snapshot de base manquant, arrêtez et réconciliez les datasets. S’il échoue à cause de conflits de mountpoint, gardez -u et corrigez les propriétés.
Task 8: Vérifier que la réception a bien atterri là où vous le pensez
cr0x@server:~$ ssh bkp1 zfs list -o name,used,refer,mountpoint bkp/replica/app1
NAME USED REFER MOUNTPOINT
bkp/replica/app1 8.21G 8.21G /bkp/replica/app1
Ce que cela signifie : Le dataset existe et le mountpoint est ce que la cible pense qu’il doit être.
Décision : Si le mountpoint pointe vers un chemin de production, arrêtez et définissez mountpoint=none ou recevez avec -u et ajustez avant de monter.
Task 9: Inspecter les erreurs côté réception et l’état partiel
cr0x@server:~$ ssh bkp1 zpool status -x
all pools are healthy
Ce que cela signifie : Le pool n’est pas dégradé. Si la réplication est lente ou échoue, le problème n’est probablement pas un vdev en train de mourir.
Décision : Si zpool status montre un resilver, des erreurs ou un vdev dégradé, réparez la santé du stockage avant d’accuser send/recv.
Task 10: Trouver les blocages cachés : holds qui empêchent le pruning
cr0x@server:~$ zfs holds tank/apps/app1@replica-20240203-0200
NAME TAG TIMESTAMP
tank/apps/app1@replica-20240203-0200 repl-base Sat Feb 3 02:05 2024
Ce que cela signifie : Un hold existe. Utile quand c’est intentionnel ; agaçant quand c’est oublié.
Décision : Gardez des holds sur la dernière base répliquée jusqu’à ce que la cible confirme la réception. Libérez les holds dans un hook post‑réplication.
Task 11: Utiliser un bookmark comme ancre incrémentielle (quand on veut purger des snapshots)
cr0x@server:~$ zfs bookmark tank/apps/app1@replica-20240203-0200 tank/apps/app1#repl-anchor
cr0x@server:~$ zfs list -t bookmark -o name -r tank/apps/app1
tank/apps/app1#repl-anchor
Ce que cela signifie : Le bookmark existe. Il peut servir de côté « from » des incrémentiels même si vous détruisez ensuite le snapshot.
Décision : Utilisez des bookmarks quand la rétention est agressive mais que la réplication a besoin d’une base stable.
Task 12: Vérifier les feature flags et la compatibilité du chiffrement (éviter les mauvaises surprises)
cr0x@server:~$ zpool get -H -o name,property,value feature@encryption tank
tank feature@encryption active
cr0x@server:~$ ssh bkp1 zpool get -H -o name,property,value feature@encryption bkp
bkp feature@encryption active
Ce que cela signifie : Les deux pools supportent les fonctionnalités de chiffrement. Les envois raw ne vont pas échouer à cause d’un manque de support.
Décision : Si la cible n’a pas les fonctionnalités nécessaires, soit mettez‑la à jour, soit évitez les workflows raw/chiffrés jusqu’à alignement.
Task 13: Diagnostiquer les limites de débit pendant la réplication
cr0x@server:~$ iostat -xz 1 3
Linux 6.6.0 (src1) 02/04/2026 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.10 0.00 3.20 18.40 0.00 66.30
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 82.0 120.0 98000.0 62000.0 8.20 0.45 91.0
Ce que cela signifie : Un %util élevé et un await élevé suggèrent que le stockage est le goulot (ou du moins fortement sollicité).
Décision : Si les disques sont saturés, limitez la réplication, planifiez‑la hors‑pointe, ou ajustez recordsize/compression pour la charge.
Task 14: Valider que vous ne changez pas accidentellement des propriétés via la réplication
cr0x@server:~$ zfs get -H -o property,value,source mountpoint,canmount tank/apps/app1
mountpoint /var/lib/app1 local
canmount on default
cr0x@server:~$ ssh bkp1 zfs get -H -o property,value,source mountpoint,canmount bkp/replica/app1
mountpoint /bkp/replica/app1 local
canmount noauto local
Ce que cela signifie : Le dataset de sauvegarde ne s’auto‑montera pas (noauto). C’est sain pour une réplique.
Décision : Forcez les cibles de sauvegarde à être inertes par défaut : canmount=noauto, montages contrôlés seulement lors des tests de restauration.
Gérer les renommages et déplacements de datasets (schémas sûrs)
Schéma A : Identité basée sur une propriété avec noms de destination stables (recommandé)
C’est l’approche « adulte dans la pièce ». Vous taggez le dataset avec une identité qui ne change pas lors d’un renommage,
puis vous répliquez vers un dataset de destination stable nommé d’après cette identité.
Comment cela fonctionne :
- Sur le dataset source (où qu’il se trouve), définissez une propriété comme
org.example:replica-name=app1. - Votre outil de réplication découvre les datasets par propriété, pas par chemin.
- Tous les flux atterrissent dans
bkp/replica/app1, que la source soittank/app1outank/apps/app1.
Avantage opérationnel : Les renommages n’importent pas. Les déplacements n’importent pas. Seule la lignée compte, et vous la gardez stable.
Ce qui peut encore casser : astuces de promotion/clone, changements de mode de chiffrement, ou laisser la cible diverger.
Schéma B : Si vous miroitez les noms, traitez le renommage comme un événement répliqué
Certains environnements veulent que le chemin de destination reflète le chemin source pour la navigation humaine.
Très bien. Mais alors vous devez répliquer les renommages comme changement à part entière.
ZFS n’expédie pas des « événements de renommage » séparés. Votre automatisation doit détecter les renommages (ou déplacements)
et appliquer des renommages correspondants sur la cible avant d’envoyer les incrémentiels.
Approche minimale :
- Maintenez un fichier de mappage GUID dataset source → chemin dataset destination.
- À chaque exécution, comparez le nom source actuel pour ce GUID. S’il a changé, renommez le dataset cible en conséquence.
- Puis exécutez les incrémentiels comme d’habitude.
Cela fonctionne parce que renommer un dataset ne change pas ses snapshots ni son GUID. Votre travail est simplement de maintenir le chemin du dataset récepteur aligné avec ce que votre automatisation attend.
Schéma C : Utiliser zfs recv -d sous une racine stable pour les déplacements de sous‑arbre
Si vous voulez préserver les noms relatifs mais pas les noms absolus, recevez sous une racine stable avec -d.
Exemple : la source envoie tank/apps/app1, la cible reçoit sous bkp/replica avec -d,
créant bkp/replica/tank/apps/app1 ou similaire selon le flux d’envoi et les options de réception.
Cela peut être utile pour une « journée de migration » ou une réplication en masse, mais c’est toujours couplé aux noms. Si le chemin source change,
vous créez un nouveau chemin sur la cible à moins de renommer l’ancien.
Que faire pendant une fenêtre de renommage
Si un renommage/déplacement est planifié, vous pouvez le rendre ennuyeux :
- Pausez brièvement la réplication (ou au moins arrêtez le pruning automatique).
- Prenez un snapshot juste avant le renommage :
@replica-pre-rename. - Renommez/déplacez le dataset.
- Confirmez que les snapshots existent toujours sous le nouveau nom.
- Mettez à jour le mappage/la propriété si nécessaire.
- Reprenez la réplication en utilisant le dernier snapshot commun.
L’idée n’est pas « éviter les renommages ». L’idée est « éviter les renommages + une automatisation qui suppose que les noms ne changent jamais ».
Blague n°2 : si votre script de réplication utilise cut -d/ -f2 pour identifier des datasets, ce n’est pas de l’automatisation — c’est un appel à l’aide.
Trois mini‑histoires en entreprise
Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne répliquait ZFS d’un fichier de production vers un fichier de sauvegarde toutes les 15 minutes. Le script de réplication était
simple : il listait les datasets sous tank/prod, puis reflétait le même chemin sous bkp/prod.
Tout le monde considérait le chemin du dataset comme l’identité des données. Il avait été stable pendant des années, alors pourquoi changer ?
Une équipe plateforme a réorganisé l’arbre des datasets pour correspondre à une nouvelle organisation. Rien de malveillant, rien de précipité :
tank/prod/finance est devenu tank/prod/business/finance. Les datasets ont été renommés correctement avec ZFS,
snapshots et enfants intacts. Ils ont même envoyé un avis de changement. La réplication, cependant, n’a pas su que
finance avait « déménagé ». Elle a essayé d’envoyer des incrémentiels pour tank/prod/business/finance vers
bkp/prod/business/finance, qui n’existait pas encore. Elle l’a donc créé, a reçu une baseline complète, et a poursuivi.
Le fichier de sauvegarde avait maintenant deux copies : l’ancien bkp/prod/finance bloqué au dernier snapshot d’hier, et le nouveau
bkp/prod/business/finance avec les snapshots d’aujourd’hui. Personne n’a remarqué car la supervision vérifiait seulement
que « le job de réplication a réussi » et que « le dernier snapshot existe quelque part ».
Puis une restauration fut nécessaire. L’astreinte a pris le nom évident—bkp/prod/finance—et l’a trouvé obsolète.
Panique, escalade, salle de crise, reproches. Les données étaient là, juste sous un autre chemin. La restauration a réussi après délai,
et la leçon a été violente : le chemin du dataset n’était jamais l’identité ; c’était une étiquette de commodité.
La correction a été ennuyeuse : mappage basé sur propriété vers un nom de destination stable. Ils ont gardé l’arbre miroir pour la navigation humaine,
mais la réplication ciblait un dataset d’identifiant de service stable et publiait des clones lisibles pour commodité.
Cela a supprimé une classe entière d’erreurs silencieuses.
Mini‑histoire 2 : L’optimisation qui s’est retournée contre eux
Une grande entreprise voulait que la réplication soit « assez rapide pour être ignorée ». Quelqu’un a optimisé la pipeline :
envois parallèles, forte compression en userspace, et pruning agressif des snapshots pour réduire le nombre de datasets.
C’était séduisant lors des premiers tests — les benchs aiment les disques vides et des charges calmes.
Le premier retour de bâton a été le CPU. La compression dans le pipe s’est battue avec le chiffrement, les checksums et la charge normale.
La réplication allait plus vite, mais tout le reste ralentissait. Ils ont aussi subi des pics de latence bizarres parce que les jobs de réplication
s’alignaient sur des traitements batch applicatifs. Les utilisateurs se sont plaints. La correction aurait dû être « planifiez mieux », mais l’équipe
a persisté : plus de parallélisme, plus de tuning.
Le deuxième retour de bâton était plus subtil. Le pruning des snapshots est devenu si agressif que la « dernière snapshot commune »
disparaissait parfois avant que le récepteur n’ait reçu les incrémentiels (coupure réseau, maintenance, peu importe).
Le système retombait alors sur des envois complets. Les envois complets étaient énormes, donc prenaient plus de temps, donc d’autres incrémentiels étaient manqués,
d’où plus d’envois complets. C’était une boucle positive auto‑infligée. Le système de sauvegarde n’était pas cassé ; on lui demandait de sprinter
pendant qu’on lui liaisonit les chevilles.
Le dernier retour de bâton était la complexité opérationnelle. Lorsqu’un dataset était renommé, la pipeline parallèle essayait de « s’auto‑guérir »
en créant de nouveaux datasets de destination et en reseedant. Cela masquait le vrai problème (un changement de mappage) derrière un « succès ».
Les restaurations sont devenues une chasse aux trésors.
Ils ont fini par supprimer la plupart des « optimisations » et les remplacer par deux choses : holds/bookmarks pour garantir une base incrémentielle,
et une règle stricte que les cibles de réplication doivent être stables et inertes (canmount=noauto, readonly=on).
Les performances se sont améliorées parce que le système a arrêté de faire des envois complets inutilement. La plus économique des optimisations est souvent
de ne pas faire de travail supplémentaire.
Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la situation
Une entreprise soumise à des exigences réglementaires exécutait des tests de restauration hebdomadaires. Pas « on l’a monté une fois l’an », mais un runbook documenté :
le dataset reçu reste non monté, ils clonent un snapshot dans un dataset temporaire, définissent un mountpoint sûr, le montent,
exécutent des vérifications d’intégrité applicatives, puis détruisent le clone. Chaque semaine. Même étapes. Même traçabilité.
C’était si ennuyeux que c’était devenu une blague. Mais cela a forcé la discipline : les cibles de réplication restaient en lecture seule,
personne ne les utilisait comme espace temporaire, et l’équipe pratiquait constamment les commandes exactes nécessaires en cas d’incident.
Ils ont aussi détecté des petites régressions tôt : un décalage de nommage de snapshot, une politique de prune trop agressive, un receive
qui commençait à échouer à cause d’un changement de propriété.
Puis un incident réel est arrivé : un ransomware qui a forcé une restauration rapide pour un service spécifique.
Le dataset du service avait été renommé deux semaines plus tôt lors d’un nettoyage. Le nom de production avait changé, mais la propriété d’identité
de réplication est restée la même. Le dataset de sauvegarde était exactement là où le runbook l’attendait : bkp/replica/svc-payments.
La restauration n’a pas été « facile », mais elle a été prévisible. Ils ont cloné le dernier bon snapshot, l’ont monté dans un chemin isolé,
validé les données, puis l’ont promu en production. Le renommage n’a jamais importé. Le test de restauration hebdomadaire avait déjà prouvé
le mappage et les étapes. Les pratiques ennuyeuses n’ont pas de lauriers. Elles rendent votre weekend disponible.
Playbook de diagnostic rapide (trouver le goulot vite)
Quand la réplication ralentit ou échoue, ne commencez pas à basculer des flags au hasard. Vous finirez avec un bazar qui « marche » mais ne permet pas de restaurer proprement.
Exécutez ceci dans l’ordre. L’objectif est d’identifier si le goulot est la lignée, le mappage, la santé du stockage,
le CPU ou le réseau.
Première étape : prouvez la lignée et le mappage (la majorité des échecs)
- Confirmez que vous recevez dans le dataset prévu : listez les snapshots cibles pour le chemin de destination et confirmez qu’il contient la série attendue.
- Trouvez le dernier snapshot commun en utilisant une intersection rapide (voir Task 5). S’il n’y en a pas, vous êtes en reseed.
- Vérifiez la divergence : la cible est‑elle modifiable, montée ou utilisée par quelque chose ? Si oui, considérez la comme divergente jusqu’à preuve du contraire.
Deuxième étape : vérifiez la santé du pool et la pression disque
- Santé du pool :
zpool status -xdoit être propre. Sinon, cessez de traiter la réplication comme le problème principal. - Saturation disque :
iostat -xzet statistiques spécifiques ZFS (si disponibles) pour voir si vous êtes lié par l’I/O. - Espacement libre : un faible espace libre casse la réplication de façons inattendues et accentue la fragmentation.
Troisième étape : vérifiez CPU et réseau
- CPU : si vous utilisez de la compression dans le pipe, du chiffrement ou des checksums lourds, surveillez le temps CPU utilisateur et steal.
- Réseau : vérifiez le débit et la perte de paquets. Les flux de réplication sont sensibles à des liaisons instables car les retries coûtent du temps et les bases de snapshots peuvent expirer.
- Pics de latence : alignez les horaires de réplication loin des jobs batch applicatifs, scrubs et resilvers.
Décisions
- Si la lignée/le mappage est erroné : corrigez le mappage dataset (renommez la cible, changez le mappage par propriété), ne « forcez » pas le receive.
- Si le stockage est malade : réparez/remplacez, puis retentez la réplication ; forcer des receives sur des pools instables crée des incidents secondaires.
- Si les ressources sont saturées : limitez la réplication, réduisez le parallélisme ou planifiez‑la. « Plus rapide » n’est pas « mieux » si ça casse l’objectif de RPO.
Erreurs fréquentes : symptôme → cause → correction
1) « cannot receive incremental stream: most recent snapshot does not match »
Symptôme : Le receive échoue lors de l’application d’un flux incrémentiel.
Cause : Le dataset de destination n’a pas le snapshot de base exact que le flux attend (mauvaise cible, base prunée, ou historique divergent).
Correction : Identifiez le dernier snapshot commun (Task 5). S’il n’y en a pas, reseedez avec un envoi complet dans la bonne destination. Arrêtez de pruner les bases avant que la réplication ne confirme le succès.
2) Les incrémentiels sont devenus des envois complets après un renommage
Symptôme : Après un déplacement de dataset, votre système crée « utilement » un nouveau dataset de destination et lance des baselines complètes.
Cause : Le mappage basé sur le nom a créé un nouveau chemin de destination. L’ancien conteneur contient toujours la lignée.
Correction : Renommez le dataset de destination pour correspondre au mappage attendu, ou—mieux—passez à des noms de destination stables basés sur des propriétés.
3) Les sauvegardes sont « réussies », mais les restaurations sont obsolètes ou manquent des données
Symptôme : Les jobs déclarent un succès ; les tentatives de restauration trouvent des snapshots anciens.
Cause : La réplication atterrit dans un dataset différent de celui attendu par le runbook de restauration (généralement dû à un renommage/déplacement ou plusieurs destinations).
Correction : Faites respecter une seule destination autoritaire par identité de service. Validez « le dernier snapshot existe dans X dataset », pas « existe quelque part ».
4) Le receive échoue avec des problèmes de mountpoint ou de filesystem occupé
Symptôme : zfs recv renvoie des erreurs sur le montage, répertoires non vides ou conflits de mountpoint.
Cause : Les propriétés reçues (ou le comportement de montage par défaut) tentent de monter dans des chemins existants ou utilisés.
Correction : Recevez avec -u. Sur la cible, définissez canmount=noauto et un mountpoint sûr (ou mountpoint=none) pour les datasets répliqués.
5) La réplication a ralenti avec le temps, puis a commencé à manquer le RPO
Symptôme : Même données, même lien, mais les fenêtres de réplication s’allongent.
Cause : Le churn a augmenté (logs, tmp, bases), les snapshots sont conservés plus longtemps que prévu, ou la fragmentation et la pression d’espace libre ont augmenté.
Correction : Déplacez les données à fort churn dans leur propre dataset avec un calendrier de snapshot distinct, gardez de l’espace libre, et arrêtez d’envoyer plus souvent que vous ne pouvez finir.
6) Quelqu’un a utilisé la réplique pour de l’« analytics rapide » et maintenant les incrémentiels n’appliquent plus
Symptôme : Les incrémentiels échouent ; le receive suggère rollback/force.
Cause : La cible a divergé en raison d’écritures locales.
Correction : Politique : les datasets répliqués sont en lecture seule et non montés par défaut. Si la divergence a eu lieu, décidez : détruire et reseed, ou zfs recv -F uniquement si vous acceptez de perdre les changements côté cible.
7) Le pruning des snapshots casse la réplication de façon intermittente
Symptôme : Certaines exécutions fonctionnent, d’autres échouent avec base manquante.
Cause : La rétention supprime le snapshot de base avant que le côté distant confirme la réception (surtout après des interruptions réseau).
Correction : Utilisez des holds sur les snapshots de base jusqu’au succès de réplication. Envisagez des bookmarks comme ancres pour éviter une rétention longue des snapshots.
Listes de contrôle / plan pas à pas
Checklist A : Rendre la réplication à l’épreuve des renommages (mappage par propriété)
- Choisissez une racine de destination stable sur la sauvegarde :
bkp/replica. - Pour chaque dataset de service, définissez des propriétés d’identité sur la source :
org.example:replicate=trueorg.example:replica-name=<service-id>
- Sur la sauvegarde, créez les datasets de destination correspondant aux IDs de service (une fois) :
bkp/replica/<service-id>. - Forcer des propriétés sûres sur les cibles de sauvegarde :
canmount=noauto,readonly=on,mountpoint=none(ou un chemin sûr). - Standardisez les noms de snapshots :
replica-YYYYMMDD-HHMM. - Implémentez des holds pour la dernière base répliquée ; libérez les holds après réception réussie.
- Ajoutez un workflow de test de restauration utilisant des clones (sans monter la réplique directement).
Checklist B : Renommage ou déplacement planifié (le faire sans casser les incrémentiels)
- Confirmez le dernier snapshot répliqué avec succès des deux côtés.
- Placez un hold sur ce snapshot (côté source) pour empêcher le pruning pendant la fenêtre de changement.
- Prenez un snapshot pré‑changement (
@replica-pre-rename). - Renommez/déplacez le dataset avec
zfs rename(et assurez‑vous que les enfants suivent comme prévu). - Vérifiez que les snapshots existent sous le nouveau nom de dataset.
- Si vous miroitez les noms sur la cible, renommez le dataset cible pour correspondre ; sinon, assurez‑vous que les propriétés continuent de mapper au même ID de destination.
- Reprenez la réplication depuis le dernier snapshot commun ; vérifiez avec une estimation en dry‑run.
- Libérez les holds une fois le nouveau snapshot confirmé sur le récepteur.
Checklist C : Quand il faut reseed (envoi complet) en sécurité
- Arrêtez les incrémentiels pour ce dataset.
- Créez un snapshot de baseline frais sur la source.
- Recevez dans un nouveau chemin de dataset (temporaire) pour éviter d’écraser des répliques connues bonnes.
- Après succès, changez le mappage vers le nouveau dataset (ou renommez‑le en place), puis détruisez l’ancienne réplique selon la politique.
- Redémarrez les incrémentiels depuis le snapshot de baseline.
FAQ
1) Renommer un dataset supprime‑t‑il des snapshots ou casse les GUID de snapshot ?
Non. Un renommage change le nom du dataset dans le namespace. Les snapshots bougent avec lui. Ce qui casse, c’est votre automatisation si elle attend l’ancien chemin.
2) Pourquoi la réplication incrémentielle tient‑elle tant au snapshot de base ?
Parce qu’un flux incrémentiel est littéralement « appliquer les changements du snapshot A au snapshot B ». Si le récepteur n’a pas le snapshot A dans le dataset cible, il ne peut pas appliquer le delta en sécurité.
3) Dois‑je utiliser zfs recv -F pour corriger des incohérences ?
Seulement si vous acceptez que le récepteur fasse rollback/détruise des snapshots plus récents (et possiblement des changements locaux) pour correspondre au flux.
C’est un outil, pas un comportement par défaut. Utilisez‑le quand vous corrigez une réplique contrôlée, pas quand vous ignorez où le flux atterrit.
4) Puis‑je répliquer un dataset qui a été promu depuis un clone ?
Oui, mais la promotion change l’ascendance. Votre base incrémentielle existante peut ne plus correspondre.
Attendez‑vous à reseeder ou à choisir soigneusement une nouvelle chaîne de snapshots commune.
5) Comment les bookmarks aident‑ils avec les renommages et le pruning ?
Les bookmarks ne tiennent pas compte des mountpoints et sont peu coûteux. Ils vous permettent de conserver une ancre incrémentielle même si vous supprimez des snapshots anciens.
Ils ne résolvent pas seuls les problèmes de mappage de noms, mais ils empêchent les interruptions dues à la disparition de la base de snapshot.
6) Les datasets répliqués doivent‑ils être montés ?
Pas par défaut. Gardez‑les inertes : canmount=noauto et recevez avec -u. Pour les restaurations, clonez un snapshot et montez le clone dans un emplacement sûr.
7) Est‑ce que je réplique des propriétés comme mountpoint et readonly ?
Soyez sélectif. Répliquer mountpoint vers un hôte de sauvegarde est un classique qui vous tire une balle dans le pied. Préférez définir des propriétés locales sûres sur le récepteur et évitez d’hériter du comportement de montage de production.
8) Si mon dataset source se déplace sous un autre parent, ai‑je besoin d’un nouvel envoi complet ?
Pas nécessairement. La lignée est toujours présente. Si votre mappage de destination reste stable (ID par propriété), vous pouvez continuer à envoyer des incrémentiels normalement.
Si votre mappage est basé sur le nom, vous créerez probablement un nouveau chemin de destination et forcerez involontairement un reseed.
9) Comment garder une navigation lisible pour les humains sans coupler la réplication aux noms ?
Répliquez dans des datasets d’identité stables (IDs de service). Puis créez des clones en lecture seule ou des datasets secondaires avec des noms conviviaux pour la navigation.
Les humains ont un arbre ; l’automatisation a la stabilité.
10) Quelle est la meilleure habitude pour éviter les surprises de réplication ?
Tests de restauration hebdomadaires utilisant des clones, avec un runbook qui précise les datasets et noms de snapshots exacts. Cela expose la dérive de mappage tôt — avant d’en avoir besoin en incident.
Prochaines étapes réalisables cette semaine
- Choisissez un schéma d’identité de réplication : mappage par propriété vers un nom de destination stable par service. Implémentez‑le pour un dataset d’abord.
- Rendez les datasets de réplique inertes : définissez
canmount=noauto, recevez avec-u, envisagezreadonly=on. - Standardisez les noms de snapshots et stoppez l’utilisation de multiples schémas de nommage dans le même environnement.
- Ajoutez des holds ou bookmarks pour empêcher le pruning de supprimer la base incrémentielle avant la fin de la réplication.
- Rédigez un runbook de test de restauration qui monte uniquement des clones, jamais le dataset de réplique directement, et exécutez‑le selon un calendrier.
- Instrumentez les bons signaux : dernier snapshot commun par ID de service, retard de réplication, et « reçu dans le dataset attendu », pas seulement les codes de sortie.
Les renommages et déplacements de datasets sont normaux. Votre travail est de les rendre ennuyeux. ZFS coopérera — tant que vous cesserez de traiter les noms de datasets comme le destin.