Aller au contenu

Lambda « Task timed out » : diagnostiquer où le temps est passé

L'erreur Lambda « Task timed out after X seconds » signifie que la fonction a atteint son délai configuré, pas qu'elle a planté. Voici comment trouver où le temps est réellement passé et corriger la bonne chose.

Task timed out after 3.00 seconds n’est pas un plantage — c’est Lambda qui applique le délai d’exécution configuré sur la fonction, et le défaut est un très court 3 secondes. Le correctif n’est presque jamais « monter le délai à 900 et passer à autre chose ». Le correctif, c’est de trouver où le temps est passé : un appel en aval qui reste suspendu, une configuration VPC qui avale le trafic sortant, une initialisation lourde dans le handler, ou une fonction réellement affamée en CPU à son réglage de mémoire actuel. Ne montez le délai qu’une fois que vous savez lequel c’est.

Ce qui se passe

Chaque fonction Lambda a un délai d’exécution entre 1 seconde et 900 secondes (15 minutes). Quand le handler tourne encore à l’échéance, Lambda tue l’environnement d’exécution en plein vol et écrit Task timed out after N seconds dans le flux de journaux. Aucun code de nettoyage ne s’exécute, aucune exception n’est attrapable, et pour les invocations asynchrones, Lambda va réessayer l’événement jusqu’à deux fois de plus — une fonction qui expire fait donc souvent ses dégâts deux ou trois fois.

Le temps part habituellement à l’un de quatre endroits. Un, une dépendance en aval : un appel HTTP, une requête RDS ou un appel SDK sans délai côté client attendra volontiers plus longtemps que la fonction n’a le droit de vivre. Deux, le réseau : une fonction attachée à un VPC sans chemin de sortie fonctionnel ne reçoit pas de connexion refusée — les paquets partent et rien ne revient, alors le socket attend jusqu’à ce que Lambda coupe le courant. Trois, l’initialisation : charger un framework lourd ou aller chercher la configuration dans le handler plutôt que dans la phase d’init ajoute des secondes à chaque invocation. Quatre, le calcul lui-même : Lambda alloue le CPU en proportion de la mémoire, donc une fonction à 128 Mo reçoit une petite tranche de vCPU et le travail intensif en CPU rampe.

Une frontière compte même si la fonction elle-même va bien : le délai d’intégration par défaut d’API Gateway est de 29 secondes. Une fonction synchrone derrière API Gateway qui roule 40 secondes retourne un 504 au client même si Lambda est contente — les deux délais doivent être conçus ensemble.

Quoi vérifier en premier

  • Le délai configuré. S’il est encore au défaut de 3 secondes, personne ne l’a jamais dimensionné — ça explique à soi seul des expirations intermittentes sur démarrages à froid ou dépendances lentes.
  • La ligne REPORT. Chaque invocation se termine par REPORT RequestId: ... Duration: ... Billed Duration: ... Max Memory Used: .... Les expirations montrent une Duration presque égale au délai, ce qui vous dit que la fonction a manqué de piste plutôt que fini lentement.
  • Si la fonction est dans un VPC. VPC plus expiration plus « ça marchait avant qu’on l’attache au VPC », c’est un problème de sortie réseau jusqu’à preuve du contraire.
  • Si ça n’arrive que sur les démarrages à froid. Cherchez un champ Init Duration sur les invocations en échec — des expirations seulement quand l’init a roulé pointent vers du code de démarrage coûteux.
  • La santé des dépendances. Si les expirations ont commencé à 14 h 02 et que votre base de données ou une API tierce a eu des misères à 14 h 02, la fonction est la victime, pas la coupable.

Comment vérifier

Obtenez la configuration actuelle :

aws lambda get-function-configuration \
  --function-name my-function \
  --query '{Timeout: Timeout, Memory: MemorySize, VPC: VpcConfig.VpcId}'

Dans la console, Lambda > Functions > (fonction) > Configuration > General configuration montre le délai et la mémoire; l’onglet Monitor montre Duration et Errors ensemble, et les expirations apparaissent comme des erreurs.

Dans CloudWatch Logs Insights, contre le groupe de journaux de la fonction :

filter @message like /Task timed out/
| stats count() by bin(15m)

Puis regardez la forme des durées pour voir si toute la population est lente ou seulement une queue :

filter @type = "REPORT"
| stats avg(@duration), pct(@duration, 95), max(@duration), max(@initDuration) by bin(15m)

Si le p95 se tient à 800 ms et que seul le max atteint le délai, vous avez une dépendance suspendue dans la queue. Si toute la distribution a dérivé vers le haut, la charge de travail a changé. Les détails du cycle de vie et des champs REPORT sont dans la documentation Lambda à https://docs.aws.amazon.com/lambda/.

Le correctif

  1. Mettez des délais côté client sur chaque appel en aval. La vraie cause la plus fréquente. Configurez le client HTTP et le pilote de base de données avec des délais de connexion et de lecture dont la somme reste confortablement sous le délai de la fonction, pour que la fonction échoue vite avec un vrai message d’erreur au lieu de mourir en silence à l’échéance.
  2. Corrigez la sortie VPC si la fonction est attachée à un VPC. Une fonction Lambda a besoin d’un sous-réseau privé dont la table de routage pointe vers une passerelle NAT pour atteindre Internet, ou de points de terminaison VPC pour les services AWS — un sous-réseau public ne fonctionne pas parce que l’ENI de la fonction ne reçoit jamais d’IP publique. Les compromis sont couverts dans Passerelle NAT ou instance NAT.
  3. Déplacez le travail unique dans la phase d’init. Créez les clients SDK, les objets de connexion à la base et les lectures de configuration hors du handler pour qu’ils s’exécutent une fois par environnement d’exécution plutôt qu’une fois par invocation.
  4. Montez la mémoire si le travail est limité par le CPU. Plus de mémoire signifie proportionnellement plus de CPU. Une fonction qui fait de l’analyse JSON, de la compression ou de la crypto à 128 Mo est souvent à la fois plus rapide et moins chère à 512 Mo ou 1024 Mo parce que la durée facturée baisse.
  5. Ensuite, et seulement ensuite, fixez un délai délibéré. Visez environ 2-3x la durée p99 observée. Pour les API synchrones derrière API Gateway, gardez le délai de la fonction sous la limite d’intégration de 29 secondes, sauf si vous avez expressément fait monter ce quota.
  6. Restructurez le travail qui a légitimement besoin de plus de 15 minutes. Découpez-le derrière SQS, ou orchestrez-le avec Step Functions — une invocation Lambda unique est la mauvaise unité pour les longues jobs.

Quand ce n’est pas la bonne approche

Si la fonction parle à RDS et que les expirations arrivent en grappe avec des erreurs de connexion, le vrai problème est peut-être l’épuisement des connexions côté base de données — c’est un problème de pooling, et RDS Proxy est le correctif, pas un délai plus long. Et si une job de traitement frôle sans cesse le plafond de 15 minutes, ne la faites pas passer de justesse avec plus de mémoire — déplacez-la vers ECS, Fargate ou Step Functions, où le plafond n’existe pas.

Erreurs fréquentes

  • Mettre le délai à 900 secondes « au cas où » — une invocation suspendue brûle maintenant 15 minutes de durée facturée par tentative et cache la faute sous-jacente.
  • Oublier que les invocations asynchrones réessaient deux fois par défaut, donc une fonction qui expire peut exécuter ses effets de bord trois fois.
  • Blâmer Lambda quand c’est le délai d’intégration de 29 secondes d’API Gateway qui a réellement coupé la requête.
  • Tester seulement avec des invocations chaudes, puis livrer et découvrir que l’init des démarrages à froid pousse le vrai trafic au-delà de l’échéance.

Prévention

  • Alarmez sur les métriques Errors et Duration de la fonction, avec le seuil de durée à ~80 % du délai pour voir la dérive avant vos clients.
  • Journalisez le temps écoulé de chaque appel en aval pour qu’une future expiration vous dise quel saut a mangé le budget.
  • Exigez des délais explicites dans chaque SDK et client HTTP comme règle de revue de code.
  • Relancez une requête de percentiles de durée après chaque changement de dépendance ou de trafic; les expirations sont habituellement une dérive lente, pas une cassure soudaine.

Les expirations sont le symptôme que Lambda vous donne quand une partie du chemin de requête cesse de répondre, ce qui en fait un assez bon système d’alerte précoce si quelqu’un les lit vraiment. Si personne dans votre équipe ne possède vraiment l’environnement AWS de bout en bout, c’est la racine de la plupart de ces problèmes — Stack Harbor opère des comptes AWS au quotidien sous Opérations AWS, et vous pouvez réserver une revue AWS.