Cache et API med høj trafik ved først at måle og derefter placere et cache-aside-lag i Redis foran de langsomste og mest læste endpoints, med eksplicitte TTL'er, invalidering ved skrivning og beskyttelse mod cache stampedes. Brug Elasticsearch til søgning og filtrerede lister, som den primære database håndterer dårligt, og cache aldrig brugerspecifikke data eller data, der skal være strengt konsistente, uden et bevidst design.
Mål P95-latensen, før du tilføjer nogen cache
Gennemsnit skjuler de forespørgsler, brugerne klager over. Følg P95- og P99-latensen pr. endpoint sammen med antallet af forespørgsler, så du kan se, hvilke ruter der både er langsomme og meget brugt.
Find derefter ud af, hvor tiden går. Et trace eller en simpel opdeling af tidsforbruget pr. forespørgsel viser som regel, om omkostningen ligger i en langsom databaseforespørgsel, gentagne databaseforespørgsler, et kald til et eksternt API eller serialisering. At cache et svar, hvis egentlige problem er et manglende indeks, skjuler kun problemet indtil næste cache miss.
- Registrér P50, P95 og P99 pr. endpoint, ikke kun ét samlet tal
- Rangér endpoints efter antal forespørgsler ganget med latens for at se, hvor caching betaler sig mest
- Tjek forholdet mellem læsninger og skrivninger, for data, der læses langt oftere, end de ændres, er de bedste kandidater
- Ret manglende indekser og N+1-forespørgsler, før du cacher uden om dem
Brug cache-aside som standardmønster
Med cache-aside tjekker applikationen først Redis. Ved et hit returnerer den den cachede værdi. Ved et miss læser den fra databasen, skriver resultatet til Redis med en TTL og returnerer det.
Mønsteret bevarer databasen som den autoritative kilde og fejler på en sikker måde. Hvis Redis er langsom eller utilgængelig, falder applikationen tilbage på databasen, med en kort timeout og en circuit breaker, så en cache i problemer ikke gør alle forespørgsler langsommere.
Design nøglerne bevidst. Medtag ressourcetypen, id'et, API-versionen og alle parametre, der ændrer svaret, som sprog eller side. En forudsigelig nøglestruktur er det, der senere gør målrettet invalidering mulig.
Sæt TTL'er efter, hvor forældede data må være, og invalidér ved ændringer
En TTL er en forretningsbeslutning skrevet som et tal. Spørg, hvor gamle dataene må være, før en bruger eller et system længere nede i kæden tager skade, og sæt TTL'en ud fra svaret. En live kampstilling og en arkiveret artikel tåler meget forskellig grad af forældelse.
Hvis du kun stoler på udløb, leverer du forældede data, indtil TTL'en løber ud. For data, som din egen applikation ændrer, skal du slette eller overskrive nøglen, efter at skrivningen er committet, helst ud fra en hændelse, der udsendes efter transaktionen, så cachen aldrig indeholder data, som databasen har rullet tilbage.
Læg en lille tilfældig variation (jitter) på TTL'erne, så nøgler, der er skrevet samtidig, ikke alle udløber i samme øjeblik.
- Kort TTL på få sekunder for data, der ændrer sig hurtigt, og hvor lidt forældelse er acceptabel
- Længere TTL plus invalidering ved skrivning for data, du selv styrer, og som sjældent ændres
- Versionerede nøgler, hvor et nyt versionsnummer invaliderer en hel gruppe nøgler på én gang
Beskyt databasen mod cache stampedes
En stampede opstår, når en populær nøgle udløber, og mange samtidige forespørgsler får et miss på samme tid og alle sender den samme dyre databaseforespørgsel. På et API med høj trafik kan det overbelaste databasen præcis i det øjeblik, trafikken topper.
Kombinér to eller flere af disse forsvar på dine mest belastede nøgler.
- Sammenlægning af forespørgsler: én forespørgsel genopbygger nøglen under en kort Redis-lås sat med NX og et udløb, mens de andre venter kort eller leverer den forrige værdi
- Stale-while-revalidate: gem et blødt udløb i selve værdien, fortsæt med at levere den forældede kopi efter det, og opdater i baggrunden
- Tidlig sandsynlighedsbaseret opdatering: genopbyg af og til en populær nøgle, før den udløber, med en sandsynlighed, der stiger, jo tættere udløbet kommer
- Forvarmning: fyld kendte populære nøgler, før en planlagt trafiktop, for eksempel en stor livebegivenhed
Brug Elasticsearch til søgning og lister, ikke som generel cache
Redis er bedst til opslag på nøgle og værdi: et enkelt objekt, et beregnet fragment, en tæller til rate limiting. Elasticsearch passer til en anden opgave: fritekstsøgning, facetterede filtre og sorterede lister, som er dyre at beregne i en relationel database.
Behandl Elasticsearch-indekset som en læsemodel, der fødes fra den primære database gennem ændringshændelser eller en planlagt synkronisering, og acceptér, at det først bliver konsistent med en vis forsinkelse. Lad databasen være autoritativ for skrivninger og for alt, der skal være præcist.
På NorthStar Networks sportsmedieplatform, som betjener 50M+ brugere om måneden, byggede vores udviklere kritiske API'er om og redesignede cachingen på tværs af Redis og Elasticsearch, hvilket reducerede P95-svartiderne.
Vid, hvad der ikke skal caches
Den dyreste cachingfejl er at levere én brugers data til en anden. Gennemgå hvert cachet endpoint for, om identiteten indgår i nøglen, og test det med to forskellige konti før release.
- Svar, der afhænger af kalderens identitet eller rettigheder, medmindre nøglen indeholder brugeren eller rollen
- Data, der skal være strengt konsistente, som saldi, lagerbeholdning ved checkout eller alt, der bruges i beslutninger om adgang
- Endpoints, der skriver, engangstokens og alt med sideeffekter
- Endpoints med lav trafik, hvor en cache tilføjer kompleksitet og en ny måde at fejle på for en lille gevinst
- Meget store payloads, der skubber mange mindre og mere brugte nøgler ud
Gør cachen observerbar, ellers kan du ikke stole på den
Følg hitraten pr. nøglepræfiks, Redis' hukommelsesforbrug og evictions, latensen på Redis-kommandoer og belastningen af databasen side om side med API'ets P95 på samme dashboard. Når P95 flytter sig, skal du inden for få minutter kunne se, om det skyldes cachen, databasen eller en tjeneste længere oppe i kæden.
Opsæt alarmer ved et pludseligt fald i hitraten, som ofte betyder, at en udrulning har ændret et nøgleformat, og ved stigende evictions, som betyder, at cachen er for lille til de data, den skal rumme.
Vi påtager os arbejde med API-performance som en afgrænset opgave: måling, redesign af cachinglaget og overdragelse af dashboards og runbooks til det team, der driver det.
Det vigtigste
- Mål P95 pr. endpoint, og ret problemer i databaseforespørgslerne, før du tilføjer en cache.
- Cache-aside i Redis er det sikreste standardvalg, fordi databasen forbliver den autoritative kilde.
- Sæt TTL'er ud fra, hvor forældede dataene må være, og invalidér ved skrivning for data, du selv styrer.
- Beskyt populære nøgler mod stampedes med låse, stale-while-revalidate eller forvarmning.
- Brug Elasticsearch som en læsemodel, der bliver konsistent med en vis forsinkelse, til søgning og lister, ikke som generel cache.
FAQ
Skal jeg bruge Redis eller Elasticsearch til at cache API-svar?
Brug Redis til caching af objekter, beregnede fragmenter og tællere på nøgle og værdi, fordi opslag på nøgle er hurtige og nemme at invalidere. Brug Elasticsearch, når det dyre er søgning, filtrering eller sortering på tværs af mange poster, og behandl indekset som en læsemodel frem for en cache.
Hvad er en god TTL for API-svar?
Der findes ingen universel værdi. Sæt hver TTL ud fra, hvor forældede dataene kan være uden at skade en bruger, brug få sekunder for data, der ændrer sig hurtigt, og kombinér længere TTL'er med invalidering ved skrivning. Tilføj tilfældig jitter, så relaterede nøgler ikke udløber samtidig.
Hvordan undgår jeg en cache stampede i Redis?
Lad kun én forespørgsel genopbygge en udløbet nøgle ved at tage en kort lås med SET og NX-indstillingen, og lad andre forespørgsler vente kort eller levere den forrige værdi. Stale-while-revalidate og tidlig opdatering af populære nøgler mindsker allerede fra starten antallet af hårde udløb.