Système de files d'attente avec Claude Code : guide pratique asynchrone
Concevoir producers, workers, retries, DLQ, idempotence et monitoring avec Claude Code.
Quand Claude Code aide à construire une application web, on peut facilement tout mettre dans le handler HTTP. Le formulaire envoie l’email avant de répondre, l’upload d’image crée les miniatures dans la même requête, et le webhook de paiement met à jour la commande, envoie le reçu et écrit dans le CRM. En démonstration, c’est simple. En production, on ajoute les timeouts, les doublons, les redémarrages de déploiement, les limites de fournisseurs et les échecs partiels.
Une file d’attente sépare la requête utilisateur du travail lent ou fragile. Le producer publie un job, le consumer ou worker le prend, lit le message payload, exécute l’effet de bord, confirme le succès, relance les erreurs temporaires et place les échecs répétés dans une dead-letter queue, ou DLQ. Il faut aussi définir le visibility timeout, c’est-à-dire la durée pendant laquelle un job pris par un worker reste caché aux autres workers ; l’idempotence, pour éviter un double effet métier si le même job revient ; le backpressure, pour ralentir l’entrée quand les workers ne suivent plus ; et le monitoring, pour voir si la file se bloque.
Les exemples ci-dessous sont des scripts Node.js sans dépendance. Ils ne demandent ni Redis, ni AWS, ni RabbitMQ. Ils servent à comprendre le contrat opérationnel avant de choisir SQS, RabbitMQ, BullMQ ou un autre broker.
Vue d’ensemble
Une file ne sert pas seulement à “faire en arrière-plan”. Elle découple les systèmes, protège les services externes, contrôle la concurrence, conserve les erreurs et donne aux opérateurs des preuves concrètes.
flowchart LR
A["Producer<br/>API, cron, webhook"] --> B["Queue<br/>message payload"]
B --> C["Consumer<br/>worker process"]
C --> D["External service<br/>mail, image, billing"]
C -- "retryable failure" --> B
C -- "poison message" --> E["DLQ<br/>manual review"]
C --> F["Metrics<br/>logs and alerts"]
| Terme | Sens simple | Décision à prendre |
|---|---|---|
| Producer | Code qui met un travail dans la file | Format du payload, validation, priorité, clé de déduplication |
| Consumer | Worker qui prend et exécute un travail | Concurrence, timeout, gestion d’échec |
| Message payload | Données lues par le worker | IDs, type, version de schéma, pas de secrets |
| Visibility timeout | Durée où le job pris reste invisible | Un peu plus long que le p95 de traitement |
| Retry | Relance d’une erreur temporaire | Nombre maximal, backoff, jitter, raison d’échec |
| DLQ | File pour les jobs qui ne doivent plus être relancés automatiquement | Responsable, alerte, règles de replay |
| Idempotence | Répéter le job sans dupliquer le résultat métier | Clé unique, table de jobs traités |
| Backpressure | Ralentir l’entrée quand la capacité manque | Limite de concurrence, rate limit, seuil de profondeur |
| Monitoring | Preuve que la file est saine ou bloquée | Profondeur, plus vieux job, taux d’échec, nombre DLQ |
Inclure ce vocabulaire dans le prompt Claude Code change la qualité du résultat. On obtient une implémentation relisible, pas seulement un exemple heureux.
Cas d’utilisation
Le premier cas est l’envoi d’emails. Emails de bienvenue, réinitialisation de mot de passe, relances de facture et réponses support ne devraient pas bloquer la réponse HTTP. Pour les détails, consultez l’automatisation email et l’implémentation SendGrid. Le payload doit contenir deliveryId, templateId et userId, pas une clé API ni le corps complet de l’email.
Le deuxième cas est le traitement d’images ou de vidéos. Miniatures, conversion WebP, scan antivirus, sous-titres ou previews peuvent consommer beaucoup de CPU. La file permet de répondre vite et de limiter le nombre de workers actifs. Le piège principal est la concurrence sans borne.
Le troisième cas est le retry de facturation. Un fournisseur de paiement ou un réseau de carte peut échouer temporairement. Une file de retry aide, mais elle doit être finie. Sans idempotence, backoff et DLQ, on risque les doubles paiements ou une surcharge du fournisseur.
Le quatrième cas est l’enrichissement de leads et la génération de rapports. Après un formulaire, on peut enrichir les données d’entreprise, écrire dans le CRM, générer un rapport commercial et notifier Slack. Ce flux s’articule avec l’architecture orientée événements, la journalisation et le monitoring et les bonnes pratiques de sécurité.
Exemple 1 : file en mémoire sans dépendance
Enregistrez ce fichier sous queue-basic-demo.mjs, puis lancez node queue-basic-demo.mjs. Il montre producer, consumer, payload, visibility timeout et backpressure. Ce n’est pas un broker de production, car tout vit en mémoire, mais le cycle de vie est clair.
// queue-basic-demo.mjs
let nextJobId = 1;
class InMemoryQueue {
constructor({ visibilityTimeoutMs = 800, maxInFlight = 2 } = {}) {
this.visibilityTimeoutMs = visibilityTimeoutMs;
this.maxInFlight = maxInFlight;
this.ready = [];
this.inFlight = new Map();
}
enqueue(type, payload) {
const job = {
id: `job-${nextJobId++}`,
type,
payload,
attempts: 0,
visibleAt: 0,
lockedBy: null,
};
this.ready.push(job);
return job.id;
}
receive(workerId) {
this.requeueExpired();
if (this.inFlight.size >= this.maxInFlight) {
return null;
}
const job = this.ready.shift();
if (!job) return null;
job.attempts += 1;
job.lockedBy = workerId;
job.visibleAt = Date.now() + this.visibilityTimeoutMs;
this.inFlight.set(job.id, job);
return {
id: job.id,
type: job.type,
payload: job.payload,
attempts: job.attempts,
};
}
ack(jobId) {
this.inFlight.delete(jobId);
}
requeueExpired(now = Date.now()) {
for (const [jobId, job] of this.inFlight.entries()) {
if (job.visibleAt <= now) {
this.inFlight.delete(jobId);
job.lockedBy = null;
this.ready.push(job);
}
}
}
stats() {
this.requeueExpired();
return {
ready: this.ready.length,
inFlight: this.inFlight.size,
};
}
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function produce(queue) {
queue.enqueue("email.send", {
deliveryId: "mail-1001",
templateId: "welcome",
userId: "user-42",
});
queue.enqueue("image.resize", {
assetId: "asset-9001",
sizes: [320, 768, 1280],
});
queue.enqueue("report.generate", {
reportId: "weekly-2026-06-02",
accountId: "acct-7",
});
}
async function consume(queue, workerId) {
for (let step = 0; step < 8; step += 1) {
const job = queue.receive(workerId);
if (!job) {
console.log(`${workerId}: no job or backpressure`, queue.stats());
await sleep(120);
continue;
}
console.log(`${workerId}: started ${job.id}`, job.payload);
await sleep(job.type === "image.resize" ? 300 : 90);
queue.ack(job.id);
console.log(`${workerId}: acked ${job.id}`, queue.stats());
}
}
async function main() {
const queue = new InMemoryQueue({
visibilityTimeoutMs: 500,
maxInFlight: 2,
});
produce(queue);
await Promise.all([consume(queue, "worker-a"), consume(queue, "worker-b")]);
console.log("final stats", queue.stats());
}
void main();
En production, le tableau ready sera remplacé par SQS, RabbitMQ, Redis ou un service équivalent. Le modèle reste identique : prêt, en cours, confirmé ou remis en file après expiration.
Exemple 2 : garde d’idempotence
La plupart des files fonctionnent en at-least-once delivery. Le même job peut donc revenir. Sans garde, le worker peut envoyer deux emails, facturer deux fois ou créer deux lignes CRM.
// idempotent-worker-demo.mjs
const idempotencyStore = new Map();
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function withIdempotency(key, work) {
const current = idempotencyStore.get(key);
if (current?.status === "done") {
return { skipped: true, result: current.result };
}
if (current?.status === "processing") {
return { skipped: true, reason: "already processing" };
}
idempotencyStore.set(key, { status: "processing" });
try {
const result = await work();
idempotencyStore.set(key, { status: "done", result });
return { skipped: false, result };
} catch (error) {
idempotencyStore.delete(key);
throw error;
}
}
async function fakeSendEmail(payload) {
await sleep(50);
return {
providerMessageId: `sg_${payload.deliveryId}`,
sentToUserId: payload.userId,
};
}
async function handleEmailJob(job) {
const key = job.payload.idempotencyKey;
if (!key) throw new Error("missing idempotencyKey");
return withIdempotency(key, () => fakeSendEmail(job.payload));
}
async function main() {
const original = {
id: "job-1",
payload: {
idempotencyKey: "email:welcome:user-42",
deliveryId: "mail-1001",
userId: "user-42",
},
};
console.log(await handleEmailJob(original));
console.log(await handleEmailJob({ ...original, id: "job-1-redelivery" }));
}
void main();
En production, remplacez Map par une contrainte unique en base, Redis SETNX ou une clé d’idempotence du fournisseur. Demandez à Claude Code de marquer le job terminé seulement après l’effet externe, de libérer le lock en cas d’erreur et de ne jamais stocker les secrets dans le payload.
Exemple 3 : retry et DLQ
Le retry corrige les erreurs temporaires. Il ne corrige pas un payload invalide, un utilisateur supprimé, un droit manquant ou une configuration absente. Un poison message doit sortir de la file principale.
// retry-dlq-demo.mjs
let nextRetryJobId = 1;
class RetryQueue {
constructor({ maxAttempts = 3 } = {}) {
this.maxAttempts = maxAttempts;
this.ready = [];
this.delayed = [];
this.dead = [];
this.completed = [];
}
enqueue(payload) {
this.ready.push({
id: `retry-job-${nextRetryJobId++}`,
payload,
attempts: 0,
runAt: Date.now(),
lastError: null,
});
}
moveReadyJobs(now = Date.now()) {
const stillDelayed = [];
for (const job of this.delayed) {
if (job.runAt <= now) {
this.ready.push(job);
} else {
stillDelayed.push(job);
}
}
this.delayed = stillDelayed;
}
retryOrDeadLetter(job, error) {
job.lastError = error.message;
if (job.attempts >= this.maxAttempts) {
this.dead.push(job);
return;
}
const delayMs = 50 * 2 ** (job.attempts - 1);
job.runAt = Date.now() + delayMs;
this.delayed.push(job);
}
async drain(handler) {
let idleRounds = 0;
while (this.ready.length > 0 || this.delayed.length > 0) {
this.moveReadyJobs();
const job = this.ready.shift();
if (!job) {
idleRounds += 1;
if (idleRounds > 100) throw new Error("drain timeout");
await sleep(20);
continue;
}
idleRounds = 0;
job.attempts += 1;
try {
const result = await handler(job);
this.completed.push({ id: job.id, result });
} catch (error) {
this.retryOrDeadLetter(job, error);
}
}
return {
completed: this.completed.length,
dead: this.dead.length,
};
}
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function handler(job) {
if (job.payload.kind === "poison") {
throw new Error("invalid payload schema");
}
if (job.payload.kind === "flaky" && job.attempts < 2) {
throw new Error("temporary provider timeout");
}
return `processed ${job.payload.kind}`;
}
async function main() {
const queue = new RetryQueue({ maxAttempts: 3 });
queue.enqueue({ kind: "normal" });
queue.enqueue({ kind: "flaky" });
queue.enqueue({ kind: "poison" });
console.log(await queue.drain(handler));
console.log(
"dead letters",
queue.dead.map((job) => ({
id: job.id,
attempts: job.attempts,
lastError: job.lastError,
payload: job.payload,
}))
);
}
void main();
Une DLQ doit avoir un propriétaire. Il faut une alerte, la raison de l’échec, une procédure de correction et une règle de replay. Sinon, elle devient une perte de données silencieuse.
Checklist opérationnelle
- Le payload contient
jobId,type,schemaVersion, un ID métier et une clé d’idempotence. - Le payload ne contient pas de clé API, token OAuth, données de carte, corps complet d’email ou texte personnel long.
- Le producer valide avant d’enfiler le job.
- Le visibility timeout dépasse légèrement le p95 de traitement.
- Retry, backoff, jitter et DLQ sont définis avant la production.
- La concurrence du worker respecte DB, rate limit, CPU et mémoire.
- On surveille profondeur, plus vieux job, actifs, taux d’échec, DLQ et p95.
- Un runbook explique revue, correction, replay et suppression de la DLQ.
- Email, facturation, points et CRM sont conçus pour des livraisons dupliquées.
- La review Claude Code couvre les erreurs, pas seulement le chemin heureux.
Le visibility timeout est un réglage discret mais critique. Trop court, il crée du double traitement. Trop long, il cache les jobs après un crash de worker. Mesurez le p95 réel et découpez les travaux longs.
Prompt pour Claude Code
Donnez le contrat d’échec, pas seulement le nom d’une librairie :
Ajoute une file d’envoi d’email. L’API sauvegarde la demande puis enfile seulement
deliveryIdettemplateId. Le worker utilise une clé d’idempotence pour éviter les doubles envois, relance les erreurs temporaires du provider au maximum 3 fois avec exponential backoff, puis place les échecs répétés dans une table DLQ. Ne mets pas de clés API, corps d’email ou données personnelles dans le payload. Expose queue depth, oldest job age, failure rate et DLQ count. Ajoute des tests pour livraison dupliquée, poison message et visibility timeout.
Avec cette demande, Claude Code produit plus souvent une base que l’on peut relire et opérer.
Documentation officielle
Si votre infrastructure est centrée sur AWS, commencez par Amazon SQS Developer Guide. Pour des topologies de messaging, exchanges, routing ou pub/sub plus fins, lisez RabbitMQ documentation. Pour un service Node.js déjà basé sur Redis avec delayed jobs et repeatable jobs, consultez BullMQ documentation.
Ne choisissez pas l’outil avant le contrat. Payload, idempotence, retry, DLQ, monitoring, permissions, coût et expérience de l’équipe doivent guider le choix.
Pièges fréquents
Le premier piège est le duplicate processing. Une file promet souvent une livraison au moins une fois, pas un effet métier exactement une fois. Le garde-fou doit être au point où l’effet métier se produit.
Le deuxième piège est le poison message. Un schema obsolète ou un utilisateur supprimé ne sera pas corrigé par dix retries. Il faut valider, enregistrer la raison, isoler en DLQ et rejouer seulement après correction.
Le troisième piège est la boucle de retry infinie. Lors d’une panne provider, les retries immédiats augmentent la charge. Utilisez un nombre fini, backoff, jitter et backpressure côté producer.
Le quatrième piège est de mettre des secrets dans le payload. Les files apparaissent dans les logs, DLQ, dashboards et outils de support. Le payload doit contenir des IDs de référence, et le worker doit relire les données sensibles depuis une source autorisée.
Formation et conseil
Les files sont simples à coder mais difficiles à opérer. ClaudeCodeLab peut transformer cette checklist en processus d’équipe : prompts Claude Code, règles CLAUDE.md, schémas de payload, runbook DLQ, métriques et review CI. Pour une équipe, utilisez la formation et le conseil Claude Code. Pour un travail individuel, copiez la checklist dans votre modèle de pull request.
Résumé
Une file de jobs est une infrastructure de production. Elle contrôle le travail lent, isole l’échec, évite les effets doublés, limite la concurrence et laisse des preuves d’exploitation. Quand vous demandez à Claude Code d’implémenter une file, spécifiez producer, consumer, payload, visibility timeout, retry, DLQ, idempotence, backpressure et monitoring dès le premier prompt.
Résultat vérifié par Masa : j’ai exécuté localement les trois exemples Node.js sans service externe et confirmé le flux de base, la protection contre la livraison dupliquée et le passage des poison messages en DLQ. L’exemple d’idempotence est particulièrement utile dans un prompt, car la deuxième livraison du même email réutilise le résultat enregistré au lieu d’envoyer une deuxième fois.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Permission receipt Claude Code : portée, preuves et rollback
Modèle de permission receipt pour Claude Code : actions autorisées, limites d'approbation, commandes de preuve, rollback et CTAs revenus.
Agent Harness securise pour Claude Code et Codex : permissions, verification et rollback
Construisez un Agent Harness pratique pour Claude Code et Codex avec politiques, plan, verification et recuperation.
Sous-agents Claude Code : guide pratique pour déléguer sans perdre le contrôle
Guide pratique des sous-agents Claude Code pour répartir articles et code : règles, prompts, pièges et checklist.