Aller au contenu

Blog ·

Projeter, jamais remplacer

Une vue d’avancement sur migrations, sauvegardes, restaurations et réinstallations, sans devenir un second registre. Deux pannes ont été totalement muettes.

Par Provibr

Un panneau d’hébergement accumule du travail de longue durée. Une migration entre deux panneaux de contrôle, une sauvegarde, une restauration vers une machine neuve, un conteneur remplacé. Chacun a fait naître sa table, son vocabulaire d’états et son écran, et cela tient jusqu’à ce que quelqu’un pose la question évidente : qu’est-ce qui tourne en ce moment sur mon compte, et y a-t-il quelque chose de bloqué.

La réponse tentante est un système de tâches. Une table, une liste d’états, tout le reste écrit dedans. La réponse tentante est aussi le chemin vers deux sources de vérité sur le même fait, et vers une conversation de support un an plus tard où la tâche dit échec et où la table des sauvegardes dit que l’archive est sur le stockage.

La vue des opérations est donc une projection. Elle lit les lignes existantes et écrit un résumé par-dessus. Rien ne va jamais dans l’autre sens : pas une fonction du module de miroir ne touche aux exécutions de sauvegarde, de restauration, de migration ou aux réinstallations. Ces tables restent la vérité, et le résumé a le droit d’être périmé, faux ou absent sans que rien ne casse en aval.

Un miroir par table, et un test qui lit le schéma

Quatre tables reposent sur cette couture, et chacune a exactement une fonction qui la projette.

Table sourceColonne d’étatType d’opération
backup_runsstatusbackup.run
backup_restore_runsstatusbackup.restore
migration_runsstatusmigration.run
container_reinstall_intentsstatecontainer.reinstall

La dernière ligne explique pourquoi les noms de colonne sont écrits comme du texte et non supposés. Cette table nomme la colonne state, et un miroir qui lirait status ne reçoit rien et bascule sans se plaindre dans la branche d’échec.

Cette table n’est pas de la documentation. Un test déduit du schéma de la base lesquelles de ces tables ont une colonne d’état, exige une entrée ici pour chacune, exige que la fonction nommée existe vraiment, et exige, c’est la part qui fait le vrai travail, que tout fichier écrivant une de ces colonnes nomme aussi son miroir. Une cinquième table arrivant sur la couture sans projection est ainsi un test rouge plutôt qu’un tiroir qui cesse silencieusement de bouger.

Cette sévérité est nécessaire parce que les écrivains sont dispersés : une exécution de sauvegarde change d’état à six endroits d’un même fichier, dont trois sur le chemin qui traite les réponses de la réconciliation qui tourne la nuit. Laisser chaque écrivain appeler la projection lui-même revient à ce que le sixième l’oublie, et l’oubli est invisible. La ligne est correcte. Seul l’écran ne se met jamais à jour.

Le puits qui existait deux fois

Le miroir vit dans le paquet qui détient les écritures en base, car une action serveur de l’application web comme un composant de la passerelle appellent les mêmes fonctions. Ce paquet ne peut pas publier d’événement en direct : il dépend de la base et de code partagé et de rien d’autre, et le bus de messages est un objet différent de chaque côté.

Il expose donc un puits. Chaque application en enregistre un, et le paquet ignore qui l’a rempli. Si personne ne le fait, l’écriture en base se poursuit et aucun événement en direct ne part, et le tiroir se met à jour au prochain chargement de page. C’est une dégradation honnête, et la fonction ne lève jamais d’exception à dessein, car un enregistrement manquant ne doit pas pouvoir renverser une sauvegarde.

Le puits était une variable de module, et c’était faux d’une manière qui ne produisait aucune erreur. Next exécute son fichier d’instrumentation comme un paquet à part, séparé des processus de route. La variable existait donc deux fois : l’enregistrement atterrissait dans un exemplaire et la fonction émettrice lisait l’autre. Les lignes étaient justes, les états avançaient, le tiroir restait immobile, et il n’y avait pas une ligne dans le moindre journal.

Cela n’a pas été trouvé en réfléchissant. Cela a été trouvé en posant un écouteur sur le bus de messages et en comptant : un événement est arrivé là où il aurait dû y en avoir trois. La réparation est un emplacement sur globalThis derrière un Symbol.for, qui survit à une frontière d’empaquetage à l’intérieur d’un même processus, même forme et même raison que la connexion partagée à la base et celle à Redis.

Deux leçons qui valent plus que la panne. Une défaillance sans ligne d’erreur exige un instrument hors du code que l’on soupçonne. Et « ce module a un exemplaire » est une hypothèse sur un empaqueteur, pas une propriété du langage.

La même série a trouvé un second cas muet. Terminer une opération et la bloquer laissaient toutes deux la colonne d’étape intacte : une sauvegarde parfaitement réussie restait donc à jamais sur « archivage en cours ». Rien n’échouait ; simplement, la dernière étape n’arrivait jamais.

Annuler est une demande, et une seule écriture en est la preuve

C’est à l’annulation qu’une projection est le plus tentée de mentir. Le tiroir connaît l’opération, l’opération a un bouton d’annulation, et la mise en œuvre la moins chère pose un indicateur et referme le panneau.

Chaque type déclare donc si annuler a seulement un sens. Un vzdump en cours, nous ne pouvons pas l’annuler : la commande est chez l’agent, la tâche chez l’hyperviseur, et personne ne sait ce que représente une demi-archive sur le stockage du client. Une restauration est identique, avec une raison en plus : la ligne de restauration tient une réservation sur l’identifiant cible tant qu’elle est ouverte, et la refermer tôt libère cet identifiant alors qu’un invité y atterrit peut-être. Les deux sont déclarées non prises en charge, et aucun chemin ne contourne cela.

Là où annuler est possible, c’est encore la ligne qui décide. Une migration s’annule tant qu’elle est planifiée et plus dès qu’un programme auxiliaire écrit des serveurs sur l’hôte de quelqu’un d’autre. Une réinstallation de conteneur s’annule jusqu’à l’aperçu et plus dès que l’agent a commencé à remplacer.

Quand cela fonctionne, la preuve que rien ne s’est produit est une unique écriture conditionnelle : passer la commande en file d’attente à annulée là où son état est encore en attente. Si elle a déjà été envoyée, cette instruction touche zéro ligne et la demande reste honnêtement à « demandée ». Consulter puis écrire seraient deux instructions et donc deux occasions de s’intercaler.

Et alors la phrase compte. L’événement dit que la commande a été retirée de la file avant que l’agent ne la reçoive, et non qu’elle a été annulée. Rien n’a été interrompu, car rien n’avait commencé. Cette ligne va dans le journal général d’événements et non dans l’avancement de l’opération, car l’avancement est balayé après quatre-vingt-dix jours et « une personne est intervenue sur l’infrastructure » n’est pas de l’avancement.

Quatre blocages, et aucun n’est un échec

Quatre situations placent une opération en attention requise plutôt qu’en échec.

Un vzdump a signalé OK et l’archive n’est pas dans l’index. Une tentative de sauvegarde a dépassé son délai et nous ignorons si l’archive existe. Une restauration n’est pas relisible ou l’invité obtenu n’a pas pu être enregistré. Une réinstallation de conteneur s’est arrêtée après la disparition de l’ancien conteneur.

Appeler l’une d’elles un échec serait un mensonge à conséquence. Échec invite le client à recommencer, et refaire une sauvegarde représente encore des dizaines de gigaoctets sur le stockage de quelqu’un pour une archive peut-être déjà là. Refaire une réinstallation, c’est un second conteneur. La boucle de réconciliation continue de regarder jusqu’à ce que l’index tranche, et l’opération reste visiblement bloquée entre-temps, ce qui est la juste dose d’inquiétude.

Le blocage porte aussi à qui il appartient. Trois d’entre eux sont les nôtres ; une commande qui a réellement échoué du côté du client le dit.

Une simulation s’arrête à la planification, et ce n’est pas un trou

Une migration en simulation compte cinq étapes ; une vraie en compte huit, les trois supplémentaires étant l’écriture et le recomptage. La liste d’étapes en contient six, une simulation réussie affiche donc ses deux dernières comme non faites.

Cela ressemble à un défaut de barre d’avancement et c’est la vérité : une simulation n’écrit pas et ne recompte pas. Les alternatives étaient un second type, qui double neuf entrées de catalogue en neuf langues pour le même travail, ou une liste d’étapes variable selon la ligne, ce qui ferait diverger le tiroir et la page de détail sur le nombre d’étapes. Montrer la vérité coûtait moins cher que les deux.

Ce que nous noterions

  1. Une projection projette. Si votre couche de résumé peut réécrire dans la source, c’est un second registre et vous en avez désormais deux.
  2. Mettez la couture dans une table qu’un test peut lire, dérivée du schéma, et faites-lui vérifier que tout écrivain d’une colonne d’état nomme son miroir. Un appel oublié ne produit aucune erreur.
  3. Ne supposez pas qu’un module a un exemplaire. Un cadre qui exécute une partie de votre code comme paquet séparé vous en donnera deux, et le symptôme est le silence.
  4. Mesurez hors du suspect. Un écouteur sur le bus a trouvé en quelques minutes ce que la lecture n’avait pas trouvé du tout.
  5. Dites ce qui s’est réellement passé. « Retirée de la file avant que l’agent ne la reçoive » est plus long que « annulée » et c’est la seule version exacte.
  6. Réservez échec à l’échec. Les issues inconnues ont besoin d’un état à elles, sinon les gens recommencent des opérations coûteuses peut-être déjà réussies.

Pour être clair sur ce dont il s’agit : les types décrits ici sont derrière des commutateurs de déploiement désactivés par défaut, les migrations et les sauvegardes client ne sont donc pas achetables aujourd’hui. Plusieurs choses restent non prouvées. L’événement d’avancement en direct n’a été mesuré que pour les migrations. Le côté passerelle de l’enregistrement du puits n’a pas été éprouvé avec une passerelle réellement en marche. La chaîne de l’agent a été reproduite en SQL. Et annuler une réinstallation de conteneur depuis l’écran des opérations n’a jamais été conduit dans un navigateur.

Issu du travail de plateforme sur Provibr. Pourquoi une barre d’avancement refuse d’estimer une heure de fin est dans une barre de progression qui refuse de deviner, et pourquoi une migration qui a sauté en silence est pire qu’une migration en échec est dans la migration qui n’a jamais tourné.

Commencez avec un hôte et un agent

Créez un compte, installez l’agent avec une seule commande et connectez votre premier hôte Proxmox. La période d’essai dure 14 jours et ne demande aucune coordonnée de paiement.