[{"data":1,"prerenderedAt":519},["ShallowReactive",2],{"print-topic-\u002Flearn\u002Fen\u002Fcourses\u002Fcaching":3,"lessons-\u002Flearn\u002Fen\u002Fcourses\u002Fcaching":44,"i-lucide:printer":515},{"id":4,"title":5,"access":6,"body":7,"description":14,"draft":15,"estimatedMinutes":16,"extension":17,"featured":15,"icon":18,"lang":19,"level":20,"meta":21,"navigation":22,"order":23,"path":24,"prerequisites":25,"resources":26,"seo":32,"sku":30,"stem":33,"subjects":34,"summary":36,"tags":37,"updated":42,"__hash__":43},"courses\u002Flearn\u002Fen\u002Fcourses\u002Fcaching\u002Findex.md","Caching strategies","premium",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",3,[],"Read-through, write-behind, invalidation and the failure modes each one buys.",false,40,"md","lucide:layers","en","advanced",{},true,1,"\u002Flearn\u002Fen\u002Fcourses\u002Fcaching",[],[27],{"label":28,"file":29,"access":6,"sku":30,"size":31},"Caching decision matrix","caching-decision-matrix.pdf","topic-caching-strategies","310 KB",{"title":5,"description":14},"learn\u002Fen\u002Fcourses\u002Fcaching\u002Findex",[35],"system-design","A cache is a bet that reading stale data is cheaper than reading correct data.\nThe strategies differ in who writes to the cache, when, and what happens when\nthe cache and the source disagree. Cache-aside is the default because it fails\nsafely. Write-behind is the fastest and loses data on a crash. Almost every\nproduction incident involving a cache comes down to invalidation, stampedes, or\na cache that was quietly doing nothing at all.\n",[38,39,40,41],"caching","performance","consistency","redis","2026-08-06","mqYWifp5mzccLyzSY681N-Yn1wymognvHpRRfhOtFBI",[45,274],{"id":46,"title":47,"access":48,"body":49,"description":268,"extension":17,"lang":19,"meta":269,"navigation":22,"order":23,"partial":22,"path":270,"seo":271,"stem":272,"__hash__":273},"lessons\u002Flearn\u002Fen\u002Fcourses\u002Fcaching\u002F01.strategies.md","The four strategies","free",{"type":8,"value":50,"toc":262},[51,55,59,64,67,219,222,226,229,232,236,239,242,246,249,252,258],[52,53,47],"h1",{"id":54},"the-four-strategies",[56,57,58],"p",{},"Every caching pattern answers two questions: who populates the cache, and when is\nthe source of truth updated relative to it.",[60,61,63],"h2",{"id":62},"cache-aside","Cache-aside",[56,65,66],{},"The application checks the cache, and on a miss reads the database and populates\nthe cache itself. The cache has no knowledge of the database.",[68,69,73],"pre",{"className":70,"code":71,"language":72,"meta":11,"style":11},"language-python shiki shiki-themes github-dark-dimmed github-dark-dimmed","def get_user(user_id):\n    cached = cache.get(f\"user:{user_id}\")\n    if cached is not None:\n        return cached\n    user = db.query(\"SELECT * FROM users WHERE id = %s\", user_id)\n    cache.set(f\"user:{user_id}\", user, ttl=300)\n    return user\n","python",[74,75,76,92,126,147,156,178,210],"code",{"__ignoreMap":11},[77,78,80,84,88],"span",{"class":79,"line":23},"line",[77,81,83],{"class":82},"sEYkB","def",[77,85,87],{"class":86},"s-wk_"," get_user",[77,89,91],{"class":90},"sM9_K","(user_id):\n",[77,93,95,98,101,104,107,111,114,117,120,123],{"class":79,"line":94},2,[77,96,97],{"class":90},"    cached ",[77,99,100],{"class":82},"=",[77,102,103],{"class":90}," cache.get(",[77,105,106],{"class":82},"f",[77,108,110],{"class":109},"szYpP","\"user:",[77,112,113],{"class":82},"{",[77,115,116],{"class":90},"user_id",[77,118,119],{"class":82},"}",[77,121,122],{"class":109},"\"",[77,124,125],{"class":90},")\n",[77,127,128,131,134,137,140,144],{"class":79,"line":12},[77,129,130],{"class":82},"    if",[77,132,133],{"class":90}," cached ",[77,135,136],{"class":82},"is",[77,138,139],{"class":82}," not",[77,141,143],{"class":142},"sQdni"," None",[77,145,146],{"class":90},":\n",[77,148,150,153],{"class":79,"line":149},4,[77,151,152],{"class":82},"        return",[77,154,155],{"class":90}," cached\n",[77,157,159,162,164,167,170,173,175],{"class":79,"line":158},5,[77,160,161],{"class":90},"    user ",[77,163,100],{"class":82},[77,165,166],{"class":90}," db.query(",[77,168,169],{"class":109},"\"SELECT * FROM users WHERE id = ",[77,171,172],{"class":82},"%s",[77,174,122],{"class":109},[77,176,177],{"class":90},", user_id)\n",[77,179,181,184,186,188,190,192,194,196,199,203,205,208],{"class":79,"line":180},6,[77,182,183],{"class":90},"    cache.set(",[77,185,106],{"class":82},[77,187,110],{"class":109},[77,189,113],{"class":82},[77,191,116],{"class":90},[77,193,119],{"class":82},[77,195,122],{"class":109},[77,197,198],{"class":90},", user, ",[77,200,202],{"class":201},"siJwO","ttl",[77,204,100],{"class":82},[77,206,207],{"class":142},"300",[77,209,125],{"class":90},[77,211,213,216],{"class":79,"line":212},7,[77,214,215],{"class":82},"    return",[77,217,218],{"class":90}," user\n",[56,220,221],{},"This is the default for a reason: if the cache is down, every request simply\nbecomes a database read. Degraded, not broken.",[60,223,225],{"id":224},"read-through","Read-through",[56,227,228],{},"The cache sits inline. The application only talks to the cache, which fetches from\nthe database on a miss. Logic moves out of the application and into the cache layer.",[56,230,231],{},"Cleaner call sites, but the cache is now on the critical path — if it is unavailable,\nreads fail entirely rather than falling back.",[60,233,235],{"id":234},"write-through","Write-through",[56,237,238],{},"Writes go to the cache, which synchronously writes to the database before\nacknowledging. The cache is never stale.",[56,240,241],{},"The cost is write latency: every write pays for both hops. Worth it when reads\nvastly outnumber writes and stale reads are unacceptable.",[60,243,245],{"id":244},"write-behind","Write-behind",[56,247,248],{},"Writes land in the cache and are flushed to the database asynchronously.",[56,250,251],{},"Fastest writes available, and the only strategy that can lose acknowledged data. A\ncrash between acknowledgement and flush loses whatever was buffered.",[253,254,255],"warning",{},[56,256,257],{},"Write-behind is appropriate for metrics, view counts and analytics events. It is not\nappropriate for anything a user would notice going missing — orders, payments,\nmessages. The performance gain is real, and so is the data loss.",[259,260,261],"style",{},"html pre.shiki code .sEYkB, html code.shiki .sEYkB{--shiki-default:#F47067;--shiki-dark:#F47067}html pre.shiki code .s-wk_, html code.shiki .s-wk_{--shiki-default:#DCBDFB;--shiki-dark:#DCBDFB}html pre.shiki code .sM9_K, html code.shiki .sM9_K{--shiki-default:#ADBAC7;--shiki-dark:#ADBAC7}html pre.shiki code .szYpP, html code.shiki .szYpP{--shiki-default:#96D0FF;--shiki-dark:#96D0FF}html pre.shiki code .sQdni, html code.shiki .sQdni{--shiki-default:#6CB6FF;--shiki-dark:#6CB6FF}html pre.shiki code .siJwO, html code.shiki .siJwO{--shiki-default:#F69D50;--shiki-dark:#F69D50}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":11,"searchDepth":12,"depth":12,"links":263},[264,265,266,267],{"id":62,"depth":94,"text":63},{"id":224,"depth":94,"text":225},{"id":234,"depth":94,"text":235},{"id":244,"depth":94,"text":245},"Cache-aside, read-through, write-through and write-behind compared.",{},"\u002Flearn\u002Fen\u002Fcourses\u002Fcaching\u002Fstrategies",{"title":47,"description":268},"learn\u002Fen\u002Fcourses\u002Fcaching\u002F01.strategies","sh2NUIv6r7JjhJWD_OzLrfaRSj7urg2L7nJtqWFgm04",{"id":275,"title":276,"access":6,"body":277,"description":509,"extension":17,"lang":19,"meta":510,"navigation":22,"order":94,"partial":15,"path":511,"seo":512,"stem":513,"__hash__":514},"lessons\u002Flearn\u002Fen\u002Fcourses\u002Fcaching\u002F02.invalidation.md","Invalidation and stampedes",{"type":8,"value":278,"toc":503},[279,282,285,289,292,296,299,302,305,312,318,387,393,399,403,406,445,448,451,455,458,497,500],[52,280,276],{"id":281},"invalidation-and-stampedes",[56,283,284],{},"Caching is easy. Knowing when the cached value stopped being true is the hard part.",[60,286,288],{"id":287},"ttl-is-a-guess-about-staleness","TTL is a guess about staleness",[56,290,291],{},"A TTL says \"I accept being wrong for up to this long\". Choosing one means deciding\nhow much staleness the product tolerates — a question for the product, not the\ninfrastructure.",[60,293,295],{"id":294},"the-thundering-herd","The thundering herd",[56,297,298],{},"A popular key expires. A thousand concurrent requests miss simultaneously. All\nthousand hit the database with the same query.",[56,300,301],{},"The database, sized for the cached load, falls over. Recovery repopulates the cache,\nthe key expires again, and the pattern repeats on a cycle matching the TTL.",[56,303,304],{},"Three defences:",[56,306,307,311],{},[308,309,310],"strong",{},"Locking."," The first miss takes a lock and recomputes; everyone else waits or\nserves stale. Correct, but adds a coordination point.",[56,313,314,317],{},[308,315,316],{},"Probabilistic early expiry."," Recompute before expiry with a probability that\nrises as the TTL approaches, so refreshes spread out instead of synchronising.",[68,319,321],{"className":70,"code":320,"language":72,"meta":11,"style":11},"def should_refresh(computed_at, ttl, beta=1.0):\n    age = now() - computed_at\n    return age + beta * delta * math.log(random.random()) >= ttl\n",[74,322,323,341,357],{"__ignoreMap":11},[77,324,325,327,330,333,335,338],{"class":79,"line":23},[77,326,83],{"class":82},[77,328,329],{"class":86}," should_refresh",[77,331,332],{"class":90},"(computed_at, ttl, beta",[77,334,100],{"class":82},[77,336,337],{"class":142},"1.0",[77,339,340],{"class":90},"):\n",[77,342,343,346,348,351,354],{"class":79,"line":94},[77,344,345],{"class":90},"    age ",[77,347,100],{"class":82},[77,349,350],{"class":90}," now() ",[77,352,353],{"class":82},"-",[77,355,356],{"class":90}," computed_at\n",[77,358,359,361,364,367,370,373,376,378,381,384],{"class":79,"line":12},[77,360,215],{"class":82},[77,362,363],{"class":90}," age ",[77,365,366],{"class":82},"+",[77,368,369],{"class":90}," beta ",[77,371,372],{"class":82},"*",[77,374,375],{"class":90}," delta ",[77,377,372],{"class":82},[77,379,380],{"class":90}," math.log(random.random()) ",[77,382,383],{"class":82},">=",[77,385,386],{"class":90}," ttl\n",[56,388,389,392],{},[308,390,391],{},"Jitter."," Add randomness to every TTL so keys written together do not expire\ntogether. The cheapest fix and often sufficient.",[394,395,396],"danger",{},[56,397,398],{},"Populating a cache during a deploy or a warm-up script writes thousands of keys\nwithin the same second. With a fixed TTL they all expire within the same second too,\nproducing a synchronised stampede hours later that looks completely unrelated to\nthe deploy that caused it. Always jitter.",[60,400,402],{"id":401},"explicit-invalidation","Explicit invalidation",[56,404,405],{},"Delete the key on write:",[68,407,409],{"className":70,"code":408,"language":72,"meta":11,"style":11},"def update_user(user_id, data):\n    db.update(user_id, data)\n    cache.delete(f\"user:{user_id}\")\n",[74,410,411,421,426],{"__ignoreMap":11},[77,412,413,415,418],{"class":79,"line":23},[77,414,83],{"class":82},[77,416,417],{"class":86}," update_user",[77,419,420],{"class":90},"(user_id, data):\n",[77,422,423],{"class":79,"line":94},[77,424,425],{"class":90},"    db.update(user_id, data)\n",[77,427,428,431,433,435,437,439,441,443],{"class":79,"line":12},[77,429,430],{"class":90},"    cache.delete(",[77,432,106],{"class":82},[77,434,110],{"class":109},[77,436,113],{"class":82},[77,438,116],{"class":90},[77,440,119],{"class":82},[77,442,122],{"class":109},[77,444,125],{"class":90},[56,446,447],{},"Delete rather than overwrite. Overwriting introduces a race where two concurrent\nwriters leave the cache holding the older value.",[56,449,450],{},"The ordering matters too — database first, then cache. Reversed, a concurrent read\nbetween the two steps repopulates the cache with the old row and the stale value\nsurvives its TTL.",[60,452,454],{"id":453},"cached-nothing","Cached nothing",[56,456,457],{},"The failure mode nobody instruments: a cache that works perfectly and is never hit,\nbecause keys are built from something that varies per request.",[68,459,461],{"className":70,"code":460,"language":72,"meta":11,"style":11},"cache.set(f\"user:{user_id}:{request_id}\", user)  # unique every time\n",[74,462,463],{"__ignoreMap":11},[77,464,465,468,470,472,474,476,478,481,483,486,488,490,493],{"class":79,"line":23},[77,466,467],{"class":90},"cache.set(",[77,469,106],{"class":82},[77,471,110],{"class":109},[77,473,113],{"class":82},[77,475,116],{"class":90},[77,477,119],{"class":82},[77,479,480],{"class":109},":",[77,482,113],{"class":82},[77,484,485],{"class":90},"request_id",[77,487,119],{"class":82},[77,489,122],{"class":109},[77,491,492],{"class":90},", user)  ",[77,494,496],{"class":495},"s0RzX","# unique every time\n",[56,498,499],{},"Hit rate is zero. Latency is unchanged. Nothing errors. Monitor hit rate as a first\nclass metric, and alert when it drops — a cache silently doing nothing is a cost with\nno benefit.",[259,501,502],{},"html pre.shiki code .sEYkB, html code.shiki .sEYkB{--shiki-default:#F47067;--shiki-dark:#F47067}html pre.shiki code .s-wk_, html code.shiki .s-wk_{--shiki-default:#DCBDFB;--shiki-dark:#DCBDFB}html pre.shiki code .sM9_K, html code.shiki .sM9_K{--shiki-default:#ADBAC7;--shiki-dark:#ADBAC7}html pre.shiki code .sQdni, html code.shiki .sQdni{--shiki-default:#6CB6FF;--shiki-dark:#6CB6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szYpP, html code.shiki .szYpP{--shiki-default:#96D0FF;--shiki-dark:#96D0FF}html pre.shiki code .s0RzX, html code.shiki .s0RzX{--shiki-default:#768390;--shiki-dark:#768390}",{"title":11,"searchDepth":12,"depth":12,"links":504},[505,506,507,508],{"id":287,"depth":94,"text":288},{"id":294,"depth":94,"text":295},{"id":401,"depth":94,"text":402},{"id":453,"depth":94,"text":454},"TTLs, explicit invalidation, and what happens when a hot key expires.",{},"\u002Flearn\u002Fen\u002Fcourses\u002Fcaching\u002Finvalidation",{"title":276,"description":509},"learn\u002Fen\u002Fcourses\u002Fcaching\u002F02.invalidation","fahMLdo2hiDD5K_BQHGZCeYMOkIquLGdRIDAy0WkguI",{"left":516,"top":516,"width":517,"height":517,"rotate":516,"vFlip":15,"hFlip":15,"body":518},0,24,"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6\"\u002F>\u003Crect width=\"12\" height=\"8\" x=\"6\" y=\"14\" rx=\"1\"\u002F>\u003C\u002Fg>",1787597904595]