Vytížené API cachujte tak, že nejdřív měříte, pak před nejpomalejší a nejčtenější endpointy předřadíte vrstvu cache-aside v Redisu s explicitními TTL, invalidací při zápisu a ochranou proti lavině požadavků (cache stampede). Elasticsearch používejte na vyhledávání a filtrované výpisy, které primární databáze zvládá špatně, a data vázaná na konkrétního uživatele nebo vyžadující striktní konzistenci nikdy necachujte bez promyšleného návrhu.
Než přidáte jakoukoli cache, změřte latenci P95
Průměry skrývají právě ty požadavky, na které si uživatelé stěžují. Sledujte latenci P95 a P99 pro každý endpoint spolu s objemem požadavků, abyste viděli, které routy jsou zároveň pomalé a hodně používané.
Pak zjistěte, kde se čas ztrácí. Trasování (trace) nebo jednoduchý rozpis časů pro každý požadavek obvykle ukáže, zda jde o pomalý dotaz, opakované dotazy, volání externího API, nebo serializaci. Cachování odpovědi, jejímž skutečným problémem je chybějící index, ten problém jen skryje do dalšího cache miss.
- Zaznamenávejte P50, P95 a P99 pro každý endpoint, ne jen jedno celkové číslo
- Seřaďte endpointy podle objemu požadavků vynásobeného latencí, abyste viděli, kde se cache vyplatí nejvíc
- Zkontrolujte poměr čtení a zápisů: nejlepší kandidát jsou data, která se čtou mnohem častěji, než se mění
- Chybějící indexy a dotazy N+1 opravte dřív, než je obalíte cache
Jako výchozí vzor používejte cache-aside
Při cache-aside aplikace nejdřív zkontroluje Redis. Při zásahu (hit) vrátí hodnotu z cache. Při minutí (miss) načte data z databáze, zapíše výsledek do Redisu s TTL a vrátí ho.
Tento vzor ponechává databázi jako zdroj pravdy a při selhání se chová bezpečně. Když je Redis pomalý nebo nedostupný, aplikace se vrátí k databázi, s krátkým timeoutem a circuit breakerem, aby cache v potížích nezpomalila každý požadavek.
Klíče navrhujte promyšleně. Zahrňte do nich typ zdroje, identifikátor, verzi API a každý parametr, který mění odpověď, například jazyk nebo stránku. Předvídatelné schéma klíčů je to, co později umožní cílenou invalidaci.
TTL nastavte podle toho, jak stará data smějí být, a při změně invalidujte
TTL je obchodní rozhodnutí zapsané jako číslo. Zeptejte se, jak stará mohou data být, než to uživateli nebo navazujícímu systému uškodí, a podle odpovědi TTL nastavte. Průběžné skóre živého zápasu a archivní článek snesou velmi rozdílnou zastaralost.
Spoléhat jen na vypršení znamená servírovat zastaralá data, dokud TTL nevyprší. U dat, která mění vaše vlastní aplikace, klíč po potvrzení zápisu smažte nebo přepište, ideálně na základě události vyslané po transakci, aby cache nikdy nedržela data, která databáze vrátila zpět (rollback).
K TTL přidejte malý náhodný rozptyl (jitter), aby klíče zapsané ve stejnou chvíli nevypršely všechny naráz.
- Krátké TTL v řádu sekund pro rychle se měnící data, u kterých je mírná zastaralost přijatelná
- Delší TTL a invalidace při zápisu pro data, která máte pod kontrolou a která se mění zřídka
- Verzované klíče, kde zvýšení čísla verze invaliduje najednou celou skupinu klíčů
Chraňte databázi před cache stampede
Cache stampede nastává, když vyprší populární klíč a mnoho souběžných požadavků ho najednou mine, takže všechny posílají do databáze stejný nákladný dotaz. U vytíženého API to může databázi přetížit přesně ve chvíli, kdy provoz vrcholí.
Na nejžhavějších klíčích zkombinujte dvě nebo více z těchto obran.
- Slučování požadavků (request coalescing): klíč znovu sestaví jediný požadavek pod krátkým zámkem v Redisu nastaveným s NX a expirací, zatímco ostatní chvíli počkají nebo vrátí předchozí hodnotu
- Stale-while-revalidate: uložte do hodnoty měkkou expiraci, i po ní dál vracejte zastaralou kopii a obnovujte ji na pozadí
- Předčasná pravděpodobnostní obnova: horký klíč občas sestavte znovu ještě před vypršením, s pravděpodobností rostoucí, jak se vypršení blíží
- Předehřátí: známé horké klíče naplňte před plánovanou špičkou provozu, například před velkou živou akcí
Elasticsearch používejte na vyhledávání a výpisy, ne jako obecnou cache
Redis se nejlépe hodí na vyhledávání podle klíče: jeden objekt, vypočítaný fragment, počítadlo pro rate limiting. Elasticsearch plní jinou roli: fulltextové vyhledávání, fasetové filtry a řazené výpisy, jejichž výpočet je v relační databázi nákladný.
Index v Elasticsearchi berte jako model pro čtení (read model) plněný z primární databáze prostřednictvím událostí o změnách nebo plánované synchronizace a smiřte se s tím, že je konzistentní jen s odstupem (eventually consistent). Pro zápisy a pro vše, co musí být přesné, zůstává autoritou databáze.
Na sportovní mediální platformě NorthStar Network, která obsluhuje 50M+ uživatelů měsíčně, naši inženýři přestavěli kritická API a přepracovali cachování v Redisu a Elasticsearchi, čímž zkrátili doby odezvy P95.
Vězte, co necachovat
Nejdražší chyba v cachování je servírovat data jednoho uživatele jinému. U každého cachovaného endpointu zkontrolujte, zda klíč obsahuje identitu, a před vydáním ho otestujte se dvěma různými účty.
- Odpovědi, které závisejí na identitě nebo oprávněních volajícího, pokud klíč neobsahuje uživatele nebo roli
- Data, která musí být striktně konzistentní, například zůstatky, sklad při dokončení objednávky nebo cokoli, z čeho se rozhoduje o autorizaci
- Zapisující endpointy, jednorázové tokeny a vše s vedlejšími účinky
- Málo vytížené endpointy, kde cache za malý přínos přidá složitost a nový způsob selhání
- Velmi velké odpovědi, které vytlačí mnoho menších, častěji používaných klíčů
Cache musí být pozorovatelná, jinak jí nemůžete věřit
Sledujte poměr zásahů (hit ratio) pro každý prefix klíčů, využití paměti Redisu a vytlačování klíčů (evictions), latenci příkazů Redisu a zátěž databáze, a to na stejném dashboardu jako P95 API. Když se P95 pohne, měli byste do pár minut poznat, zda to způsobila cache, databáze, nebo služba o úroveň výš.
Nastavte upozornění na náhlý pokles poměru zásahů, který často znamená, že nasazení změnilo formát klíče, a na rostoucí počet vytlačených klíčů, který znamená, že cache je na svou pracovní sadu příliš malá.
Práci na výkonu API přebíráme jako jasně vymezený blok: měření, přepracování vrstvy cache a předání dashboardů a provozních postupů (runbooků) týmu, který ji provozuje.
Hlavní body
- Změřte P95 pro každý endpoint a problémy s dotazy opravte dřív, než přidáte cache.
- Cache-aside v Redisu je nejbezpečnější výchozí volba, protože zdrojem pravdy zůstává databáze.
- TTL nastavujte podle toho, jak stará data smějí být, a u dat pod vaší kontrolou invalidujte při zápisu.
- Horké klíče chraňte před stampede zámky, stale-while-revalidate nebo předehřátím.
- Elasticsearch používejte jako model pro čtení s konzistencí s odstupem pro vyhledávání a výpisy, ne jako obecnou cache.
Časté dotazy
Cachovat odpovědi API v Redisu, nebo v Elasticsearchi?
Redis používejte pro cachování objektů, vypočítaných fragmentů a počítadel podle klíče, protože vyhledání podle klíče je rychlé a snadno se invaliduje. Elasticsearch použijte, když je nákladnou částí vyhledávání, filtrování nebo řazení napříč mnoha záznamy, a jeho index berte jako model pro čtení, ne jako cache.
Jaké TTL je vhodné pro odpovědi API?
Univerzální hodnota neexistuje. Každé TTL nastavte podle toho, jak stará mohou data být, aniž by to uživateli uškodilo, pro rychle se měnící data použijte několik sekund a delší TTL kombinujte s invalidací při zápisu. Přidejte náhodný rozptyl, aby související klíče nevypršely současně.
Jak v Redisu zabránit cache stampede?
Nechte vypršený klíč znovu sestavit jen jeden požadavek, který si vezme krátký zámek pomocí SET s volbou NX, a ostatní požadavky ať chvíli počkají nebo vrátí předchozí hodnotu. Stale-while-revalidate a předčasná obnova horkých klíčů už samy snižují počet tvrdých vypršení.