Aller au contenu

Blog ·

Une jointure plutôt qu’un rapprochement

Une étape d’automatisation s’est figée sur l’état qu’elle a vu. Le correctif n’était pas un processus de fond : cette ligne explique une décision prise.

Par Provibr

Un flux d’automatisation peut envoyer une commande à une machine et l’attendre. Si l’attente expire, l’étape échoue et l’exécution prend sa sortie d’erreur. C’est correct : le flux a demandé d’attendre que le travail soit fait, il ne l’est pas, et personne ne sait donc si le nœud suivant va agir sur une machine encore en train de s’éteindre.

L’étape consigne ce qu’elle a vu à cet instant. Mesurée lors d’une manche antérieure contre un vrai agent, elle a consigné sent, et soixante millisecondes plus tard la commande est passée en dépassement de délai. L’étape disait toujours sent, un jour après, pour toujours.

La lecture évidente est que la ligne est périmée et qu’il faut la réparer. Cette lecture est fausse, et comprendre pourquoi a occupé l’essentiel de la réflexion de cette manche.

La ligne est une explication, pas un champ d’état

La valeur figée n’est pas une copie de l’état de la commande qui aurait dérivé. C’est la réponse à une autre question : que savait le flux au moment où il a décidé de prendre la sortie d’erreur.

Écrivez timeout par-dessus et la ligne dit désormais que le flux a réagi à un dépassement de délai. Il n’en a rien fait. Il a réagi à une attente qui s’achevait alors que la commande était encore en vol, un événement différent avec une autre cause et un autre correctif. Un mois plus tard, qui lirait cette exécution tirerait la mauvaise conclusion d’une ligne corrigée avec de bonnes intentions.

La valeur figée reste donc figée. Ce qui était réellement faux, c’est le commentaire au-dessus, qui promettait de lire sur cette ligne si la commande avait fini par réussir. On ne le pouvait pas, et une promesse dans un commentaire a une durée de conservation qui court jusqu’au jour où quelqu’un s’y fie.

La vraie question, celle de savoir si cela s’est finalement bien terminé, se règle en lisant l’état actuel de la commande au moment où quelqu’un regarde. La page d’exécution montre les deux côte à côte : ce que le flux voyait alors, et ce qui est vrai maintenant.

D’où cela vientCe que cela répond
AlorsLa ligne d’audit, figée au moment de la décisionPourquoi l’exécution a pris la sortie qu’elle a prise
MaintenantLa table des commandes, lue à l’ouverture de la pageSi le travail a fini par aboutir
Ligne absenteNi l’une ni l’autre ; montrée comme état propreLa commande ne figure plus dans les registres

Cette dernière ligne compte. Si la ligne de commande a été nettoyée, l’affichage ne se rabat pas sur la répétition de la valeur figée. « Ne figure plus dans les registres » et « dit toujours sent » sont deux faits distincts, et une page qui rend le second alors que le premier est vrai ment sans sourciller.

Trois raisons pour lesquelles un processus de fond perd face à une jointure

L’autre solution était un processus de fond réécrivant l’état final dans la ligne d’audit dès que la commande est tranchée. Nous avons ailleurs des processus exactement de cette forme, c’était donc un vrai choix et non un épouvantail.

Un processus de fond écrase une explication. Voir plus haut. L’échappatoire consiste à écrire un second champ à côté, ce qui est honnête, mais c’est une seconde vérité qui serait lue à exactement un endroit : celui qui fait maintenant la jointure.

Un processus de fond ne répare que ce qui arrive après lui. La ligne qui a lancé toute cette enquête existe déjà. Un processus déployé aujourd’hui ne la toucherait jamais, ni aucune exécution des deux manches précédentes, car leurs commandes sont tranchées depuis longtemps et aucun événement futur ne repassera par là. Une jointure les montre toutes dès le jour de sa mise en service, y compris celle que nous cherchions.

Un processus de fond amène tout un appareil. Mettre des lignes à jour en arrière-plan réclame une cadence, une réservation pour que deux instances ne se heurtent pas, un délai contre la dérive, une réponse sur la conservation et un chemin d’échec propre, et chacun d’eux peut être faux à trois heures du matin. Retrouver une poignée d’identifiants par clé primaire quand quelqu’un ouvre une page ne réclame rien de tout cela.

La règle générale en dessous : mieux vaut lire la source que la copier, sauf si la copie répond à une question à laquelle la source ne peut pas répondre. Ici la copie répond à une autre question et l’on veut les deux, et c’est précisément le cas où on les garde séparées.

L’identifiant venait d’une colonne JSON, la requête est donc bornée

Un détail de cette jointure n’est ni une optimisation ni une politesse.

L’identifiant de commande se trouve dans un bloc JSON de la ligne d’audit. Les colonnes JSON n’ont pas de clés étrangères, donc rien ne garantit que l’identifiant qui s’y trouve appartienne à ce compte. Aujourd’hui il y appartient, parce que notre propre code l’y a écrit. Ce n’est pas une frontière, c’est une habitude.

La recherche est donc bornée à l’organisation en plus de l’identifiant. Sans cette limite, une valeur modifiée à la main dans ce bloc lirait l’état de la commande de quelqu’un d’autre. Petite fuite, facile à manquer, et du genre que seul trouve celui qui la cherchait.

Un flux par moment, avec le moment dans la clé

Une offre d’hébergement pouvait relier un flux d’automatisation, dans une seule colonne. Cette colonne signifiait « exécuter ceci quand une machine est provisionnée », et cela n’était écrit nulle part sauf dans le comportement de la fonction qui la lisait.

Les clients voulaient aussi un flux à la réinstallation et un autre à la suppression. Le correctif évident est trois colonnes. Il est faux pour une raison qui mérite d’être nommée : chacune de ces trois colonnes pourrait contenir un flux dont le propre déclencheur dit autre chose. Un flux qui s’exécute à la suppression, posé dans la colonne qui se déclenche au provisionnement. L’invariant « le déclencheur dit quand il s’exécute » devient violable en trois endroits au lieu d’aucun, et le schéma ne proteste nulle part.

Le lien est donc devenu une table à part, avec l’offre et le moment ensemble comme clé unique. Un flux par moment et par offre, imposé par un index plutôt que par le soin.

Deux détails de cette migration méritent d’être gardés.

Le moment est stocké en texte avec une contrainte de contrôle énumérant trois valeurs, et non dans l’énumération de déclencheurs existante. Cette énumération compte cinq valeurs, dont deux que ce mécanisme ne choisit jamais : une exécution manuelle est lancée par une personne, une exécution par webhook par une requête entrante. Une colonne énumérée accepterait volontiers l’une ou l’autre, et il existerait alors une ligne de liaison avec laquelle rien n’arrive jamais. Un réglage qui, en silence, ne fait rien est pire qu’un réglage absent.

Et l’ancienne colonne a été supprimée dans la migration même qui a recopié son contenu. La garder comme lecture dérivée commode est exactement la seconde vérité que cette table vient supprimer. Le compilateur a trouvé chaque lecteur dès la sortie de la colonne du schéma ; il y en avait sept. La recopie précède la suppression, dans une seule transaction, il n’existe donc aucun instant où la configuration d’un client ne se trouve nulle part.

La recopie reprend en outre le déclencheur propre de chaque flux au lieu de supposer le sens de l’ancienne colonne. Les lignes dont le flux portait un autre moment n’auraient jamais tourné sous l’ancien code ; désormais, le lien fait par une personne fait vraiment ce qu’elle croyait.

Un courrier sur adhésion, dans le graphe et non dans les réglages

Un flux peut écrire une notification, et cette manche lui permet de partir aussi par courrier. Cela s’est révélé une question de valeurs par défaut et non d’envoi.

Les catégories de courrier de la plateforme forment une liste d’exclusion : ce qui n’est pas explicitement éteint est allumé. C’est juste pour les catégories déjà présentes, qui sont toutes des choses qu’un client doit savoir. Ce ne l’est pas pour une notification dont quelqu’un a écrit le texte dans un éditeur d’automatisation, car un flux qui tourne à chaque commande écrirait à toute l’équipe à chaque fois, jusqu’à ce que quelqu’un trouve le réglage.

Plutôt que d’inverser la liste, la valeur par défaut vit dans le graphe : la case du nœud est éteinte, et l’allumer est un acte délibéré de celui qui a écrit le texte. La catégorie continue d’exister pour que le courrier puisse être éteint à l’endroit habituel, exactement comme les autres. Deux mécanismes, chacun pour ce qu’il sait faire.

Une exécution d’essai n’envoie jamais de courrier. Elle écrit la notification du panneau, qui est interne et se referme d’un clic, et elle n’envoie rien vers l’extérieur, pour la même raison qu’une exécution d’essai ne touche jamais une machine : le courrier arrive dans la boîte de personnes réelles, et un bouton de test qui surprend quelqu’un n’est pas un bouton de test.

Encore une petite chose issue du même travail. Le côté envoi était réputé inaccessible depuis l’endroit où tournent les flux, et c’est pourquoi cela avait été reporté. Mesuré, c’était l’inverse : la fonction que nous voulions contourner existe justement parce que l’autre processus ne sait pas composer un courrier, et elle demande au processus dans lequel les flux tournent déjà de le composer. Le détour était plus long que la voie directe, et la voie directe compte un saut HTTP, une signature et un chemin d’échec de moins.

Ce que nous noterions

  1. Une ligne d’audit explique une décision. Ne la réparez pas. La question de ce qui est vrai maintenant a une autre réponse et mérite sa propre place.
  2. Une réparation en arrière-plan ne répare que l’avenir. Si la ligne à l’origine du travail ne serait pas touchée, c’est une réponse de conception et non un détail.
  3. Lire la source, sauf si la copie répond à ce que la source ne peut pas.
  4. Un identifiant issu d’une colonne JSON n’a pas de clé étrangère. Borner la requête au compte ; que notre code ait écrit la valeur n’est pas une frontière.
  5. Mettre la distinction dans la clé et non dans le nom de colonne. Trois colonnes font trois endroits où ranger la mauvaise chose ; une clé n’en fait aucun.
  6. Supprimer l’ancienne colonne dans la même migration. Une lecture dérivée laissée derrière est la seconde vérité que l’on voulait éliminer.
  7. Une liste d’exclusion est le mauvais défaut pour un texte écrit par un client. L’adhésion se place là où le texte s’écrit.

Pour être clair : tout cela est construit et fonctionne. L’envoi lui-même a été prouvé avec un pilote qui se contente de journaliser et non contre un vrai serveur de courrier, et le lien avec l’offre n’a pas encore été exercé par une vraie commande.

Issu du travail de plateforme sur Provibr. Comment se comportent l’attente et la sortie d’erreur au départ est expliqué dans un fil que l’on tire, et exactement un déclencheur, et pourquoi un dépassement de délai n’est pas un bouton d’arrêt dans un dépassement de délai n’est pas un bouton d’arrêt.

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.