Aller au contenu principal

Durabilité et fiabilité des Workflows

Pourquoi la durabilité change tout

Dans un script Python classique, si votre processus plante à l’étape 7 d’un pipeline de 10 étapes, vous devez tout relancer depuis le début. Avec un workflow durable, le système reprend exactement à l’étape 7 — sans refaire les 6 premières, sans dupliquer d’actions, sans perdre de données.

Cette propriété fondamentale est rendue possible par un mécanisme appelé event sourcing.

Event sourcing : le principe

L’event sourcing consiste à enregistrer chaque action du workflow comme un événement immuable dans un journal (log). Au lieu de stocker uniquement l’état actuel du workflow, la plateforme conserve l’historique complet de toutes les opérations.

Voici ce qui se passe concrètement lors de l’exécution d’un workflow :

  1. Le workflow démarre et reçoit ses paramètres d’entrée → événement enregistré
  2. Le workflow appelle l’activité A → événement enregistré
  3. L’activité A termine avec un résultat → événement enregistré
  4. Le workflow appelle l’activité B → événement enregistré
  5. L’activité B termine → événement enregistré
  6. Le workflow retourne son résultat final → événement enregistré

Chaque étape est persistée avant de passer à la suivante. Ce journal d’événements constitue la source de vérité absolue.

Replay automatique : la reprise sur panne

Lorsqu’un worker tombe en panne (crash, redémarrage, coupure réseau), voici ce qui se produit :

  1. Un nouveau worker prend le relais
  2. Il récupère le journal d’événements du workflow
  3. Il rejoue (replay) le code du workflow depuis le début
  4. Pour chaque activité déjà terminée, il utilise le résultat enregistré au lieu de la réexécuter
  5. Le workflow reprend à la première activité non terminée

C’est pour cette raison que les activités déjà réussies ne sont jamais réexécutées lors d’un replay. Seul le résultat stocké dans le journal est réutilisé.

import mistralai.workflows as workflows

@workflows.activity()
async def envoyer_email(destinataire: str, contenu: str) -> dict:
    """Cette activité ne sera exécutée qu'une seule fois,
    même si le workflow est rejoué plusieurs fois."""
    response = await service_email.envoyer(destinataire, contenu)
    return {"status": "envoyé", "id": response.id}

@workflows.workflow.define(name="notification_workflow")
class NotificationWorkflow:
    @workflows.workflow.entrypoint
    async def run(self, destinataire: str) -> dict:
        # Si le workflow plante APRÈS l'envoi de l'email,
        # l'email ne sera PAS renvoyé lors du replay.
        resultat_email = await envoyer_email(destinataire, "Votre rapport est prêt")

        # Le workflow reprendra ici
        resultat_log = await enregistrer_notification(destinataire, resultat_email)
        return resultat_log

Tolérance aux pannes : les scénarios couverts

La plateforme Workflows gère automatiquement plusieurs types de pannes :

Crash du worker

Le processus Python qui exécute votre workflow s’arrête brutalement (erreur mémoire, kill, redémarrage serveur). Un autre worker reprend le workflow là où il en était.

Erreur transitoire réseau

Un appel API échoue à cause d’un timeout réseau. La politique de retry relance automatiquement l’activité avec un backoff exponentiel.

Erreur dans une activité

Votre code lève une exception. Selon la politique de retry configurée, l’activité est relancée un nombre défini de fois avant de considérer l’échec comme définitif.

Indisponibilité temporaire d’un service

Un service externe est down pendant 10 minutes. Les activités en attente sont automatiquement relancées lorsque le service redevient disponible.

La contrainte de déterminisme

Pour que le replay fonctionne, le code du workflow (pas des activités) doit être déterministe. Cela signifie que, étant donné les mêmes entrées, le workflow doit produire la même séquence d’opérations.

Concrètement, dans le code d’un workflow, vous ne devez jamais :

  • Utiliser datetime.now() → utilisez workflow.now()
  • Utiliser uuid.uuid4() → utilisez workflow.uuid4()
  • Utiliser random.random() → utilisez workflow.random()
  • Faire des appels réseau ou I/O directement → déplacez-les dans une activité

Cette contrainte sera détaillée dans la leçon 8 consacrée au déterminisme.

Comparaison avec les approches classiques

ApprocheReprise sur panneGarantie d’exécutionComplexité
Script Python simpleNonAucuneFaible
File de messages (Celery, RQ)PartielleAt-least-onceMoyenne
Cron + base de donnéesManuelleDépend de l’implémentationÉlevée
Workflows Mistral (Temporal)AutomatiqueExactly-once (logique)Faible (SDK)

Exactly-once : une garantie logique

La plateforme garantit que la logique de votre workflow s’exécute exactement une fois. Si une activité réussit, son résultat est enregistré et ne sera jamais réexécuté. Si elle échoue, elle est retentée selon la politique configurée.

Attention cependant : au niveau réseau, un appel peut être envoyé deux fois (si l’accusé de réception est perdu). C’est pourquoi vos activités doivent être idempotentes — produire le même résultat si elles sont appelées plusieurs fois avec les mêmes paramètres.

Points clés à retenir

  • L’event sourcing enregistre chaque étape comme un événement immuable dans un journal
  • Le replay automatique permet de reprendre un workflow interrompu sans réexécuter les activités déjà terminées
  • La plateforme gère automatiquement les crashs, erreurs réseau et indisponibilités de services
  • Le code du workflow doit être déterministe pour que le replay fonctionne
  • Les activités doivent être idempotentes pour garantir la cohérence en cas de retry
  • La garantie d’exécution est exactly-once au niveau logique