Le worker est mort. La synchronisation, non.

Tuez le worker au milieu d’un lot et suivez un contact : quel curseur a été enregistré, pourquoi l’écriture rejouée n’arrive pas deux fois, qui détient la synchronisation pendant son redémarrage. Puis les deux incidents qui ont montré que la reprise n’était que la moitié du travail.

reprise après échec de synchronisation

Que devient une synchronisation en cours quand le worker meurt au milieu d’un lot ?

Le curseur n’avance qu’à la fin d’un lot sans erreur : un worker tué au milieu d’un lot le laisse au début de ce lot, et l’exécution suivante relit la même page. La clé de livraison de chaque enregistrement est dérivée du curseur de page et de sa position, et un registre de sept jours des écritures terminées empêche le rejeu d’arriver deux fois. Un seul bail par synchronisation empêche deux exécutions de travailler en même temps. C’était la moitié facile. Une synchronisation a échoué encore et encore, exécution après exécution, avant que quoi que ce soit ne la mette en pause, et un espace de travail est resté silencieux, avec des erreurs et aucune réussite, avant que quoi que ce soit ne le remarque. Il existe donc désormais un disjoncteur qui met en pause après cinq échecs consécutifs et une alerte horaire après vingt-quatre heures sans réussite.

En savoir plus
Sur cette page

Dans une conférence de l’équipe relations développeurs de Crusoe, publiée le 3 octobre 2026 par AI Engineer, une tâche d’entraînement PyTorch a été lancée sur deux nœuds A100. Puis on a signalé au propre système de supervision de la démo qu’un GPU avait levé une erreur XID 79. Le nœud a été marqué hors service et la tâche annulée. Le processus a reçu un SIGTERM et « jusqu’à deux minutes pour traiter ce SIGTERM, soit pour enregistrer vos checkpoints, soit pour vider vos logs ». Un nœud de réserve a pris le relais, et « la tâche slurm va redémarrer. Elle chargera le checkpoint et reprendra l’entraînement là où elle s’était arrêtée ». Un peu moins de quinze minutes de bout en bout. Personne n’a été appelé d’astreinte.

Une synchronisation est une bête bien plus petite qu’un entraînement. La question est la même. De quoi une exécution a-t-elle besoin pour que tuer le worker coûte quelques minutes et aucun doublon ?

Voici la réponse pour le moteur de synchronisation de Tajo, lue dans le code plutôt que dans une brochure. Le Reverse ETL décrit la forme du travail. Voici ce qui se passe quand le travail meurt.

Suivre un contact

Une exécution lit la source page par page, à partir d’un curseur enregistré. Appelons le curseur de page c3, et notre contact le 212e enregistrement de cette page. Trois choses sont vraies pour ce contact avant que quoi que ce soit ne soit écrit.

D’abord, il a une clé de livraison. La clé est un hachage de l’identifiant de la synchronisation, de la source, du curseur de page, de la position sur la page et de l’enregistrement lui-même. Le curseur et l’enregistrement entrent dans le hachage et ne sont jamais intégrés à la clé : la clé ne contient donc aucune adresse e-mail.

Ensuite, l’écriture passe par un registre, pas directement par la destination. Avant l’appel à la destination, la clé est réservée comme en attente. Quand l’écriture réussit, la ligne est marquée comme terminée et conservée sept jours.

Enfin, le curseur n’a pas bougé. Il n’avance qu’à la fin d’un lot, seulement si chaque enregistrement de ce lot est arrivé, et seulement après que l’exécution a prouvé qu’elle détient toujours la synchronisation.

Tuez maintenant le worker entre l’écriture et le point de reprise.

Sur un SIGTERM, chaque worker de file d’attente se vide dans un même délai partagé de quinze secondes, et le processus se termine au bout de vingt-cinq secondes quoi qu’il reste en cours ; systemd lui en accorde quarante-cinq avant de le tuer. Une exécution qui n’a pas fini dans cette fenêtre reste marquée en cours, et le worker suivant la reprend grâce à la récupération des tâches bloquées de la file. Sur un arrêt brutal, il n’y a aucune vidange. Dans les deux cas, les deux mêmes faits tiennent. La destination a notre contact. Notre curseur indique toujours c3.

Le rejeu

L’exécution suivante démarre à c3, lit la même page, dérive la même clé pour le 212e enregistrement et interroge le registre. Le registre répond : terminé. La destination n’est pas appelée ; le résultat stocké est rejoué et l’exécution passe à la suite. Sur le chemin d’import en masse, où le point de terminaison d’import de Brevo reçoit une page entière en un seul appel, la même vérification s’exécute pour chaque clé avant la construction de la requête. Une page dont toutes les clés sont déjà terminées n’envoie rien.

C’est tout le secret, et il vaut la peine d’expliquer pourquoi il fonctionne. La clé est dérivée du curseur de page au début du lot, pas de l’exécution qui tournait à ce moment-là. Un crash, une nouvelle tentative soixante secondes plus tard et une exécution planifiée demain dérivent tous la même clé pour le même enregistrement. Le registre ne se soucie pas de savoir lequel d’entre eux le consulte.

Venons-en au coût. Le registre oublie au bout de sept jours. Le commentaire du code lui-même indique que sept jours couvrent une nouvelle tentative différée et une courte panne, et il ne prétend rien de plus. Un rejeu depuis un curseur plus ancien écrit à nouveau. Et une exécution interrompue brutalement peut attendre. Le bail de l’exécution morte tient jusqu’à quinze minutes, et, sur une synchronisation planifiée, une exécution dont le bail est libre n’est balayée et replanifiée qu’une fois démarrée depuis plus d’une heure. La démo de Crusoe repart en moins de quinze minutes. La nôtre, pas toujours.

Qui détient la synchronisation pendant son redémarrage

Une seule exécution détient une synchronisation à la fois. L’exécution prend un bail dans Redis avec son propre jeton, une durée de vie de quinze minutes et un renouvellement toutes les cinq minutes. Avant chaque point de reprise, elle vérifie qu’elle détient toujours ce bail. Si ce n’est pas le cas, elle s’arrête. L’exécution est enregistrée avec la phrase simple que la personne lira : « The run lost its slot part-way through and stopped to avoid writing twice. » (L’exécution a perdu sa place en cours de route et s’est arrêtée pour éviter d’écrire deux fois.) Une seconde exécution qui entre en collision avec un détenteur actif n’est pas relancée. Elle se termine par « Another run of this sync was already in progress » (Une autre exécution de cette synchronisation était déjà en cours), et l’exécution planifiée suivante démarre normalement.

La responsabilité de la correction est elle aussi écrite. Chaque code d’échec correspond à une phrase sur ce qui s’est passé, une phrase sur ce qu’il faut faire ensuite, et un responsable : vous ou Tajo. Une destination qui a rejeté chaque enregistrement parce que le mappage est faux, c’est à vous. Un bail perdu, c’est à nous. La page de l’exécution indique l’étape suivante ; le guide de reprise des synchronisations précise lequel.

Jusqu’ici, c’est la moitié facile. Un crash coûte un rejeu et rien n’arrive deux fois. Deux incidents nous ont appris l’autre moitié.

Échec après échec

Une synchronisation a échoué encore et encore. Pas dans une boucle serrée : une fois par intervalle du planificateur, exécution après exécution, avec le même message à chaque fois, selon lequel un identifiant personnalisé ne peut pas contenir de deux-points. L’exécution n’a jamais atteint la destination. Elle mourait pendant sa mise en file d’attente, avant qu’un moteur d’exécution ne la voie : une alerte fondée sur le moteur d’exécution n’avait donc jamais rien sur quoi se fonder.

Rien ne surveillait la série. C’est tout le constat.

La correction est un disjoncteur. Après chaque échec enregistré, il lit les cinq dernières exécutions terminées de la synchronisation depuis sa dernière modification. Si les cinq sont des erreurs, il met la synchronisation en pause, écrit une ligne d’audit avec le motif circuit_breaker_tripped et déclenche une alerte critique. La série est dérivée des exécutions stockées, pas d’un compteur dans le worker. Un échec enregistré par n’importe quel chemin compte, y compris l’échec de mise en file d’attente qui a causé l’incident. Le commentaire du code indique qu’il aurait arrêté cet incident à la cinquième exécution. Une seule réussite remet la série à zéro. Lever la pause réarme le disjoncteur au lieu de le faire sauter à nouveau sur les anciennes lignes. Le guide du connecteur Shopify dit la même chose en une ligne : après cinq exécutions échouées consécutives, la synchronisation se met en pause et sa page affiche la dernière erreur.

Ce que le disjoncteur ne fait pas, c’est réparer quoi que ce soit. Crusoe remplace le nœud et redémarre la tâche sans demander. Tajo s’arrête et demande : une synchronisation en pause le reste jusqu’à ce qu’une personne corrige la cause et la republie. Ce choix prudent par défaut est délibéré : une nouvelle tentative automatique d’une synchronisation qui écrit dans des fiches clients est elle-même une écriture.

Le silence que personne n’a vu

L’objection est légitime, et elle se formule ainsi : « Un disjoncteur qui met en pause après cinq échecs aurait aussi intercepté le second incident. Pourquoi construire un second veilleur ? » Parce que le second incident ne ressemblait pas à un échec. Pendant une longue période, un espace de travail avait une synchronisation active, une pile croissante de lignes d’erreur et pas une seule réussite, jamais, et rien n’a alerté. Chaque signal existant reposait sur une activité : une exécution a échoué, un webhook est arrivé, un budget a été dépassé. Zéro réussite n’est pas un événement. C’est l’absence d’un événement, et un disjoncteur ne s’exécute que lorsqu’un échec est enregistré. Un planificateur mort, une file d’attente bloquée, un worker qui ne démarre jamais : rien de tout cela n’enregistre quoi que ce soit, et un disjoncteur ne se réveille jamais.

Le dispositif d’homme mort d’une locomotive fonctionne à l’inverse. Le conducteur doit continuer à appuyer dessus, et le silence arrête le train. La version pour la synchronisation s’exécute une fois par heure, à la treizième minute. Pour chaque espace de travail doté d’une synchronisation active, elle pose une seule question : une exécution a-t-elle réussi au cours des vingt-quatre dernières heures ? Un espace de travail dont la plus ancienne synchronisation active a été activée il y a moins d’un jour est laissé tranquille ; il n’a pas encore eu une journée pour réussir. Un espace plus ancien et silencieux reçoit une alerte critique. L’alerte précise de quel silence il s’agit : des exécutions enregistrées mais aucune réussie, ou aucune exécution enregistrée du tout. Elle se déclenche une fois par incident. Un marqueur dans Redis la maintient fermée jusqu’à la première exécution réussie, puis s’efface, de sorte que l’incident suivant déclenche à nouveau une alerte.

Les deux veilleurs sont arrivés dans un même commit, après coup. Le commentaire du disjoncteur indique qu’il aurait arrêté le premier incident à la cinquième exécution. Celui du dispositif d’homme mort indique qu’une synchronisation activée il y a longtemps avec zéro exécution doit déclencher une alerte. Aucun des deux n’existait quand on en avait besoin. Nous les avons écrits après coup, avec les deux incidents consignés dans l’en-tête du fichier pour que personne n’oublie pourquoi.

Ce qui est construit, et ce qui ne l’est pas

Construit, à l’heure où nous écrivons :

  • Le curseur est enregistré à la fin de chaque lot sans erreur, et à nouveau lors d’un crash.
  • La clé de livraison de chaque enregistrement est dérivée du curseur de page et de sa position, et vérifiée dans un registre de sept jours avant chaque appel à la destination.
  • Un seul bail par synchronisation, avec une durée de vie de quinze minutes.
  • Cinq tentatives par exécution, avec un délai d’attente croissant à partir de soixante secondes.
  • Un disjoncteur qui met la synchronisation en pause après cinq échecs consécutifs et enregistre pourquoi.
  • Une alerte horaire après vingt-quatre heures sans réussite.

Pas construit, et à savoir avant de vous y fier :

  • Le dispositif d’homme mort surveille l’espace de travail, pas la synchronisation. Une synchronisation saine masque une voisine silencieuse dans le même espace de travail.
  • Son alerte parvient aux opérateurs de Tajo : toujours une ligne de log, et Sentry, un webhook ou un e-mail lorsqu’ils sont configurés. Elle n’apparaît pas sur la page de la synchronisation. La pause du disjoncteur, si.
  • Rien ne redémarre une synchronisation en pause. Une personne le fait.
  • La reprise après un arrêt brutal n’est pas rapide. Le bail peut tenir quinze minutes et le balayage attend une heure.
  • Les deux seuils, cinq échecs et vingt-quatre heures, sont des réglages de déploiement. Vous ne pouvez pas les définir par synchronisation.

Une longue chaîne d’étapes presque parfaites échoue quand même ; la solution consiste à définir l’état final et à le vérifier. Cet article en est la moitié opérationnelle. Définissez ce qu’une exécution morte laisse derrière elle, et vérifiez le néant que laisse le silence.

La phrase de conclusion de Crusoe était que « les pannes sont inévitables ». La nôtre va un pas plus loin. Le crash était la moitié facile, parce qu’il laissait des lignes. Le silence était la moitié que nous avons dû aller chercher.

Questions fréquemment posées

Que devient une synchronisation en cours quand le worker meurt au milieu d’un lot ?

Le curseur pointe toujours sur le début du lot interrompu, car Tajo ne le déplace qu’une fois un lot arrivé sans erreur. L’exécution suivante relit la même page. Chaque enregistrement porte une clé de livraison dérivée du curseur de page et de sa position, et un registre conserve chaque écriture terminée pendant sept jours : les enregistrements déjà écrits sont donc rejoués depuis le registre au lieu d’être envoyés une seconde fois à la destination.

Pourquoi une synchronisation rejouée n’écrit-elle pas deux fois le même contact ?

Avant chaque appel à la destination, l’exécution vérifie la clé de livraison de l’enregistrement dans un registre. La clé est un hachage de la synchronisation, de la source, du curseur de page, de la position sur la page et de l’enregistrement lui-même : un crash, une nouvelle tentative une minute plus tard et une exécution planifiée le lendemain dérivent donc la même clé pour le même enregistrement. Une clé déjà marquée comme terminée dans la fenêtre de sept jours n’est pas renvoyée.

Qu’est-ce qu’une alerte « homme mort » pour une synchronisation ?

Une vérification qui se déclenche sur une absence plutôt que sur un échec. Une fois par heure, Tajo examine chaque espace de travail doté d’une synchronisation active et alerte lorsqu’aucune exécution n’a réussi au cours des vingt-quatre dernières heures, y compris lorsqu’aucune exécution n’a été enregistrée du tout. Elle se déclenche une fois par incident et se réarme après la première exécution réussie. Elle existe parce qu’une alerte d’échec ne peut voir que les échecs qui ont été enregistrés.

Demandez un accès anticipé

Indiquez votre prénom et votre adresse e-mail. Nous vous enverrons un lien pour confirmer votre demande d’accès à Tajo.

Obtenir Brevo

Cette page en Markdown: worker-died-sync-didnt.md

Chargement du Markdown…