Vol. 8 · № 01 · Un atelier d'architectureAn architecture workshop

Caching, ou la vitesse qu'on paie en fraîcheurCaching, or the speed you pay for in freshness

Le dernier volume s'ouvre par le pattern le plus rentable, et le plus piégeux. « Mets un cache » divise les temps de réponse par cent — mais copier une donnée, c'est en avoir deux exemplaires à garder d'accord, et sans invalidation le cache ment. La règle du volume : on ne cache pas une intuition, on cache un point chaud mesuré — et on décide comment l'invalider avant même qu'il existe.The final volume opens with the most profitable pattern, and the most treacherous. 'Add a cache' divides response times by a hundred — but copying data means two copies to keep in agreement, and without invalidation the cache lies. The volume's rule: you don't cache a hunch, you cache a measured hot path — and decide how to invalidate it before it even exists.

AudienceAudience
Dev tenté d'ajouter un cache « parce que c'est plus rapide », sans l'avoir mesuréDev tempted to add a cache 'because it's faster', without measuring it
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026June 2026
≈ 18 min●●○○ScalabilitéCachingTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Le cache va plus vite. Mais il peut mentir — et savoir quand, c'est tout le sujet.The cache is faster. But it can lie — and knowing when is the whole subject.

Voici le dernier volume, celui de la scalabilité — et il s'ouvre par le pattern le plus rentable de tous, et le plus piégeux. Une requête est lente et appelée sans cesse ? Le réflexe est immédiat : « mets un cache ». Garder le résultat sous la main au lieu de le recalculer divise les temps de réponse par dix ou cent, et soulage la source d'autant. Le gain est si évident qu'on en oublie la contrepartie : dès qu'on copie une donnée, on a deux exemplaires de la vérité — et les garder d'accord est l'un des deux problèmes vraiment durs de l'informatique. Sans stratégie d'invalidation, le cache continue de servir l'ancienne valeur longtemps après que la source a changé : il ment. Le réflexe complet n'est donc pas « mets un cache », c'est « mets un cache, et décide d'avance comment et quand tu le rafraîchis » — et, parce que c'est le volume de la scalabilité, on ne le pose qu'avec un chiffre : un point chaud mesuré, pas une intuition.Here's the final volume, the one on scalability — and it opens with the most profitable pattern of all, and the most treacherous. A query is slow and called constantly? The reflex is immediate: 'add a cache'. Keeping the result on hand instead of recomputing it divides response times by ten or a hundred, and relieves the source by as much. The gain is so obvious you forget its counterpart: the moment you copy a piece of data, you have two copies of the truth — and keeping them in agreement is one of the two genuinely hard problems in computing. Without an invalidation strategy, the cache keeps serving the old value long after the source has changed: it lies. So the full reflex isn't 'add a cache', it's 'add a cache, and decide up front how and when you refresh it' — and, because this is the scalability volume, you only add it with a number: a measured hot path, not a hunch.

Le code naïf — un cache qu'on remplit, mais qu'on n'invalide jamaisThe naive code — a cache you fill, but never invalidate
# une requête lente, appelée souvent. le réflexe : « mets un cache ». la moitié du geste seulement.
cache = {}
def get_profile(user_id):
    if user_id in cache:
        return cache[user_id]              # ✓ rapide : on évite la base
    row = db.query("SELECT * FROM profiles WHERE id = ?", user_id)   # 💥 lent, mais TOUJOURS vrai
    cache[user_id] = row
    return row

def update_profile(user_id, data):
    db.update("profiles", user_id, data)   # 💥 on écrit la source… et on oublie le cache.
    # → le cache garde l'ancienne valeur. tous les lecteurs voient le PASSÉ, peut-être pour des heures.
# un cache sans stratégie d'invalidation n'est pas un cache : c'est un mensonge qui s'installe.
Lire la source vs lire le cacheReading the source vs reading the cache
lire la sourceread the source
lent · coûteux · toujours vraislow · costly · always true
⇄
vsvs
lire le cacheread the cache
rapide · léger · peut mentirfast · light · can lie

La source dit toujours la vérité, mais lentement. Le cache répond vite, mais il dit la vérité telle qu'elle était au moment où on l'a copiée — et plus le monde change derrière, plus il s'écarte du vrai. Tout le métier du cache tient dans cet arbitrage : combien de fraîcheur suis-je prêt à sacrifier contre combien de vitesse ? Un cache n'est jamais « activé » ou « désactivé » ; il est réglé sur une tolérance au mensonge — zéro pour un solde bancaire, quelques minutes pour un compteur de vues. Décider cette tolérance, c'est concevoir le cache.The source always tells the truth, but slowly. The cache answers fast, but it tells the truth as it was when you copied it — and the more the world changes behind it, the further it drifts from what's real. The whole craft of caching lives in this trade-off: how much freshness am I willing to sacrifice for how much speed? A cache is never 'on' or 'off'; it's tuned to a tolerance for lying — zero for a bank balance, a few minutes for a view counter. Deciding that tolerance is designing the cache.

Le réflexe — lecture, écriture, invalidationThe reflex — read, write, invalidation

Un cache se conçoit en trois temps. La lecture, d'abord : le motif le plus courant est le cache-aside — on regarde le cache, on sert s'il a la valeur (hit), sinon on lit la source, on remplit, on sert (miss). L'écriture, ensuite : c'est là qu'on choisit sa stratégie. Soit on écrit la source puis on invalide l'entrée (le prochain lecteur la rechargera), soit on écrit cache et source en même temps (write-through). L'invalidation, enfin, est le vrai sujet : un TTL borne la durée du mensonge sans rien savoir des écritures ; une invalidation explicite supprime l'entrée dès que la source change ; les deux se combinent souvent. Sauter cette troisième étape, c'est garder un cache qui ment indéfiniment.A cache is designed in three beats. The read, first: the commonest pattern is cache-aside — you check the cache, serve if it has the value (hit), otherwise read the source, fill, serve (miss). The write, next: that's where you choose your strategy. Either you write the source then invalidate the entry (the next reader will reload it), or you write cache and source at once (write-through). Invalidation, finally, is the real subject: a TTL bounds the lie's duration without knowing anything about writes; an explicit invalidation removes the entry the moment the source changes; the two often combine. Skipping that third beat means keeping a cache that lies indefinitely.

La règle du volume : jamais sans un chiffre.The volume's rule: never without a number.

La scalabilité est le dernier volume, pas le premier, et pour une raison : c'est le terrain le plus exposé à l'optimisation prématurée. Le cache en est l'exemple parfait — si tentant qu'on le pose par réflexe, sur une requête dont on n'a jamais mesuré le coût ni la fréquence. Or un cache n'est rentable que sur un point chaud : une donnée lue beaucoup plus souvent qu'elle n'est écrite, et assez coûteuse à produire. Sur une donnée froide, il n'évite presque aucun calcul, gonfle la mémoire, et ajoute un risque d'incohérence pour rien. La discipline du volume tient en une phrase : on n'optimise pas une intuition, on optimise un goulot mesuré. Avant tout cache, un profil, un taux de réutilisation, un chiffre. C'est aussi un rappel de « cache invalidation and naming things » — l'une des deux choses vraiment dures, qu'on ne s'inflige pas sans raison chiffrée.Scalability is the last volume, not the first, for a reason: it's the ground most exposed to premature optimization. The cache is its perfect example — so tempting you add it by reflex, on a query whose cost and frequency you never measured. Yet a cache only pays on a hot path: data read far more often than written, and costly enough to produce. On cold data, it avoids almost no computation, bloats memory, and adds an inconsistency risk for nothing. The volume's discipline fits in one sentence: you don't optimize a hunch, you optimize a measured bottleneck. Before any cache, a profile, a reuse ratio, a number. It's also a nod to 'cache invalidation and naming things' — one of the two genuinely hard things, which you don't inflict on yourself without a measured reason.

№ 02La douleurThe pain4 min

Le cache ment, la vérité se dédouble, et le jour où il tombe, la base tombe avec.The cache lies, the truth doubles, and the day it falls, the database falls with it.

Un cache mal conçu a trois coûts, et aucun n'est visible le jour où on le pose — tous arrivent plus tard. D'abord il ment : sans invalidation reliée aux écritures, il sert l'ancienne valeur longtemps après que la source a changé, et plus le TTL est long, plus le mensonge dure. Ensuite la vérité se dédouble : on a désormais deux copies à garder d'accord, et entre les deux s'ouvrent des courses — deux écritures qui s'entrelacent, mille lecteurs qui ratent en même temps à l'expiration et foncent tous sur la base d'un coup. Enfin les modes de panne se déplacent : le cache, en absorbant l'essentiel du trafic, est devenu porteur — et le jour où il tombe ou démarre froid, toute la charge qu'il masquait s'abat d'un seul coup sur une base qui ne l'a jamais vue. Trois douleurs différées, qu'on paie d'autant plus cher qu'on les a ignorées au départ.A badly designed cache has three costs, and none shows the day you add it — all arrive later. First it lies: without invalidation tied to writes, it serves the old value long after the source changed, and the longer the TTL, the longer the lie. Then the truth doubles: you now have two copies to keep in agreement, and between them races open — two writes interleaving, a thousand readers missing at once on expiry and all rushing the database together. Finally the failure modes shift: the cache, by absorbing most of the traffic, has become load-bearing — and the day it falls or starts cold, all the load it masked crashes at once onto a database that never saw it. Three deferred pains, paid all the more dearly for having been ignored at the start.

№ 1 · Le cache ment (donnée périmée)The cache lies (stale data)

La source change, le cache ne le sait pas : il continue de servir la valeur copiée plus tôt. Le profil affiche l'ancien nom, le prix affiché n'est plus celui facturé, le stock dit « disponible » alors qu'il est vide. Sans invalidation reliée aux écritures, le mensonge n'a pas de fin ; avec un TTL seul, il dure exactement la durée du TTL — qu'on a souvent choisie au hasard.The source changes, the cache doesn't know: it keeps serving the value copied earlier. The profile shows the old name, the displayed price is no longer the one charged, the stock says 'available' when it's empty. Without invalidation tied to writes, the lie has no end; with a TTL alone, it lasts exactly the TTL's duration — which you often picked at random.

№ 2 · La vérité se dédouble (courses, stampede)The truth doubles (races, stampede)

Deux copies, donc deux écritures à synchroniser : entre lire, modifier et réécrire, deux requêtes s'entrelacent et l'une écrase l'autre. Et à l'expiration d'une entrée très demandée, tous les lecteurs ratent au même instant et se ruent ensemble sur la source pour la recalculer — c'est le cache stampede, la version « cache » du thundering herd vu au Vol 7.Two copies, so two writes to synchronize: between read, modify and rewrite, two requests interleave and one overwrites the other. And on a hot entry's expiry, all readers miss at the same instant and rush the source together to recompute it — that's the cache stampede, the 'cache' version of the thundering herd seen in Vol 7.

№ 3 · La panne se déplace (cache porteur)The failure shifts (load-bearing cache)

À force d'absorber 95 % des lectures, le cache est devenu indispensable sans qu'on l'ait décidé. Le jour où il redémarre froid, où il est vidé, où il tombe, les 95 % de trafic qu'il masquait s'abattent d'un coup sur la base — qui n'a jamais été dimensionnée pour ça et s'effondre. On croyait avoir ajouté une optimisation ; on avait ajouté un point de défaillance unique.By absorbing 95% of reads, the cache became indispensable without your deciding it. The day it restarts cold, gets flushed, or falls, the 95% of traffic it masked crashes at once onto the database — which was never sized for it and collapses. You thought you'd added an optimization; you'd added a single point of failure.

Sans invalidation, sans garde-fou — les trois douleurs en germeNo invalidation, no guardrail — the three pains in seed
# 14h00 : on écrit le nouveau prix en base. le cache garde l'ancien (TTL = 1h).
db.update("products", id, {"price": 89})       # 💥 source à jour, cache périmé pendant 1h
# 14h00–15h00 : tous les clients voient 99 €, sont facturés 89 €. (le cache ment)

# 15h00 : l'entrée expire. 3 000 requêtes/s la ratent AU MÊME instant…
price = cache.get(id) or db.query(...)          # 💥 …et foncent toutes sur la base (stampede)

# 15h02 : le serveur de cache redémarre, vide. 100 % du trafic bascule sur la base.
# la base, habituée à 5 % des lectures, reçoit 100 % d'un coup → elle tombe. (panne déplacée)
Le diagnostic.The diagnosis.

Trois questions à poser avant de poser un cache. Sur la fraîcheur : « quelle péremption cette donnée tolère-t-elle, et qu'est-ce qui supprime l'entrée quand la source change ? » — si la réponse est « rien », le cache mentira sans fin. Sur les courses : « que se passe-t-il quand mille requêtes ratent la même clé en même temps ? » — sans garde-fou, elles écrasent la base. Sur la panne : « si le cache disparaît à l'instant, la source encaisse-t-elle 100 % du trafic ? » — si non, le cache est porteur et c'est un SPOF déguisé. Le réflexe : choisir une stratégie d'invalidation explicite (écrire puis invalider, ou write-through), borner le mensonge par un TTL, protéger l'expiration des points chauds (verrou ou single-flight), et garder à l'esprit que la source doit pouvoir survivre, dégradée, à la perte du cache.Three questions to ask before adding a cache. On freshness: 'what staleness does this data tolerate, and what removes the entry when the source changes?' — if the answer is 'nothing', the cache will lie endlessly. On races: 'what happens when a thousand requests miss the same key at once?' — without a guardrail, they crush the database. On failure: 'if the cache vanishes right now, does the source take 100% of the traffic?' — if not, the cache is load-bearing and it's a disguised SPOF. The reflex: choose an explicit invalidation strategy (write then invalidate, or write-through), bound the lie with a TTL, protect hot keys' expiry (a lock or single-flight), and keep in mind the source must be able to survive, degraded, the loss of the cache.

№ 03L'invalidationThe invalidation5 min

Miss, hit, mensonge, invalidation — la boucle qui garde le cache honnête.Miss, hit, lie, invalidation — the loop that keeps the cache honest.

Le cache ne se comprend pas comme un interrupteur, mais comme un cycle de vie. L'animation suit une seule entrée — un prix — à travers ses cinq moments : le miss qui la remplit depuis la base, le hit qui la sert en un éclair, l'instant où la source change et où le cache se met à mentir, l'invalidation qui supprime l'entrée périmée, et le miss suivant qui la recharge à jour. Surveille les deux valeurs — celle du cache, celle de la base — et l'étiquette « vrai / ment / vide » : c'est leur écart qui raconte pourquoi l'invalidation n'est pas une option mais le pivot de tout le pattern.The cache isn't understood as a switch, but as a life cycle. The animation follows a single entry — a price — through its five moments: the miss that fills it from the database, the hit that serves it in a flash, the instant the source changes and the cache starts lying, the invalidation that removes the stale entry, and the next miss that reloads it fresh. Watch the two values — the cache's, the database's — and the 'true / lying / empty' label: it's their gap that tells why invalidation isn't an option but the pivot of the whole pattern.

Animation — une entrée de cache, de sa naissance à son rafraîchissementAnimation — one cache entry, from birth to refresh
Miss — le cache est videMiss — the cache is empty · 1/5
appliapp
120 ms
→
cachecache
∅
videempty
→
base (source)database (source)
99 €
véritétruth
cache vide — la prochaine lecture rechargera la sourceempty cache — the next read reloads the source
Première lecture : le cache n'a rien (miss). L'application descend jusqu'à la base, paie le plein prix — 120 ms —, récupère la valeur, puis la dépose dans le cache pour la prochaine fois. Un miss coûte plus cher qu'une lecture sans cache : on a fait le trajet long PLUS le remplissage.First read: the cache has nothing (miss). The app goes down to the database, pays full price — 120 ms —, fetches the value, then drops it into the cache for next time. A miss costs more than a cacheless read: you did the long trip PLUS the fill.

Cache-aside en lecture, invalidation à l'écritureCache-aside on read, invalidation on write
def get_price(id):
    if (v := cache.get(id)) is not None:
        return v                              # HIT — 2 ms, on ne touche pas la base
    v = db.query("SELECT price FROM products WHERE id = ?", id)   # MISS — 120 ms
    cache.set(id, v, ttl=300)                 # on remplit, TTL = filet de sécurité (5 min)
    return v

def set_price(id, price):
    db.update("products", id, {"price": price})   # 1) on écrit la SOURCE d'abord
    cache.delete(id)                              # 2) puis on INVALIDE — le prochain read rechargera
    # ordre crucial : source puis cache. l'inverse laisse une fenêtre où le cache est "à jour" et faux.
    # reste une course rare : un get_price qui a lu l'ancien prix AVANT l'update peut le
    # ré-écrire APRÈS le delete. c'est elle que le TTL borne — d'où « ceinture et bretelles ».
TTL et invalidation explicite : ceinture et bretelles.TTL and explicit invalidation: belt and braces.

Les deux stratégies ne s'opposent pas, elles se complètent. L'invalidation explicite — supprimer l'entrée à chaque écriture — donne la fraîcheur la plus fine : dès que la source change, le cache l'oublie. Mais elle suppose qu'on connaisse tous les chemins d'écriture ; en oublier un, c'est rouvrir le mensonge. Le TTL, lui, ne sait rien des écritures : il borne aveuglément la durée de vie d'une entrée. Seul, il laisse mentir le cache jusqu'à l'expiration ; mais comme filet, il rattrape l'invalidation qu'on a oubliée — au pire, le mensonge dure un TTL, pas l'éternité. La combinaison gagnante : invalider explicitement pour la fraîcheur, et garder un TTL court comme assurance contre nos propres oublis. Choisir la durée du TTL, c'est choisir le pire cas d'incohérence qu'on accepte.The two strategies don't oppose, they complement. Explicit invalidation — removing the entry on each write — gives the finest freshness: the moment the source changes, the cache forgets it. But it assumes you know every write path; missing one reopens the lie. The TTL knows nothing of writes: it blindly bounds an entry's lifetime. Alone, it lets the cache lie until expiry; but as a net, it catches the invalidation you forgot — at worst, the lie lasts one TTL, not forever. The winning combination: invalidate explicitly for freshness, and keep a short TTL as insurance against your own omissions. Choosing the TTL's duration is choosing the worst-case inconsistency you accept.

Modèle mentalMental model
L'imageThe image

Une antisèche recopiée sur ta main pour ne pas rouvrir le manuel à chaque question : instantanée à relire — tant que le cours n'a pas changé sous tes doigts.A cheat sheet copied onto your hand so you don't reopen the textbook at each question: instant to reread — as long as the material hasn't changed under your fingers.

La règleThe rule

Copier un point chaud mesuré pour le lire vite ; à chaque écriture de la source, effacer la copie ; garder un TTL court qui borne le mensonge quand on oublie.Copy a measured hot path to read it fast; on each write to the source, erase the copy; keep a short TTL that bounds the lie when you forget.

Où l'analogie casseWhere the analogy breaks

L'antisèche, tu sais qu'elle peut être fausse — tu t'en méfies. Le cache sert le périmé avec le même aplomb que le frais : rien, côté lecteur, ne distingue un hit vrai d'un hit qui ment. Et l'antisèche n'a qu'un auteur, quand un cache a autant de chemins de péremption qu'il y a d'écritures sur la source — en oublier un seul, et le mensonge devient permanent.The cheat sheet — you know it might be wrong, so you distrust it. The cache serves stale with the same confidence as fresh: nothing, on the reader's side, tells a true hit from a lying one. And the cheat sheet has a single author, whereas a cache has as many staleness paths as there are writes to the source — miss just one, and the lie becomes permanent.

№ 04Le piègeThe trap4 min

Cacher sans mesurer, ou cacher sans jamais invalider.Caching without measuring, or caching without ever invalidating.

Deux dérives, qui vont souvent de pair parce qu'elles naissent du même réflexe — « ça ne peut pas faire de mal de cacher ». La première cache sans chiffre : on pose un cache sur des données froides, peu relues, dont chaque requête est quasi unique. Le taux de hits frôle zéro, et le cache ne rapporte rien — il ne fait qu'ajouter de la mémoire consommée, de la complexité, et un risque d'incohérence, pour un gain qui n'existe pas. La seconde cache sans invalidation : un TTL choisi au hasard tient lieu de stratégie, aucun chemin d'écriture ne supprime l'entrée, et le cache sert du périmé jusqu'à expiration. La première dérive paie un coût sans bénéfice ; la seconde transforme la vitesse en mensonge. Bascule le toggle : repère le cache qu'aucune mesure ne justifie, et celui qu'aucune écriture ne rafraîchit.Two drifts, which often go together because they spring from the same reflex — 'caching can't hurt'. The first caches without a number: you put a cache on cold data, rarely re-read, where each request is nearly unique. The hit rate grazes zero, and the cache returns nothing — it only adds consumed memory, complexity, and an inconsistency risk, for a gain that doesn't exist. The second caches without invalidation: a randomly chosen TTL stands in for a strategy, no write path removes the entry, and the cache serves stale until expiry. The first drift pays a cost with no benefit; the second turns speed into a lie. Flip the toggle: spot the cache no measurement justifies, and the one no write refreshes.

# on a MESURÉ : /price est lu 3000×/s, écrit 5×/jour, et coûte 120 ms. ratio lecture/écriture énorme.
# → point chaud réel : le cache est justifié. on le conçoit complet.
def get_price(id):
    if (v := cache.get(id)) is not None:
        return v                                  # hit
    with single_flight(id):                       # ✓ anti-stampede : un seul recalcul à la fois
        if (v := cache.get(id)) is not None: return v   # re-check : un autre vient de remplir
        v = db.query("SELECT price FROM products WHERE id = ?", id)
        cache.set(id, v, ttl=300)                 # ✓ TTL court = filet contre l'invalidation oubliée
        return v

def set_price(id, price):
    db.update("products", id, {"price": price})   # ✓ source d'abord
    cache.delete(id)                              # ✓ invalidation explicite à CHAQUE écriture
# et si le cache tombe : get_price retombe sur la base. dégradé, lent — mais vrai et debout.
Pourquoi c'est justeWhy it's right

Le cache n'existe ici que parce qu'une mesure l'a appelé : 3000 lectures par seconde contre cinq écritures par jour, 120 ms l'unité — un point chaud incontestable, exactement le terrain où un cache rend cent fois sa mise. Et il est conçu complet : la lecture en cache-aside, l'invalidation explicite à chaque écriture pour ne pas mentir, un TTL court en filet pour rattraper l'écriture qu'on aurait oubliée, et un single-flight pour qu'à l'expiration mille lecteurs ne recalculent pas tous en même temps. Surtout, il reste facultatif : si le cache disparaît, la lecture retombe sur la base — plus lente, mais vraie et debout. C'est un cache qui accélère sans mentir et sans devenir un SPOF : le bénéfice, sans les trois douleurs.The cache exists here only because a measurement called for it: 3000 reads per second against five writes per day, 120 ms apiece — an indisputable hot path, exactly the ground where a cache returns a hundredfold. And it's designed complete: cache-aside on read, explicit invalidation on every write so it doesn't lie, a short TTL as a net to catch the write you might have forgotten, and a single-flight so that at expiry a thousand readers don't all recompute at once. Above all, it stays optional: if the cache vanishes, the read falls back to the database — slower, but true and standing. It's a cache that speeds up without lying and without becoming a SPOF: the benefit, without the three pains.

La règle qui tranche.The rule that settles it.

Ne cache qu'un point chaud mesuré, et décide la stratégie d'invalidation avant que le cache n'existe. Deux tests. Le bénéfice : « cette donnée est-elle lue beaucoup plus souvent qu'écrite, et assez coûteuse à produire ? » — sinon, le cache coûte plus qu'il ne rapporte, ne le pose pas. La fraîcheur : « quand la source change, qu'est-ce qui supprime l'entrée, et combien de temps puis-je tolérer qu'elle mente entre-temps ? » — toute écriture met à jour ou invalide, et le TTL borne le pire cas. Un cache sans réponse à la première question est une optimisation imaginaire ; un cache sans réponse à la seconde est un mensonge à retardement. Et garde-le facultatif : si le perdre fait tomber la source, tu n'as pas ajouté un cache, tu as ajouté une dépendance.Cache only a measured hot path, and decide the invalidation strategy before the cache exists. Two tests. The benefit: 'is this data read far more often than written, and costly enough to produce?' — if not, the cache costs more than it returns, don't add it. The freshness: 'when the source changes, what removes the entry, and how long can I tolerate it lying in the meantime?' — every write updates or invalidates, and the TTL bounds the worst case. A cache with no answer to the first question is an imaginary optimization; a cache with no answer to the second is a delayed-action lie. And keep it optional: if losing it takes down the source, you didn't add a cache, you added a dependency.

Le piège jumeau : le cache qu'on ne peut plus éteindre.The twin trap: the cache you can no longer turn off.

À mesure qu'un cache absorbe le trafic, il glisse, sans qu'on le décide, d'« optimisation » à « organe vital ». Le signe : on n'ose plus le redémarrer en heure de pointe. C'est qu'il est devenu porteur — la base ne sait plus encaisser ce qu'il masque. Trois garde-fous gardent un cache facultatif. Le cache chaud : on le pré-remplit (warm-up) avant d'ouvrir le trafic, pour ne jamais démarrer froid. L'expiration étalée : on ajoute du jitter aux TTL pour que les entrées n'expirent pas toutes au même instant. Et le test de vérité : couper le cache en pré-production et vérifier que la source tient, dégradée. Le cache idéal accélère le cas courant et disparaît sans drame — pas celui dont la chute entraîne tout le reste. C'est exactement le fallback du Vol 7, vu côté lecture.As a cache absorbs traffic, it slides, without your deciding it, from 'optimization' to 'vital organ'. The sign: you no longer dare restart it at peak hours. That's because it's become load-bearing — the database can no longer take what it masks. Three guardrails keep a cache optional. The warm cache: you pre-fill it (warm-up) before opening traffic, to never start cold. The staggered expiry: you add jitter to TTLs so entries don't all expire at the same instant. And the truth test: cut the cache in pre-production and check the source holds, degraded. The ideal cache speeds up the common case and vanishes without drama — not the one whose fall drags everything else down. It's exactly Vol 7's fallback, seen from the read side.

№ 05Bilan & éditoWrap-up & editorial2 min

Lire vite, ou lire vrai — et le cache bien réglé fait les deux.Read fast, or read true — and a well-tuned cache does both.

Cache naïf vs cache conçu, côte à côteNaive cache vs designed cache, side by side
cache naïfnaive cachecache conçudesigned cache
La décisionThe decision« ça peut pas faire de mal »'it can't hurt'un point chaud mesuréa measured hot path
La fraîcheurFreshnessment jusqu'au TTLlies until the TTLinvalidé à chaque écritureinvalidated on each write
L'expirationExpirystampede sur la basestampede on the databasesingle-flight · TTL jittésingle-flight · jittered TTL
Si le cache tombeIf the cache fallsla base tombe avecthe database falls with itdégradé · lent mais deboutdegraded · slow but standing
Sur donnée froideOn cold datacoût sans bénéficecost with no benefiton ne cache pasyou don't cache

Quand ne pas (encore) cacherWhen not to cache (yet)

Le cache a un loyer permanent : une seconde copie à invalider, un mode de panne en plus, une incohérence possible. On lit directement la source — et c'est correct — quand :The cache has permanent rent: a second copy to invalidate, an extra failure mode, a possible inconsistency. You read straight from the source — and that's fine — when:

  • aucun goulot n'est mesuré : la requête est déjà rapide, ou rarement appelée. Cacher « au cas où » optimise une intuition — c'est précisément l'erreur que ce volume refuse (la règle : on n'entre dans la scalabilité qu'avec un chiffre).no bottleneck is measured: the query is already fast, or rarely called. Caching 'just in case' optimizes a hunch — precisely the error this volume refuses (the rule: you enter scalability only with a number).
  • la donnée est écrite autant qu'elle est lue, ou peu réutilisée : un faible ratio lecture/écriture, ou des clés quasi uniques, donnent un taux de hits si bas que le cache coûte plus qu'il n'épargne. Pas de point chaud, pas de cache.the data is written as much as it's read, or little reused: a low read/write ratio, or near-unique keys, give a hit rate so low the cache costs more than it saves. No hot path, no cache.
  • la moindre péremption est inacceptable : un solde débiteur, un droit d'accès qu'on vient de révoquer, un stock au dernier exemplaire. Là où servir le passé une seule seconde est une faute, on lit la source — ou l'on accepte la complexité d'un cache à invalidation forte et à TTL nul, ce qui revient souvent à ne pas cacher.the slightest staleness is unacceptable: an overdrawn balance, an access right just revoked, a last-item stock. Where serving the past for even a second is a fault, you read the source — or you accept the complexity of a strongly-invalidated, zero-TTL cache, which often amounts to not caching.

Le réflexe n'est donc pas « ajoute un cache, c'est plus rapide » : c'est « ai-je mesuré un point chaud lu bien plus qu'écrit, et sais-je dire quand le cache ment et ce qui le rafraîchit ? Alors cache, avec invalidation et TTL. Sinon, lis la source — elle est lente, mais elle ne ment jamais. »So the reflex isn't 'add a cache, it's faster': it's 'have I measured a hot path read far more than written, and can I say when the cache lies and what refreshes it? Then cache, with invalidation and TTL. Otherwise, read the source — it's slow, but it never lies.'

Mot de l'éditeurFrom the editor

Le Vol 8 s'ouvre, et c'est le dernier — celui de la scalabilité, placé tout à la fin pour une raison de principe. C'est le terrain le plus exposé à l'optimisation prématurée, et le cache en est l'emblème : si rentable, si facile à poser, qu'on le dégaine par réflexe sur des requêtes dont on n'a jamais mesuré le coût. D'où la règle qui gouverne tout le volume — on n'y entre qu'avec un chiffre. Le cache illustre aussi la vérité que la série n'a cessé de marteler : il n'existe pas de geste gratuit. Copier une donnée pour la lire vite, c'est accepter une seconde source de vérité, donc l'invalidation — « l'une des deux choses vraiment dures de l'informatique ». Le débutant voit un bouton « plus rapide » ; l'architecte voit un arbitrage entre la vitesse et la fraîcheur, et le règle consciemment, sur une tolérance au mensonge qu'il a choisie.Vol 8 opens, and it's the last — the one on scalability, placed at the very end on principle. It's the ground most exposed to premature optimization, and the cache is its emblem: so profitable, so easy to add, that you draw it by reflex on queries whose cost you never measured. Hence the rule that governs the whole volume — you enter only with a number. The cache also illustrates the truth the series has hammered throughout: there's no free gesture. Copying data to read it fast means accepting a second source of truth, hence invalidation — 'one of the two genuinely hard things in computing'. The beginner sees a 'faster' button; the architect sees a trade-off between speed and freshness, and tunes it consciously, on a tolerance for lying they chose.

Le cache résout un problème de vitesse de lecture en gardant une seule source de vérité, simplement recopiée. Mais que faire quand la source elle-même ne tient plus sur une seule machine — quand la donnée est trop grosse, ou les écritures trop nombreuses pour un seul nœud ? C'est le sujet du #2, le sharding : découper la donnée en morceaux répartis sur plusieurs machines, et payer le prix des requêtes qui doivent les traverser. Et derrière le cache comme derrière le sharding rôde la même question, qu'on a frôlée ici avec le couple vitesse/fraîcheur et au Vol 7 avec la cohérence à terme : dès qu'une donnée existe en plusieurs exemplaires, peut-on être à la fois cohérent et disponible quand le réseau se coupe ? Le #3 y répondra par le théorème CAP — le mur contre lequel toute la scalabilité distribuée vient buter, et la note finale de la série.The cache solves a read-speed problem while keeping a single source of truth, merely copied. But what to do when the source itself no longer fits on one machine — when the data is too big, or the writes too many for a single node? That's #2's subject, sharding: splitting the data into pieces spread across several machines, and paying the price of queries that must cross them. And behind the cache as behind sharding lurks the same question, brushed here with the speed/freshness pair and in Vol 7 with eventual consistency: once data exists in several copies, can you be both consistent and available when the network cuts? Issue #3 will answer with the CAP theorem — the wall every distributed scalability scheme hits, and the series' final note.

Les voisins que ce numéro débloqueThe neighbors this issue unlocks

  • Cache-aside — le motif de lecture par défaut, celui de tout ce numéro : l'application interroge le cache, sert sur hit, et sur miss lit la source puis remplit. Le cache reste « à côté » du chemin de données, jamais sur le chemin critique d'écriture — d'où sa principale vertu : s'il tombe, on lit simplement la source. Sa contrepartie : c'est à l'application de gérer l'invalidation, donc de ne jamais oublier un chemin d'écriture.the default read pattern, the one this whole issue uses: the app queries the cache, serves on hit, and on miss reads the source then fills. The cache stays 'beside' the data path, never on the critical write path — hence its main virtue: if it falls, you simply read the source. Its counterpart: it's up to the app to handle invalidation, so to never forget a write path.
  • Write-through — la stratégie d'écriture inverse : chaque écriture passe par le cache, qui écrit ensuite la source dans le même geste. Le cache n'est jamais périmé puisqu'il est mis à jour avant la lecture — au prix d'écritures plus lentes, et d'un cache désormais sur le chemin critique. Avec son cousin write-behind (écrire le cache tout de suite, la source plus tard, en risquant la perte), il forme la palette des stratégies entre lesquelles la fraîcheur exigée fait choisir.the inverse write strategy: every write goes through the cache, which then writes the source in the same gesture. The cache is never stale since it's updated before the read — at the cost of slower writes, and a cache now on the critical path. With its cousin write-behind (write the cache at once, the source later, risking loss), it forms the palette of strategies between which required freshness makes you choose.
  • CDN & TTL — le cache poussé jusqu'au bord du réseau. Un CDN réplique les contenus (images, pages, réponses d'API) sur des serveurs proches de l'utilisateur, transformant un cache applicatif en cache géographique — et y ramenant exactement les mêmes questions : que met-on en cache, et avec quel TTL ? Le TTL, justement, est le réglage universel de tout cache : la durée de vie d'une entrée, donc la durée maximale du mensonge qu'on accepte. Le choisir, partout, c'est choisir où l'on place le curseur entre vitesse et fraîcheur.the cache pushed to the network's edge. A CDN replicates content (images, pages, API responses) on servers near the user, turning an application cache into a geographic one — and bringing back exactly the same questions: what do you cache, and with what TTL? The TTL, precisely, is every cache's universal knob: an entry's lifetime, hence the maximum duration of the lie you accept. Choosing it, everywhere, is choosing where you set the cursor between speed and freshness.

Le kata — écris-le, ne le relis pasThe kata — write it, don't reread it

Un cache réel n'a pas une mémoire infinie : il faut une capacité bornée et une politique d'éviction. Le LRU (least recently used) évince le moins récemment utilisé ; on lui ajoute le TTL du chapitre 3. Copie ce fichier de tests dans ton langage et ton runner habituels et fais-les passer au vert sans revenir au numéro. Le dernier test est le sournois : lire une entrée doit la rendre « récente » et la protéger de l'éviction. C'est ce qui distingue un vrai LRU d'une simple file d'attente (FIFO) — et la ligne qu'on oublie neuf fois sur dix, c'est le rafraîchissement de l'ordre à la lecture.A real cache doesn't have infinite memory: it needs a bounded capacity and an eviction policy. The LRU (least recently used) evicts the least recently used entry; we add chapter 3's TTL to it. Copy this test file into your usual language and runner and turn them green without coming back to the issue. The last test is the sneaky one: reading an entry must make it 'recent' and shield it from eviction. That's what separates a true LRU from a plain queue (FIFO) — and the line forgotten nine times out of ten is refreshing the order on read.

test_lru_ttl.py
# test_lru_ttl.py — la spec EST le harnais : fais-le passer au vert.
# un cache borné : capacité fixe, on évince le moins récemment UTILISÉ, et le périmé est un miss.

def test_hit_puis_miss():
    c = LRU(capacity=2, ttl=300)
    c.set("a", 1)
    assert c.get("a") == 1                        # posé puis lu → hit
    assert c.get("z") is None                     # jamais vu → miss

def test_evince_au_dela_de_la_capacite():
    c = LRU(capacity=2, ttl=300)
    c.set("a", 1); c.set("b", 2); c.set("c", 3)   # capacité 2 → une entrée doit partir
    assert c.get("a") is None                      # "a", le plus ancien, a été évincé
    assert c.get("b") == 2 and c.get("c") == 3

def test_le_ttl_perime_une_entree():
    c = LRU(capacity=2, ttl=300)
    c.set("a", 1)
    avance_le_temps(301)                           # au-delà du TTL…
    assert c.get("a") is None                      # …présente mais périmée = miss

def test_reecrire_met_a_jour_la_valeur():
    c = LRU(capacity=2, ttl=300)
    c.set("a", 1); c.set("a", 9)
    assert c.get("a") == 9                         # même clé ré-écrite → dernière valeur

def test_lire_protege_de_l_eviction():
    c = LRU(capacity=2, ttl=300)
    c.set("a", 1); c.set("b", 2)
    c.get("a")                                     # on TOUCHE "a" : il redevient le plus récent
    c.set("c", 3)                                  # une entrée doit partir…
    assert c.get("a") == 1                         # …c'est "b", pas "a"
    assert c.get("b") is None                      # LRU = moins récemment UTILISÉ, pas le plus ancien posé
Chez toi, cette semaine.In your codebase, this week.

Trouve le cache qu'aucune écriture n'invalide — le mensonge à retardement du chapitre 4. Un point de départ : grep -rniE "cache\.(set|put)|@cache|@lru_cache|@memoize" src/, puis, pour chaque clé cachée, cherche le cache.delete (ou l'update) correspondant sur les chemins d'écriture de la même donnée. Aucun ? Alors seul le TTL rafraîchit, et tu sers du périmé jusqu'à expiration. La question à poser : « quand cette donnée change, qu'est-ce qui efface la copie — et combien de temps est-ce que je tolère qu'elle mente ? »Find the cache no write invalidates — chapter 4's delayed-action lie. A starting point: grep -rniE "cache\.(set|put)|@cache|@lru_cache|@memoize" src/, then, for each cached key, look for the matching cache.delete (or update) on the write paths of the same data. None? Then only the TTL refreshes it, and you serve stale until expiry. The question to ask: 'when this data changes, what erases the copy — and how long do I tolerate it lying?'