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 :
- What is Durable Execution — Temporal, la définition canonique
- The Three Durable Function Forms — Jack Vanlightly, une taxonomie des formes d’exécution durable
- Absurd Workflows — Armin Ronacher, une approche minimaliste avec PostgreSQL
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 :
- Virtualisation de l’exécution — le workflow peut migrer d’un processus à un autre de manière transparente
- Indépendance temporelle — un workflow peut durer de quelques millisecondes à plusieurs années
- Préservation automatique de l’état — les variables locales survivent aux crashs sans code de persistance explicite
- 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é | Stateless | Session | Acteur |
|---|---|---|---|
| Adressable publiquement | Non | Oui | Oui |
| Type d’identité | Exécution | Exécution | Entité |
| Durée de vie | Bornée | Bornée | Non bornée |
| Communication | One-shot | Continue | Continue |
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
| Aspect | Temporal | Inngest | Trigger.dev | Restate | Absurd | Azure Durable |
|---|---|---|---|---|---|---|
| Mécanisme | Event sourcing + replay | Mémoïsation HTTP | Checkpoint-resume | Journaling + replay | Checkpoint Postgres | Event sourcing + replay |
| Infra requise | Cluster + DB | Aucune (managé) | Docker + Postgres | Binaire unique | PostgreSQL | Azure / SQL Server |
| Self-host | Complexe | Limité | Moyen | Très simple | Trivial | Moyen |
| Langages | 7 | 4 | 1-2 | 5 | 3 | 6 |
| Licence | MIT | SDKs OSS | Apache 2.0 | BSL 1.1 | Open source | MIT (SDKs) |
| Cloud managé | Oui | Oui | Oui | Oui | Non | Oui (Azure) |
| Maturité | 9+ ans | ~3 ans | ~2 ans | ~2 ans | ~5 mois | 7+ ans |
| Déterminisme requis | Strict | Steps wrappés | Non | Steps wrappés | Non | Strict |
| Cas d’usage principal | Workflows enterprise complexes | Serverless event-driven | Background jobs TS/IA | Microservices low-latency | Workflows simples self-hosted | Apps 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.