fr

Exécution durable : les frameworks qui rendent vos workflows crash-proof

Guide complet et sourcé de l'exécution durable : concept, mécanismes internes (event sourcing, checkpointing, journaling), et comparatif des frameworks existants (Temporal, Inngest, Trigger.dev, Restate, Absurd, Azure Durable Functions).

L’exécution durable est une idée simple qui résout un problème fondamental : un processus qui plante ne devrait pas perdre son travail. C’est la promesse d’un code qui survit aux crashs, aux redémarrages et aux pannes réseau sans que le développeur n’ait à écrire manuellement la logique de reprise.

Le concept n’est pas nouveau — AWS Simple Workflow Service (SWF) faisait déjà ça en 2012 — mais l’écosystème a explosé ces dernières années. On compte aujourd’hui une dizaine de frameworks qui proposent chacun leur vision de l’exécution durable, avec des compromis très différents en termes d’infrastructure, de complexité et de modèle d’exécution.

Cet article synthétise trois textes fondateurs sur le sujet avant de passer en revue les principales solutions disponibles :


1. Qu’est-ce que l’exécution durable ?

Le problème

Dans une application classique, quand un processus plante, tout son état disparaît : variables locales, position dans le flux d’exécution, résultats intermédiaires. Tout est perdu. Pour y remédier, les développeurs ajoutent manuellement des sauvegardes en base, des queues de messages, des mécanismes de retry, des idempotency keys. Temporal estime que plus des deux tiers du code de production sont dédiés à la gestion des erreurs plutôt qu’à la logique métier.

Source : Temporal — What is Durable Execution

Le problème empire avec les architectures distribuées : chaque appel entre services est un point de défaillance supplémentaire. Un workflow qui enchaîne paiement → réservation → notification → email devient un cauchemar de fiabilité sans outillage dédié.

La solution : virtualiser l’exécution

L’exécution durable abstrait l’exécution du processus physique qui la porte. Comme une machine virtuelle survit à la migration entre hyperviseurs, un workflow durable survit au crash du worker qui l’exécute. Quatre propriétés caractérisent une exécution durable :

  1. Virtualisation de l’exécution — le workflow peut migrer d’un processus à un autre de manière transparente
  2. Indépendance temporelle — un workflow peut durer de quelques millisecondes à plusieurs années
  3. Préservation automatique de l’état — les variables locales survivent aux crashs sans code de persistance explicite
  4. Indépendance matérielle — la fiabilité est logicielle, pas liée au hardware

Le principe fondamental : logique déterministe + effets de bord mémoïsés

Tous les systèmes d’exécution durable reposent sur la même idée : séparer la logique de contrôle (déterministe) des effets de bord (non-déterministes), puis mémoïser les résultats des effets de bord pour pouvoir reconstituer l’état en cas de crash.

┌─────────────────────────────────────────┐
│           Workflow (déterministe)        │
│                                         │
│  1. résultat_a = step("appeler API A")  │
│  2. résultat_b = step("appeler API B")  │
│  3. if résultat_a > 0:                  │
│       step("envoyer email")             │
│                                         │
└─────────┬───────────────────────────────┘


┌─────────────────────────────────────────┐
│         Stockage durable                │
│                                         │
│  step 1 → résultat_a = 42    ✓ mémoïsé │
│  step 2 → résultat_b = "ok"  ✓ mémoïsé │
│  step 3 → (en cours...)      ✗ crash   │
│                                         │
└─────────────────────────────────────────┘

Après un crash, le système reprend l’exécution : les steps 1 et 2 retournent instantanément leurs résultats mémoïsés, et le step 3 est ré-exécuté. L’API appelée en step 1 et 2 n’est jamais rappelée.


2. Les trois formes de fonctions durables

Jack Vanlightly propose une taxonomie utile qui distingue trois formes de fonctions durables, selon leur durée de vie et leur capacité d’interaction.

Source : The Three Durable Function Forms

Fonctions stateless

Un processus one-shot avec un début et une fin. Pas d’identité publique, pas d’interaction externe pendant l’exécution. L’état n’existe que pour la mémoïsation interne.

Exemple : un traitement de paiement qui valide la carte, débite le montant et enregistre la transaction.

Sessions (workflows)

Un processus interactif et long-running avec une identité temporaire (un ID d’exécution). Des parties externes peuvent interagir avec le workflow pendant son exécution via des signaux ou des queries.

Exemple : un workflow de demande de prêt où un agent peut consulter l’avancement et approuver manuellement une étape.

Acteurs

Des entités persistantes avec une identité basée sur l’entité (pas sur l’exécution) et une durée de vie non bornée. Chaque acteur traite ses invocations de façon sérialisée et maintient un état mutable.

Exemple : un panier d’achat qui existe indépendamment des sessions utilisateur et qui persiste entre les visites.

PropriétéStatelessSessionActeur
Adressable publiquementNonOuiOui
Type d’identitéExécutionExécutionEntité
Durée de vieBornéeBornéeNon bornée
CommunicationOne-shotContinueContinue

Tous les frameworks ne supportent pas les trois formes. Cette distinction aide à choisir le bon outil selon le cas d’usage.


3. Les mécanismes internes

Trois approches techniques existent pour implémenter la durabilité. Elles diffèrent par leur stratégie de persistance et de reprise.

Event sourcing + replay déterministe

Utilisé par : Temporal, Azure Durable Functions

Le système enregistre chaque événement (démarrage de step, résultat, timer, signal) dans un journal immuable. En cas de crash, le code du workflow est rejouée depuis le début : le runtime fournit les résultats mémoïsés pour chaque step déjà complété, reconstituant l’état exact au point de défaillance.

Contrainte forte : le code du workflow doit être déterministe. Pas de Math.random(), pas de Date.now(), pas d’I/O direct. Toute opération non-déterministe doit passer par une Activity.

Avantage : reconstitution exacte de l’état complet, y compris les variables locales et la pile d’appels.

Checkpointing

Utilisé par : Absurd, Trigger.dev

Le système stocke le résultat de chaque step complété. En cas de crash, il charge les résultats sauvegardés et reprend à partir du dernier step non complété. Pas de replay du code depuis le début.

Contrainte : plus faible que le replay — le code n’a pas besoin d’être strictement déterministe, mais les steps doivent être idempotents.

Avantage : plus simple à comprendre et à débugger. Pas de “replay surprise”.

Journaling

Utilisé par : Restate

Proche de l’event sourcing mais avec un journal par exécution plutôt qu’un journal centralisé. Le runtime Restate journal chaque opération wrappée dans run(). En cas de crash, il rejoue le journal local.

Avantage : performances élevées (pas de round-trip vers une base centrale à chaque step), fonctionne bien en edge.


4. Les frameworks

Temporal

Source : temporal.io

Le vétéran de l’exécution durable. Créé par les architectes d’AWS SQS, AWS SWF et Uber Cadence. Plus de 9 ans en production chez NVIDIA, Salesforce, Twilio, Descript.

Principe : event sourcing + replay déterministe. Le Temporal Service enregistre chaque événement dans un Event History immuable. Les Workers (hébergés par vous) exécutent le code. Quand un Worker crash, un autre récupère l’historique et rejoue le Workflow.

Architecture :

┌──────────────┐         ┌──────────────────────┐
│   Client     │────────▶│   Temporal Service    │
└──────────────┘         │  ┌─────────────────┐  │
                         │  │ Frontend Service │  │
┌──────────────┐         │  │ History Service  │  │
│   Worker     │◀───────▶│  │ Matching Service │  │
│  (votre code)│         │  └─────────────────┘  │
└──────────────┘         │         │              │
                         │         ▼              │
                         │  ┌─────────────┐       │
                         │  │   Database   │       │
                         │  │ (Postgres /  │       │
                         │  │  MySQL / ES) │       │
                         │  └─────────────┘       │
                         └──────────────────────┘

Infrastructure requise :

  • Self-hosted : cluster Temporal Server + base de données (PostgreSQL 12+, MySQL 8+, ou SQLite pour le dev). Elasticsearch recommandé en production pour la visibilité avancée. Docker Compose pour le dev, Helm charts pour Kubernetes.
  • Temporal Cloud : entièrement managé.

Langages : Go, Java, TypeScript, Python, C#/.NET, PHP, Ruby (7 SDKs).

Tarification : open source (MIT). Cloud à partir de 100$/mois (1M actions). Crédits gratuits : 1 000$ pour les nouveaux utilisateurs, 6 000$ pour les startups.

Forces : maturité, écosystème le plus large, battle-tested à grande échelle.

Faiblesses : complexité d’opération en self-hosted, contrainte de déterminisme stricte, courbe d’apprentissage.


Inngest

Source : inngest.com

Une plateforme event-driven qui orchestre vos fonctions via HTTP. Votre code tourne sur votre propre infra (serverless, serveur, edge) ; Inngest gère la coordination.

Principe : mémoïsation par steps sur HTTP. Chaque appel à step.run() est une requête HTTP séparée. Quand un step complète, son résultat est mémoïsé. Les step.sleep() et step.waitForEvent() ne consomment aucun compute — c’est Inngest qui gère les timers côté serveur.

import { inngest } from "./client";

export default inngest.createFunction(
  { id: "process-order" },
  { event: "order/created" },
  async ({ event, step }) => {
    const payment = await step.run("charge-card", async () => {
      return stripe.charges.create({ amount: event.data.amount });
    });

    await step.sleep("wait-before-email", "1h");

    await step.run("send-confirmation", async () => {
      return sendEmail(event.data.email, payment.id);
    });
  },
);

Infrastructure requise :

  • Inngest Cloud : entièrement managé. Votre code reste sur votre infra, Inngest l’appelle en HTTP.
  • Self-hosted : le Dev Server est disponible pour le développement local. Les SDKs sont open source.
  • Pas de base de données à gérer de votre côté.

Langages : TypeScript, Python, Go, Kotlin (4 SDKs).

Tarification : free tier à 50 000 exécutions/mois. Pro à partir de 75$/mois (1M exécutions).

Forces : event-driven natif, fonctionne avec n’importe quel hébergement (Vercel, Cloudflare, AWS Lambda…), zéro compute pour les sleeps, flow control intégré (concurrence, throttling, batching).

Faiblesses : le serveur central n’est pas trivial à self-hoster, dépendance au service Inngest pour l’orchestration.


Trigger.dev

Source : trigger.dev

Une plateforme spécialisée dans les tâches longues en arrière-plan, avec un focus marqué sur les agents IA et le streaming LLM.

Principe : checkpoint-resume. Les tâches sont définies en TypeScript. Le runtime sauvegarde l’état de la tâche à chaque point de checkpoint. En cas de crash, la tâche reprend du dernier checkpoint sans rejouer depuis le début. Chaque déploiement est versionné : les tâches en cours continuent sur leur version d’origine.

import { task } from "@trigger.dev/sdk/v3";

export const processOrder = task({
  id: "process-order",
  run: async (payload) => {
    const result = await doExpensiveWork(payload);
    // checkpoint automatique ici
    await sendNotification(result);
  },
});

Infrastructure requise :

  • Trigger.dev Cloud : conteneurs managés avec tailles configurables (Micro à Large 2x).
  • Self-hosted : open source (Apache 2.0). Nécessite Docker + PostgreSQL.

Langages : TypeScript/JavaScript (primary), Python via extensions.

Tarification : free tier inclus. Hobby à 10$/mois. Pro à 50$/mois. Facturation au compute + invocations.

Forces : DX TypeScript de premier ordre, hooks React pour le suivi temps réel, streaming LLM natif, déploiements atomiques versionnés.

Faiblesses : écosystème quasi exclusivement TypeScript, plus jeune que les alternatives.


Restate

Source : restate.dev

Un runtime d’exécution durable écrit en Rust, conçu pour la performance et le déploiement léger.

Principe : journaling + replay. Le runtime Restate journalise chaque opération wrappée dans run(). En cas de crash, il rejoue le journal. Le runtime est un binaire unique sans dépendance externe — pas de base de données à opérer séparément. L’état et les journaux sont stockés en interne.

Restate supporte les trois formes de fonctions durables définies par Jack Vanlightly :

  • Services basiques → fonctions stateless
  • Workflow Services → sessions (un handler principal + des handlers secondaires)
  • Virtual Objects → acteurs avec identité persistante par clé
import * as restate from "@restatedev/restate-sdk";

const checkout = restate.service({
  name: "checkout",
  handlers: {
    process: async (ctx: restate.Context, order: Order) => {
      const payment = await ctx.run("charge", () =>
        stripe.charges.create({ amount: order.amount }),
      );

      await ctx.sleep(Duration.ofHours(1));

      await ctx.run("notify", () => sendEmail(order.email, payment.id));
    },
  },
});

Infrastructure requise :

  • Self-hosted : un seul binaire. Pas de base de données externe. Tourne sur Linux/macOS. Se déploie sur Kubernetes, Vercel, Cloudflare Workers, AWS Lambda, Google Cloud Run, Deno Deploy.
  • Restate Cloud : service managé avec SOC 2, SSO entreprise, support HIPAA.

Langages : TypeScript, Python, Java/Kotlin, Go, Rust (5 SDKs).

Tarification : self-hosted gratuit (licence BSL 1.1, convertie en Apache 2.0 après 4 ans). Cloud avec tarification entreprise.

Performances : latence p99 < 100ms pour des workflows à 10 steps, 13 000 workflows/sec sur un cluster 3 nœuds.

Forces : performances exceptionnelles, zéro dépendance externe, binaire unique, traces OpenTelemetry automatiques, support des trois formes (stateless, session, acteur).

Faiblesses : licence BSL 1.1 (interdit de le proposer comme service managé à des tiers), écosystème plus jeune.


Absurd

Source : Absurd Workflows — Armin Ronacher

L’approche la plus minimaliste : l’exécution durable réduite à un fichier SQL appliqué sur PostgreSQL. Créé par Armin Ronacher (créateur de Flask, ingénieur chez Sentry). Open sourcé après 5 mois en production.

Principe : checkpointing dans PostgreSQL. Les workers récupèrent les tâches avec SELECT ... FOR UPDATE SKIP LOCKED. Chaque step complété est sauvegardé en base. En cas de crash, les steps déjà complétés sont chargés depuis la base, et seul le step en cours est ré-exécuté.

await ctx.step("charge-card", async () => {
  return stripe.charges.create({ amount: order.amount });
});

// Les steps auto-numérotés supportent les boucles dynamiques
await ctx.step("iteration", async () => {
  // iteration, iteration#2, iteration#3...
});

// Sleep et events
await ctx.sleep(60 * 60 * 24 * 7); // 1 semaine
await ctx.waitForEvent("approval", { timeout: 300 });

Infrastructure requise :

  • PostgreSQL uniquement. Pas de serveur séparé, pas de message broker, pas de runtime dédié.
  • Optionnel : absurdctl (CLI) et Habitat (dashboard web).
  • Pas d’offre cloud managée.

Langages : TypeScript (~1 400 lignes de SDK), Python (~1 900 lignes), Go (expérimental).

Tarification : open source. Purement self-hosted.

Forces : simplicité radicale, aucune dépendance au-delà de PostgreSQL, codebase minuscule, pas de contrainte de déterminisme stricte, trivial à self-hoster.

Faiblesses : très jeune (~5 mois de production), pas d’offre managée, pas de replay déterministe (donc pas de reconstitution complète de l’état local), limité par les performances de PostgreSQL pour les très hauts débits.


Azure Durable Functions

Source : Azure Durable Functions

La solution Microsoft, disponible en deux formes : Azure Durable Functions (extension serverless) et Durable Task SDK (standalone, tourne n’importe où).

Principe : event sourcing + replay déterministe, le même modèle que Temporal (les fondateurs de Temporal ont travaillé sur les prédécesseurs de ce projet chez Microsoft).

Composants :

  • Orchestrators — logique de workflow, doit être déterministe
  • Activities — unités de travail, pas de contrainte de code
  • Entities — objets stateful avec exécution sérialisée (acteurs)

Infrastructure requise :

  • Durable Task Scheduler (recommandé) : service managé Azure, meilleur débit.
  • Azure Storage : queues + tables + blobs. Zéro config, coût minimal.
  • SQL Server / Azure SQL : on-premises ou cloud, fonctionne en environnement déconnecté.
  • Durable Task SDK : open source (MIT), tourne sur n’importe quel compute hors Azure.

Langages : C#/.NET, JavaScript, TypeScript, Python, PowerShell, Java (6 langages).

Tarification : SDKs open source (MIT). Azure Functions en pay-per-execution. Durable Task Scheduler avec tarification Azure managée.

Forces : intégration profonde avec l’écosystème Azure, multiple backends de stockage (unique parmi les frameworks), support des entités/acteurs, support PowerShell (unique).

Faiblesses : fortement lié à Azure pour l’expérience complète, moins de communauté open source indépendante.


5. Comparatif

AspectTemporalInngestTrigger.devRestateAbsurdAzure Durable
MécanismeEvent sourcing + replayMémoïsation HTTPCheckpoint-resumeJournaling + replayCheckpoint PostgresEvent sourcing + replay
Infra requiseCluster + DBAucune (managé)Docker + PostgresBinaire uniquePostgreSQLAzure / SQL Server
Self-hostComplexeLimitéMoyenTrès simpleTrivialMoyen
Langages741-2536
LicenceMITSDKs OSSApache 2.0BSL 1.1Open sourceMIT (SDKs)
Cloud managéOuiOuiOuiOuiNonOui (Azure)
Maturité9+ ans~3 ans~2 ans~2 ans~5 mois7+ ans
Déterminisme requisStrictSteps wrappésNonSteps wrappésNonStrict
Cas d’usage principalWorkflows enterprise complexesServerless event-drivenBackground jobs TS/IAMicroservices low-latencyWorkflows simples self-hostedApps Azure-native

6. Autres solutions notables

Hatchet

Source : hatchet.run

Plateforme d’orchestration managée pour workloads async et agents IA. Architecture à deux composants : moteur d’orchestration managé + workers sur votre infra. Latence de démarrage < 20ms, sémantiques exactly-once. SDKs Python, TypeScript, Go.

Windmill

Source : windmill.dev

Plateforme open source pour workflows, outils internes et pipelines de données. Se distingue par le support de 20+ langages (Python, TypeScript, Go, PHP, Bash, SQL, REST, GraphQL…) et un app builder low-code intégré. Self-hostable en 3 minutes, scale de un VPS à des clusters Kubernetes de 1 000 nœuds.


7. Comment choisir

Le choix dépend de trois axes principaux :

Axe 1 : infrastructure que vous acceptez d’opérer

  • Vous avez déjà PostgreSQL et voulez rester simple → Absurd
  • Vous ne voulez rien opérer → Inngest Cloud, Trigger.dev Cloud ou Temporal Cloud
  • Vous voulez un binaire unique sans dépendance → Restate
  • Vous êtes sur Azure → Azure Durable Functions

Axe 2 : complexité de vos workflows

  • Workflows simples (queue + retry + steps) → Absurd, Trigger.dev, Inngest
  • Workflows complexes avec signaux, queries, child workflows → Temporal, Restate
  • Entités/acteurs persistants → Temporal, Restate, Azure Durable Functions

Axe 3 : votre stack

  • TypeScript-first → Trigger.dev, Inngest
  • Polyglotte → Temporal (7 SDKs), Azure Durable Functions (6 langages)
  • Performance critique → Restate (Rust, sub-100ms p99)
  • Minimalisme → Absurd (un fichier SQL)

En pratique, Temporal reste la référence pour les cas d’usage enterprise complexes. Mais la tendance est clairement à la simplification : des solutions comme Inngest, Restate et Absurd prouvent qu’on peut obtenir de l’exécution durable avec une fraction de la complexité opérationnelle.