Une équipe SaaS fait tourner en production une application PHP fournisseur derrière un ALB sur trois cibles. La connexion fonctionne. La session expire après un clic — l’utilisateur se connecte, atterrit sur le tableau de bord, clique sur un lien, et est renvoyé à l’écran de connexion. La moitié du temps. La session du fournisseur est en mémoire sur l’hôte PHP, et le round-robin d’ALB place des requêtes successives sur des cibles différentes, dont aucune n’a trace de la session. L’équipe active la persistance ALB, le symptôme disparaît, et l’incident est « fermé ». Six mois plus tard, une cible tourne à 90 % CPU pendant que les deux autres sont à 30 %, parce que les clients de longue date se sont tous collés à la machine qui était saine le jour où ils se sont connectés. La persistance a réglé le problème visible et en a créé un invisible. La bonne réponse a toujours été « rendre la session indépendante de l’hôte » — mais cela requiert que le fournisseur livre un changement de code, et l’équipe d’opérations devait livrer une correction vendredi. Voici quand chaque chemin est le bon choix.
Ce qui se passe
Les sessions persistantes sur ALB lient un client à une cible spécifique pendant une fenêtre de temps. Une fois que le client atterrit sur une cible, les requêtes suivantes du même client vont à la même cible tant que la fenêtre de persistance n’a pas expiré et que la cible est saine.
ALB supporte deux modes de persistance :
-
Basé sur la durée (cookie du load balancer). ALB définit un cookie nommé
AWSALB(etAWSALBCORSpour cross-origin). Le cookie contient un identifiant de cible encodé et une expiration. Chaque requête suivante du même client porte le cookie ; ALB le lit et route vers la même cible. La fenêtre est configurable de 1 seconde à 7 jours. L’application n’a pas besoin de savoir que la persistance existe. -
Basé sur l’application (cookie applicatif). L’application définit un cookie (n’importe quel nom de votre choix). ALB lit le cookie et route selon sa valeur. Cela permet à l’application de décider quels clients rendre persistants et lesquels round-robin — par exemple un cookie « mode édition » qui épingle l’utilisateur à une cible seulement pendant qu’il tient une édition en cours.
Les problèmes que la persistance crée :
-
Cibles chaudes. La persistance dépend du chemin. La première cible sur laquelle un client atterrit reste la cible jusqu’à ce que le cookie expire ou que la cible échoue. Si un déploiement rend une cible saine légèrement avant les autres, chaque client qui se connecte pendant cette fenêtre se colle à elle. Le résultat est un déséquilibre de longue durée — une cible reçoit 60 % du trafic, les autres 20 % chacune, et le déséquilibre ne se guérit pas seul.
-
Échec de la montée en charge. Quand le trafic monte en flèche et que l’autoscaling ajoute deux nouvelles cibles, les nouvelles cibles ne reçoivent du trafic que des clients sans cookie (nouvelles sessions). Les utilisateurs déjà connectés restent sur les anciennes cibles. La montée en charge ne soulage pas la pression existante ; elle gère seulement la nouvelle pression par-dessus.
-
Cascade d’échec de cookie. Si la cible à laquelle un client est collé devient malsaine, ALB bascule le client vers une autre cible. Mais la nouvelle cible n’a pas de session pour ce client. Le client soit se réauthentifie soit reçoit une erreur, selon la logique de l’application. Les utilisateurs touchent cela lors d’un déploiement ou d’un événement d’AZ et appellent ça un produit instable.
-
Le piège de l’épinglage de latence. La persistance vous route vers la même cible peu importe où la cible se trouve dans le cluster. Si la cible est dans une AZ différente de l’entrée réseau du client, vous payez la latence cross-AZ sur chaque requête. Avec round-robin et clients bien distribués, la latence moyenne sur l’ensemble des requêtes est la moyenne équilibrée par AZ ; avec persistance, votre latence est celle de l’AZ de votre cible.
L’architecture cible meilleure est application sans état + magasin de sessions externe. L’application lit et écrit les données de session dans Redis (ElastiCache ou auto-hébergé), avec une clé qui est un identifiant de session dans un cookie que l’application gère elle-même. Toute cible peut servir toute requête. La persistance n’est pas nécessaire ; round-robin est correct.
Quand la persistance est quand même le bon choix :
-
Applications héritées non modifiables. Une application PHP ou Java fournisseur qui stocke la session en mémoire de processus et n’expédie pas de support Redis. La persistance est le correctif opérationnel pendant que vous négociez le changement de code avec le fournisseur (ou que vous remplacez le fournisseur).
-
Connexions WebSocket, par nature. Un WebSocket est une connexion à longue durée ; elle est persistante à une cible pour la vie de la connexion. La persistance ALB pour WebSocket est implicite et correcte.
-
SPA rendus côté serveur avec état en cours par cible. Certaines configurations SSR mettent en cache le rendu en cours sur la cible. Épingler le client à une cible pendant que le SPA se rend évite de re-rendre sur une cible différente. C’est le cas rare où la persistance est la bonne architecture, pas un contournement.
-
Cibles GPU ou spécialisées. Un target group de serveurs d’inférence où chaque cible a un modèle par client préchauffé. La persistance garde le modèle chaud. L’application pourrait régler cela avec un routage explicite, mais la persistance est un raccourci raisonnable.
Comment vérifier
Vérifier si la persistance est activée pour un target group :
aws elbv2 describe-target-group-attributes \
--target-group-arn "$TG_ARN" \
--query 'Attributes[?Key==`stickiness.enabled` || Key==`stickiness.type` || Key==`stickiness.lb_cookie.duration_seconds`]'
Confirmer l’hypothèse de déséquilibre — compte de requêtes par cible :
aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name RequestCountPerTarget \
--dimensions Name=TargetGroup,Value=$TG_NAME Name=LoadBalancer,Value=$LB_NAME \
--start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 60 \
--statistics Average
Pour les target groups persistants, le compte de requêtes par cible devrait être relativement plat sous un trafic stable. Un pic sur une cible sans pic correspondant sur les autres est le pattern de cible chaude.
La vérification plus rapide pour le déséquilibre de cible chaude est de transférer les journaux d’accès du target group à CloudWatch Insights :
fields @timestamp, target_status_code, target_ip
| stats count(*) as request_count by target_ip
| sort request_count desc
Une distribution round-robin saine ressemble à chaque cible dans les 10 % de la médiane. Une distribution persistante déséquilibrée ressemble à une cible à 2-3x la médiane.
Confirmer le coût de latence cross-AZ — TargetResponseTime par cible :
aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name TargetResponseTime \
--dimensions Name=TargetGroup,Value=$TG_NAME Name=LoadBalancer,Value=$LB_NAME Name=AvailabilityZone,Value=us-east-1a \
--start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 60 \
--extended-statistics p95
Comparez à travers les AZ. Si les cibles d’une AZ sont constamment plus lentes pour la même charge, les clients persistants dans cette AZ paient pour ça.
La correction
Le flux de décision :
-
Défaut sur round-robin (pas de persistance). La plupart des applications modernes sans état fonctionnent ainsi et devraient continuer à le faire.
-
Si une session est nécessaire, l’externaliser. Redis (ElastiCache), DynamoDB, ou même une table de base de données. L’appel au magasin de sessions ajoute quelques millisecondes par requête ; c’est un prix juste pour une flotte qui passe à l’échelle horizontalement.
-
Si vous ne pouvez pas externaliser (app fournisseur, déploiement immédiat), activez la persistance — mais plafonnez la durée agressivement. Une fenêtre de persistance de 24 heures garantit un déséquilibre à long terme. Une fenêtre d’une heure laisse les clients se redistribuer à mesure que les cibles tournent. Choisissez la fenêtre la plus courte qui garde l’expérience utilisateur acceptable.
-
Surveiller le déséquilibre. Une alarme CloudWatch sur l’écart-type de
RequestCountPerTarget, ou un rapport quotidien des comptes de requêtes par cible. Le seuil d’alarme est défini empiriquement ; le principe est que vous voulez voir les cibles chaudes, pas les découvrir vendredi. -
Planifier la migration hors persistance comme un vrai projet. Externalisez le magasin de sessions, mettez à jour l’application pour le lire/écrire, déployez avec la persistance encore activée, puis désactivez la persistance et confirmez que le round-robin fonctionne. Ne faites pas cela dans le même changement que la mise à jour de l’application — séparez le déploiement.
Trois règles que nous tenons :
La persistance est opérationnelle, pas architecturale. Un système qui a besoin de la persistance pour fonctionner a un problème d’état une couche plus bas. La persistance achète du temps pour le régler.
Courte durée, cookie applicatif quand possible. Une fenêtre cookie load-balancer d’une heure et votre application contrôle quand une requête est persistante et quand non. La durée par défaut de 7 jours est presque toujours trop longue.
Surveiller la métrique de déséquilibre, pas la latence moyenne. La latence moyenne cache le pattern de cible chaude. La flotte semble saine sur le tableau de bord pendant qu’une cible peine.
Pour plus de détails sur les vérifications de santé (qui déterminent si une cible est dans la rotation au départ), voir Tuning de vérifications de santé pour cibles SaaS. Pour les patterns de règles de listener ALB qui interagissent avec les target groups persistants, voir Règles de listener ALB — routage par hôte et par chemin.
Stack Harbor conçoit les topologies ALB + NLB et le modèle d’opération autour dans le cadre de l’onboarding Gestion AWS et des Environnements infogérés en continu. Si votre flotte a une cible chaude et trois cibles inactives, commencez par une consultation d’infrastructure.