Payez la concurrence provisionnée quand un parcours synchrone, face utilisateur, a un budget de latence que les cold starts font exploser, et uniquement sur les heures où ce parcours travaille vraiment. Partout ailleurs, un pool inactif est de l'argent dépensé pour rien. La concurrence provisionnée garde des environnements d'exécution déjà initialisés avant l'arrivée des requêtes ; elle n'accélère pas une invocation déjà chaude, et elle n'apporte rien à un travail qui vit derrière une file.
Qu'est-ce que la concurrence provisionnée Lambda, exactement ?
La concurrence provisionnée, ou provisioned concurrency, demande à Lambda de maintenir un nombre fixe d'environnements d'exécution entièrement initialisés et en attente. Le runtime a démarré, votre code de premier niveau a tourné, les clients SDK sont construits, les pools de connexions sont ouverts, tout cela avant qu'une seule requête n'arrive. Quand une requête se présente, Lambda la route vers l'un de ces environnements et le handler démarre aussitôt : la phase d'init ne touche jamais le chemin de la requête.
Deux points de configuration décident si le mécanisme fonctionne. Il s'attache à une version publiée ou à un alias, jamais à $LATEST : une fonction sans versioning ne peut pas l'utiliser, et vos déploiements doivent déplacer l'alias. Et les environnements naissent au moment où vous posez le réglage, pas quand le trafic arrive, ce qui explique que la facture démarre dès leur existence.
Concurrence provisionnée ou concurrence réservée : quelle différence ?
Les deux noms se ressemblent, les deux réglages font l'inverse l'un de l'autre. La concurrence réservée est un plafond : elle découpe une part de la limite de concurrence du compte, la garantit à une fonction, et déclenche du throttling sur cette fonction une fois la part pleine. Elle ne crée strictement aucun environnement chaud. C'est un contrôle de rayon d'impact, là pour qu'une fonction emballée ne puisse pas affamer le reste du compte.
La concurrence provisionnée est un pool d'environnements prêts, donc un contrôle de latence. Les deux se combinent : la réservée plafonne la fonction, la provisionnée en préchauffe une partie. Régler la réservée en dessous du compte provisionné est une erreur de configuration, pas une économie. Une phrase à retenir : la réservée décide combien une fonction a le droit de tourner, la provisionnée décide combien est prêt à l'avance.
Que corrige la concurrence provisionnée, et que ne corrige-t-elle pas ?
Elle corrige une chose, précisément : l'init qui atterrit sur la requête d'un utilisateur. Sur une connexion, un paiement, une recherche instantanée, le p99 est presque toujours un cold start payé par quelques malchanceux, et la concurrence provisionnée le supprime jusqu'au nombre d'environnements que vous financez. C'est tout le bénéfice.
Ce qu'elle ne fait pas, c'est accélérer les invocations déjà chaudes. Un environnement chaud se comporte de la même façon dans les deux cas : le handler exécute le même code, interroge la même base, attend le même partenaire tiers. Si le p50 est lent, la concurrence provisionnée n'y change rien et le problème est dans le handler. Elle ne raccourcit pas non plus la durée d'init : elle la déplace hors du chemin de la requête, donc un bundle lourd reste lourd et chaque montée en charge le repaie. Nous détaillons l'étape de mesure dans notre article sur les warm starts Lambda, et le travail sur le bundle dans notre guide de réduction des cold starts.
Que se passe-t-il quand le trafic dépasse le pool provisionné ?
Rien ne casse : le trafic déborde. Les requêtes au-delà du nombre provisionné sont servies par des environnements à la demande classiques, qui font un cold start exactement comme si la fonctionnalité était désactivée. Pas de throttling, pas d'erreur, juste une requête plus lente : un pool sous-dimensionné se dégrade en silence au lieu d'échouer bruyamment.
D'où la nécessité d'une métrique plutôt que d'une intuition. Lambda publie ProvisionedConcurrencyUtilization et ProvisionedConcurrencySpilloverInvocations, décrites dans la documentation des métriques Lambda. Une utilisation collée au plafond avec un débordement continu signale un pool trop petit. Une utilisation basse toute la journée signale que vous payez des environnements que personne n'utilise. Posez une alarme des deux côtés : un seul de ces deux problèmes se voit dans un dashboard de latence.
La concurrence provisionnée serait-elle rentable sur votre charge ? Décrivez votre courbe de trafic : diagnostic d'une page sous 48 h.
Recevoir mon diagnostic →Comment arrêter de payer un pool inactif ?
D'abord, comprendre ce que vous achetez. La concurrence provisionnée facture chaque environnement réservé pendant toute la durée où il est provisionné, qu'il serve du trafic ou non, plus un tarif de durée réduit sur les requêtes qu'il traite réellement. Les tarifs du moment vivent sur la page de tarification Lambda. La conséquence est brutale : un pool laissé à sa taille de journée pendant la nuit est du gaspillage pur, et un pool dimensionné pour la haute saison en pleine basse saison est le même gaspillage avec une meilleure justification.
Ensuite, confier le nombre à Application Auto Scaling. Deux mécanismes existent, et la plupart des mises en production utilisent les deux. Les actions planifiées déplacent le plancher sur un calendrier : on monte avant l'ouverture, on redescend la nuit et le week-end, quand la courbe est prévisible. Le target tracking suit l'utilisation : il ajoute des environnements quand le pool se remplit et les retire quand il se vide, ce qui rattrape les jours où le calendrier se trompe.
// Concurrence provisionnée sur l'alias, jamais sur $LATEST
await lambda.send(new PutProvisionedConcurrencyConfigCommand({
FunctionName: "checkout",
Qualifier: "live",
ProvisionedConcurrentExecutions: 2,
}));
// Puis Application Auto Scaling pilote le nombre, cible 70 % d'utilisation
const target = {
ServiceNamespace: "lambda",
ResourceId: "function:checkout:live",
ScalableDimension: "lambda:function:ProvisionedConcurrency",
};
await appAutoScaling.send(new RegisterScalableTargetCommand({
...target,
MinCapacity: 2,
MaxCapacity: 40,
}));
await appAutoScaling.send(new PutScalingPolicyCommand({
...target,
PolicyName: "pc-utilisation",
PolicyType: "TargetTrackingScaling",
TargetTrackingScalingPolicyConfiguration: {
TargetValue: 0.7,
PredefinedMetricSpecification: {
PredefinedMetricType: "LambdaProvisionedConcurrencyUtilization",
},
},
}));
Une nuance surprend souvent. Le target tracking réagit à un trafic déjà arrivé, et les environnements fraîchement ajoutés doivent encore s'initialiser avant de servir quoi que ce soit : un pic vertical prend la policy de vitesse. Quand le pic est au calendrier, une vente flash, un cycle de paie, un passage télé, inscrivez-le dans une action planifiée au lieu d'espérer que la policy suive.
Concurrence provisionnée, réservée, SnapStart ou rien : le comparatif
Quatre options, un seul axe : ce que chacune fait à la phase d'init. La provisionnée sort l'init du chemin de la requête et la facture. SnapStart rejoue un instantané au lieu de dérouler l'init, sur les runtimes compatibles. La réservée ne touche pas à l'init du tout, et figure dans ce tableau uniquement parce qu'on la confond sans arrêt avec un pool chaud. Ne rien faire reste une réponse légitime pour la plupart des fonctions.
| Critère | Concurrence provisionnée | Concurrence réservée | SnapStart | Rien |
|---|---|---|---|---|
| Ce que ça fait | Maintient N environnements initialisés prêts | Plafonne la concurrence utilisable par une fonction | Restaure un instantané initialisé au lieu de dérouler l'init | Environnements à la demande, init à la première requête |
| Effet sur les cold starts | Supprimés jusqu'au nombre provisionné | Aucun, ce n'est pas un pool chaud | Temps d'init fortement réduit sur les runtimes compatibles | Init complet à chaque montée en charge |
| Runtimes | Tous | Tous | Java, Python et .NET ; vérifier la documentation pour le reste | Tous |
| Forme de coût | Tarif horaire par environnement réservé, invoqué ou non, plus un tarif de durée réduit | Gratuit, cela redistribue seulement la limite du compte | Sans surcoût sur Java ; frais de cache et de restauration sur Python et .NET | Facturation standard à l'invocation et à la durée |
| Contrainte principale | Versions et alias uniquement ; un pool inactif est du gaspillage pur | Throttle la fonction une fois la part pleine | Versions publiées uniquement ; exclusif avec la concurrence provisionnée | Le p99 porte l'init |
| Quand ça gagne | Parcours synchrones face utilisateur avec un vrai budget de latence | Protéger le compte d'une fonction bruyante | Runtimes longs à initialiser quand un pool horaire ne se justifie pas | Asynchrone, batch, cron, tout ce qui vit derrière une file |
Sur Java, Python et .NET, chiffrez SnapStart avant de vous engager sur un pool horaire : les deux sont mutuellement exclusifs sur une même fonction, c'est donc un choix, pas un empilement.
Quand ne faut-il pas payer de concurrence provisionnée ?
Dès que personne n'attend. Consommateurs de file, handlers de streams S3 et DynamoDB, jobs cron EventBridge, exports nocturnes, traitements de webhooks qui répondent 202 et finissent plus tard : tous absorbent un cold start sans qu'un seul utilisateur le remarque. Payer un tarif horaire pour économiser l'init d'un batch reste la façon la plus courante de voir une facture Lambda grossir sans que rien n'aille plus vite.
Trois autres cas où l'outil est le mauvais. Un trafic régulier et assez dense garde déjà les environnements chauds tout seul : il reste peu de cold starts à racheter. Un trafic réellement imprévisible, avec de longues plages calmes et des pics rares, impose un choix perdant : un pool dimensionné pour le pic tourne à vide la plupart du temps, un pool dimensionné pour la moyenne déborde quand même, et un plancher planifié n'aide pas quand le calendrier ne veut rien dire. Enfin, une fonction dont l'init traîne à cause d'un bundle obèse ou d'un framework lourd doit être corrigée, pas subventionnée. Si l'arbitrage de fond porte sur Lambda contre un conteneur qui tourne en continu, c'est une autre question, et nos guides de décision AWS la reprennent depuis le début.
Check-list de décision
- Parcours synchrone, face utilisateur, avec un budget de latence que le p99 rate : la concurrence provisionnée se justifie.
- Asynchrone, batch, cron, ou tout ce qui vit derrière une file : ne l'achetez pas, à aucun prix.
- Mesurez d'abord la durée d'init et la fréquence des cold starts, et allégez le bundle avant de payer quoi que ce soit.
- Configurez sur un alias, jamais sur
$LATEST, et déployez à travers cet alias. - Confiez le nombre à Application Auto Scaling : un plancher planifié pour la courbe connue, du target tracking pour le reste.
- Alarmez sur l'utilisation et sur le débordement, pour qu'un pool surdimensionné se voie autant qu'un pool sous-dimensionné.
- Sur Java, Python et .NET, évaluez SnapStart avant de vous engager sur un pool horaire.
- Revérifiez la page de tarification officielle avant tout chiffrage : tarifs et franchises évoluent.