AWS Certified CloudOps Engineer - Associate
Mise en cache avec CloudFront et ElastiCache
Où placer un cache pour qu'il enlève de la charge réelle : la cache key et les règles de TTL qui décident de ce que CloudFront garde à la périphérie, et les stratégies lazy loading, write-through et TTL qui décident de ce qu'ElastiCache garde devant votre base de données.
- Décider si une charge de travail a besoin d'un cache de périphérie, d'un cache de base de données, ou des deux
- Prédire combien de temps CloudFront garde un objet à partir des TTL de la cache policy et des en-têtes Cache-Control de l'origine
- Augmenter un cache hit ratio CloudFront en réduisant la cache key, et choisir entre invalidation et noms de fichiers versionnés
- Comparer lazy loading, write-through et TTL comme stratégies de remplissage d'ElastiCache et nommer la faiblesse propre à chacune
- Lire les métriques CloudWatch d'ElastiCache pour choisir entre monter en gamme, ajouter des réplicas et ajouter des shards
Votre page catalogue exécute une requête qui prend 300 ms et renvoie les mêmes 200 lignes à tous les visiteurs. À 400 requêtes par seconde, la base exécute cette requête 400 fois par seconde et brûle son CPU à produire une réponse qu'elle a déjà produite. La requête n'a aucun défaut. Le problème, c'est que vous recalculez une constante, et le correctif consiste à garder la réponse plus près que la machine qui l'a calculée.
AWS vous propose deux endroits très différents pour la garder, et la compétence 2.1.2 du SOA-C03 nomme les deux : Amazon CloudFront à la périphérie et Amazon ElastiCache à côté de votre application. Ils ne sont pas interchangeables, et se tromper d'endroit revient à ajouter un service qui n'enlève aucune charge.
Deux caches, deux distances
| CloudFront | ElastiCache | |
|---|---|---|
| Où il se trouve | Les edge locations AWS, près du visiteur | Dans votre VPC, près de votre application |
| Ce qu'il stocke | Des réponses HTTP entières, indexées par une cache key | Ce que votre code y met, indexé par une chaîne |
| Qui écrit dedans | CloudFront, automatiquement, sur un miss | Votre code applicatif, explicitement |
| Ce qu'il protège | L'origine et le chemin réseau qui y mène | La base de données |
| Coût d'un miss | Une requête vers l'origine | Une requête en base plus une écriture dans le cache |
| Ce que vous réglez | Cache policies, TTL, cache key | Stratégie, TTL, type de nœud, nombre de shards |
La phrase qui tranche : CloudFront est un cache que vous configurez, ElastiCache est un cache que vous programmez. Si les mêmes octets partent vers de nombreux visiteurs en HTTP, CloudFront absorbe ce trafic sans une ligne de code. Si la chose coûteuse est un résultat de requête, un objet de session ou un classement calculé que seule votre application comprend, aucun cache de périphérie ne peut aider, parce que CloudFront ne voit pas à l'intérieur de la réponse et ne sait pas qu'elle est réutilisable.
Les grands systèmes font tourner les deux. CloudFront prend les réponses publiques répétées, ElastiCache prend les lectures internes répétées, et la base ne voit plus que ce qui varie vraiment.
La cache key CloudFront décide de ce qu'est un « hit »
CloudFront range chaque objet sous une cache key. Une requête est un hit seulement si elle produit la même cache key qu'une requête antérieure et que l'objet est encore valide dans cette edge location. Tout le réglage du cache CloudFront revient à une règle : moins il y a de valeurs dans la cache key, plus il y a de hits.
Vous contrôlez la clé avec une cache policy attachée à un cache behavior. La policy dit quels en-têtes, cookies et query strings entrent dans la clé, et elle porte aussi les réglages de TTL.
Les policies managées valent d'être mémorisées, parce qu'elles marquent les deux extrémités de la plage :
| Cache policy managée | Min TTL | Max TTL | Default TTL | Contenu de la cache key |
|---|---|---|---|---|
CachingOptimized | 1 s | 31 536 000 s (365 jours) | 86 400 s (24 h) | Rien, sauf l'en-tête Accept-Encoding normalisé |
CachingDisabled | 0 s | 0 s | 0 s | Rien |
CachingOptimized est la réponse par défaut pour les assets statiques. CachingDisabled est la bonne réponse pour tout ce qui est réellement propre à un utilisateur, et elle fonctionne parce que ses trois TTL valent 0, pas parce qu'elle ne transmet rien.
Trois erreurs de cache key coûtent du hit ratio, et chacune a son correctif :
- La casse et l'ordre dans les query strings.
?parameter1=Aet?parameter1=asont deux clés différentes, tout comme?parameter1=a¶meter2=bet?parameter2=b¶meter1=a. Même objet, jusqu'à quatre entrées de cache. Normalisez une casse et un ordre dans le code qui construit les URL. - Transmettre tous les cookies. Pour chaque cookie transmis, CloudFront garde une copie distincte par combinaison de nom et de valeur. Deux cookies à trois valeurs possibles chacun, cela fait jusqu'à 9 copies du même fichier
.css. Séparez statique et dynamique dans deux cache behaviors et ne transmettez les cookies que sur le behavior dynamique. - Mettre en cache sur
User-Agent. Le nombre de valeurs distinctes est énorme, donc cet en-tête désactive le cache en donnant l'apparence d'une configuration qui marche. Si vous avez besoin de réponses adaptées à l'appareil, utilisez plutôt les en-têtes CloudFront dédiés :CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-VieweretCloudFront-Is-Tablet-Viewer. Quatre valeurs au lieu de milliers, et vous ne transmettez que ceux qui changent réellement la réponse.
Un dernier levier vit en dehors de la clé. Si la compression n'entre pas en jeu, attachez un custom origin header nommé Accept-Encoding avec une valeur vide. CloudFront retire alors cet en-tête de la cache key au lieu de découper chaque objet en variantes Gzip, Brotli et non compressée.
Combien de temps un objet reste : l'origine propose, la policy décide
C'est la partie que les opérateurs ratent le plus souvent, et elle mérite qu'on la déroule lentement.
Trois réglages vivent dans la cache policy : Minimum TTL, Maximum TTL et Default TTL. L'origine peut de son côté envoyer Cache-Control: max-age, Cache-Control: s-maxage ou un en-tête Expires. Aucun des deux camps ne gagne d'office. L'origine propose une durée et la policy la recadre dans sa plage.
Déroulez avec une policy à Minimum TTL 60, Maximum TTL 86400, Default TTL 3600 :
| Réponse de l'origine | CloudFront met en cache pendant | Pourquoi |
|---|---|---|
Cache-Control: max-age=10 | 60 s | Sous le minimum, donc remonté au Minimum TTL |
Cache-Control: max-age=7200 | 7200 s | Dans la plage, donc respecté tel quel |
Cache-Control: max-age=31536000 | 86400 s | Au-dessus du maximum, donc redescendu au Maximum TTL |
Aucun en-tête Cache-Control | 3600 s | Rien n'a été proposé, donc le Default TTL s'applique |
Quatre détails sur les bords de ce tableau :
s-maxagebatmax-ageà la périphérie. Quand l'origine envoie les deux, CloudFront recadres-maxageet le navigateur utilisemax-age. C'est ainsi qu'on garde un objet une heure à la périphérie et une minute dans le navigateur.Expiresest l'option faible. Si l'origine envoie à la foisCache-Control: max-ageetExpires, CloudFront n'utilise quemax-age. AWS recommandemax-age.- Un visiteur ne peut pas forcer un rafraîchissement. CloudFront ignore
Cache-ControletPragmadans les requêtes visiteur, donc un rechargement forcé dans le navigateur ne vide pas la périphérie. - Sans
max-ageet avec un Minimum TTL supérieur à 0, CloudFront retient le plus grand du Minimum TTL et du Default TTL. Avec 60 et 3600, cela donne bien 3600, mais la règle n'est pas « le Default TTL gagne ».
Vient maintenant l'idée fausse qui livre des bugs en production. On suppose volontiers qu'un Cache-Control: no-store venu de l'origine empêche toujours CloudFront de mettre en cache. C'est faux dès que le Minimum TTL de la cache policy dépasse 0. Dans ce cas, CloudFront garde l'objet pendant la durée du minimum alors que l'origine a dit non, et c'est ainsi qu'une page de compte personnalisée est servie au visiteur suivant qui atterrit sur la même edge location. CachingOptimized (Minimum TTL 1 seconde) et la policy Amplify (Minimum TTL 2 secondes) portent cet avertissement dans la documentation AWS. Si un contenu ne doit jamais être mis en cache, la réponse est une policy dont le minimum vaut 0.
Servir du périmé volontairement
Deux directives échangent un peu de fraîcheur contre de la latence et contre la survie à une panne d'origine :
Cache-Control: max-age=3600, stale-while-revalidate=600, stale-if-error=86400
- Pendant la première heure, CloudFront sert depuis le cache normalement.
- Ensuite,
stale-while-revalidate=600autorise CloudFront à rendre immédiatement la copie périmée au visiteur pendant qu'il va chercher une version fraîche en arrière-plan, jusqu'à 10 minutes. stale-if-error=86400autorise CloudFront à continuer de servir la copie périmée jusqu'à 24 heures si l'origine est injoignable ou renvoie une erreur 5xx.
Les deux sont plafonnées par le Maximum TTL de la cache policy, la valeur la plus faible l'emportant. Passé le Maximum TTL, l'objet a quitté la périphérie quoi qu'aient demandé les directives.
Invalidation ou noms de fichiers versionnés
Quand il faut sortir un contenu du cache avant son expiration, vous avez deux options, et AWS recommande celle vers laquelle les gens se tournent en second.
L'invalidation retire les fichiers des caches de périphérie tout de suite. Les 1 000 premiers chemins d'invalidation par mois sont gratuits par compte AWS, toutes distributions confondues, et vous payez au chemin au-delà. Un chemin contenant un joker * compte pour un seul chemin quel que soit le nombre de fichiers retirés, donc /* est une unité facturable unique. Le joker doit être le dernier caractère ; un astérisque ailleurs est traité littéralement.
Deux pièges d'invalidation gâchent un déploiement :
- Les query strings font partie du chemin. Si la cache key contient les query strings,
/images/logo.jpgn'invalide pas/images/logo.jpg?v=2. Utilisez/images/logo.jpg*. - Les chemins de répertoire demandent les deux formes. Si vos URL ne sont pas cohérentes sur le slash final, invalidez
/imageset/images/.
Les noms de fichiers versionnés consistent à livrer app.a3f91c.js au lieu d'écraser app.js. AWS recommande cette approche en premier pour les contenus qui changent souvent, et la raison ne se limite pas au coût. Une invalidation vide CloudFront mais ne fait rien contre la copie posée dans le navigateur du visiteur ou dans un proxy d'entreprise ; un nouveau nom de fichier change l'URL, donc tous les caches de la chaîne font un miss. Cela rend aussi le rollback trivial et les access logs lisibles, puisque la ligne de log nomme la version servie.
Origin Shield est l'autre levier structurel. Il ajoute une couche de cache supplémentaire devant l'origine, pour que toutes les couches CloudFront, edge locations et regional edge caches, convergent vers un seul emplacement. L'origine sert alors une requête par objet au lieu d'une requête par regional cache.
ElastiCache : le cache qu'il faut programmer
CloudFront se remplit tout seul. ElastiCache non : votre code décide de ce qui entre, quand, et pour combien de temps. AWS documente trois stratégies, et l'examen teste le mode d'échec de chacune plutôt que sa définition.
Le lazy loading écrit dans le cache seulement après un miss.
get_customer(customer_id)
customer_record = cache.get(customer_id)
if (customer_record == null)
customer_record = db.query("SELECT * FROM Customers WHERE id = {0}", customer_id)
cache.set(customer_id, customer_record)
return customer_record
Seules les données demandées entrent dans le cache, et une panne de nœud est survivable : un nœud neuf et vide renvoie encore des réponses justes, simplement plus lentement, pendant que les miss le remplissent. Le prix, ce sont trois allers-retours par miss (lecture du cache, requête en base, écriture du cache) et des données périmées, puisque rien ne met le cache à jour quand la base change en dessous.
Le write-through écrit dans le cache à chaque écriture en base.
save_customer(customer_id, values)
customer_record = db.query("UPDATE Customers WHERE id = {0}", customer_id, values)
cache.set(customer_id, customer_record)
return success
Les données en cache ne sont jamais périmées, et la latence supplémentaire tombe sur les écritures, là où les utilisateurs la tolèrent mieux. Le prix est l'image inversée du lazy loading : des données manquantes sur tout nouveau nœud, puisqu'un nœud neuf ne contient rien tant que les lignes correspondantes ne sont pas réécrites, et du cache churn, puisque vous mettez en cache des écritures que personne ne lira peut-être jamais.
Ajouter un TTL à chaque écriture est ce qui fait fonctionner le couple. Un TTL borne la péremption d'une entrée chargée paresseusement et évince les entrées write-through que personne ne lit. Le motif de production, c'est les trois ensemble : write-through pour la justesse, lazy loading pour la résilience, TTL pour borner les deux.
cache.set(customer_id, customer_record, 300) # expire dans 5 minutes
Un TTL ne garantit jamais la fraîcheur. Il garantit un plafond de péremption, ce qui est une promesse différente et bien plus tenable. Dites-le en revue de conception, plutôt que de laisser « on met en cache 5 minutes » se faire entendre comme « le cache est correct ».
Choisir le moteur, et pourquoi cela change vos options de scaling
ElastiCache prend en charge Memcached, Valkey et Redis OSS. Le moteur choisi détermine quelles actions de scaling existent, donc ce n'est pas une question de préférence.
| Memcached | Valkey / Redis OSS (cluster mode disabled) | Valkey / Redis OSS (cluster mode enabled) | |
|---|---|---|---|
| Types de données | Chaînes et objets simples | Complexes (listes, hashes, sets, sorted sets) | Complexes |
| Multi-thread | Oui | Non | Non |
| Réplication (read replicas) | Non | Oui | Oui |
| Automatic failover | Non | Optionnel | Obligatoire |
| Partitionnement des données | Oui, côté client | Non | Oui |
| Online resharding | Non | Non | Oui |
| Backup et restore | Clusters à nœuds : non | Oui | Oui |
Prenez Memcached quand vous voulez le modèle le plus simple possible, de gros nœuds multicœurs et du cache d'objets sans plus. Prenez Valkey ou Redis OSS quand vous avez besoin de réplication, de failover, de persistance, de sorted sets ou de pub/sub. La conséquence pratique : un cluster Memcached ne se répare pas en ajoutant des réplicas, puisqu'il n'en a pas. Ses seuls leviers sont un type de nœud plus gros ou plus de nœuds.
Lire les métriques, puis choisir l'action de scaling
Les questions de scaling ElastiCache sont en général des questions de diagnostic. Une métrique vous dit quelle ressource manque, et la ressource plus le moteur vous donnent l'action.
| Métrique | Ce qu'elle signifie | Ce vers quoi elle pointe |
|---|---|---|
CPUUtilization | Le CPU au niveau de l'hôte | Sur les petits nœuds, le plafond de la charge |
EngineCPUUtilization | L'usage du cœur moteur unique | Le vrai signal sur les nœuds à 4 vCPU ou plus |
Evictions | Des clés retirées pour faire de la place | Pas assez de mémoire pour le working set |
SwapUsage et FreeableMemory | La pression mémoire | FreeableMemory sous 100 Mo, ou SwapUsage au-dessus de FreeableMemory, veut dire que le nœud est en difficulté |
ReplicationLag | Le retard d'un réplica sur le primaire | Les lectures sur réplica renvoient des données anciennes |
TrafficManagementActive | Une valeur de 1 veut dire qu'ElastiCache throttle les commandes entrantes | Le nœud est sous-dimensionné pour la charge |
Le seuil de CPU piège beaucoup de monde, donc travaillez-le. Valkey et Redis OSS exécutent le moteur sur un seul thread, donc un nœud peut être complètement saturé pendant que le CPU de l'hôte affiche une valeur très inférieure à 100 %. AWS donne le calcul : fixez le seuil à 90 divisé par le nombre de cœurs. Sur un nœud à 2 cœurs, cela fait 45 %. Un cluster à 46 % de CPU avec une latence qui grimpe n'est pas sain et tranquille, il est cloué. Sur les types de nœuds à 4 vCPU ou plus, utilisez EngineCPUUtilization et la division disparaît. Memcached est multi-thread, donc son seuil est bien autour de 90 %.
Une fois la ressource identifiée, l'action découle du moteur et de la charge :
- Lecture intensive au-dessus du seuil, Valkey ou Redis OSS : ajoutez des read replicas.
- Écriture intensive, cluster mode disabled : montez en gamme de nœud. Il n'y a qu'un primaire, donc plus d'écritures demandent un primaire plus gros.
- Écriture intensive, cluster mode enabled : ajoutez des shards, ce qui répartit les écritures sur plus de nœuds primaires.
- N'importe quelle pression, Memcached : type de nœud plus gros, ou plus de nœuds.
Comment le scaling se produit réellement
En cluster mode enabled, l'online resharding change le nombre de shards pendant que le cluster continue de servir les requêtes. Ajouter des shards augmente la capacité en lecture et en écriture, en retirer réduit le coût, et le rebalancing égalise l'espace de clés entre les shards existants. Trois limites à connaître : les nouveaux shards reçoivent le nombre de nœuds du plus petit shard existant, vous ne pouvez pas fixer les espaces de clés par shard en ligne (cela demande le chemin offline par backup et restore), et les clés dont les éléments dépassent 256 Mo après sérialisation ne sont pas migrées, ce qui peut laisser des shards déséquilibrés. Avant de retirer des shards, ElastiCache vérifie que les shards restants peuvent contenir les données et annule l'opération plutôt que de perdre des clés.
Le scaling vertical par type de nœud est aussi une opération en ligne pour Valkey et Redis OSS. Le resharding offline, la voie backup et restore, est celui qui coupe le service, et vous acceptez cette coupure seulement pour ce qu'il permet en une seule manœuvre : changer le type de nœud, la version du moteur, le nombre de réplicas par shard et les espaces de clés.
ElastiCache Serverless supprime la décision sur les shards. Il suit le CPU, la mémoire et le réseau en continu et ajoute des shards de lui-même, et vous surveillez deux métriques au lieu d'une liste de nœuds : BytesUsedForCache pour le stockage et ElastiCacheProcessingUnits (ECPU) pour le calcul. Vous pouvez plafonner les deux pour borner le coût, à condition de comprendre ce qu'un plafond produit à la limite : atteindre le maximum de stockage fait évincer les clés porteuses de TTL en LRU puis renvoyer des erreurs de mémoire insuffisante, et atteindre le maximum d'ECPU fait throttler les requêtes. AWS recommande une alarme CloudWatch à 75 % de tout maximum que vous fixez, pour l'apprendre avant vos utilisateurs.
Conseils pour l'examen
- « La même réponse à beaucoup de visiteurs en HTTP » désigne CloudFront. « Un résultat de requête coûteux réutilisé par l'application » désigne ElastiCache. Un énoncé qui décrit une base sous pression de lecture ne parle pas de la périphérie.
- Devant des chiffres de TTL, faites le recadrage : un
max-agesous le Minimum TTL remonte au minimum, au-dessus du Maximum TTL il redescend au maximum, et le Default TTL ne s'applique que si l'origine n'envoie aucunmax-age. Les mauvaises réponses reprennent le dernier nombre cité. - « L'origine envoie no-store et du contenu périmé est quand même servi » désigne une cache policy dont le Minimum TTL dépasse 0, pas un défaut de CloudFront.
- 1 000 chemins d'invalidation gratuits par mois et par compte, et un chemin avec joker compte pour un. Si l'énoncé insiste sur des mises à jour fréquentes, la réponse attendue est en général les noms de fichiers versionnés, pas plus d'invalidations.
- Toute option qui augmente le cache hit ratio en ajoutant des en-têtes, des cookies ou des query strings à la cache key est fausse. Les hits viennent d'une clé plus petite.
- Le lazy loading autorise des données périmées et survit aux nœuds vides. Le write-through est toujours frais et échoue sur les nœuds vides. Le TTL est ce qui rend viable de faire tourner les deux. Rattachez le symptôme à la stratégie, pas la définition.
- Valkey et Redis OSS sont mono-thread : le seuil d'alarme CPU vaut 90 divisé par le nombre de cœurs, et
EngineCPUUtilizationest le signal propre sur les gros nœuds. - Memcached n'a ni réplicas, ni failover, ni cluster mode. Toute réponse qui propose l'un des trois pour un cluster Memcached est fausse sur la seule frontière du moteur.
- Lecture intensive veut dire réplicas, écriture intensive en cluster mode disabled veut dire un nœud plus gros, écriture intensive en cluster mode enabled veut dire plus de shards.
La règle à emporter : nommez ce qui se répète avant de nommer le service. Les réponses HTTP répétées appartiennent à la périphérie, les résultats de requêtes répétés appartiennent à la mémoire à côté de l'application, et ce qui varie réellement à chaque requête n'appartient qu'à la base. Cette dernière catégorie est celle que le cache ne sauve pas, et c'est là que commence la leçon suivante, sur la mise à l'échelle de la base relationnelle elle-même.
