Към съдържанието

Ръководства

Кеширане на API с висок трафик с Redis и Elasticsearch

· 6 мин. четене

Кеширайте API с висок трафик, като първо измерите, а после поставите слой cache-aside в Redis пред най-бавните и най-четените крайни точки, с изрично зададени TTL, инвалидиране при запис и защита от cache stampede. Използвайте Elasticsearch за търсене и филтрирани списъци, с които основната база данни се справя зле, и никога не кеширайте данни, специфични за потребителя, или данни, които трябва да са строго консистентни, без обмислен дизайн.

Измерете латентността P95, преди да добавите какъвто и да е кеш

Средните стойности скриват заявките, от които потребителите се оплакват. Следете латентността P95 и P99 за всяка крайна точка, заедно с обема заявки, за да видите кои маршрути са едновременно бавни и силно натоварени.

След това открийте къде отива времето. Трасиране или просто разбивка на времето за всяка заявка обикновено показва дали цената идва от бавна заявка към базата, от повтарящи се заявки, от извикване на външен API или от сериализацията. Кеширането на отговор, чийто истински проблем е липсващ индекс, само скрива този проблем до следващия пропуск в кеша.

  • Записвайте P50, P95 и P99 за всяка крайна точка, а не само една обща стойност
  • Подредете крайните точки по обема заявки, умножен по латентността, за да видите къде кеширането носи най-много
  • Проверете съотношението между четене и запис, защото данните, които се четат много по-често, отколкото се променят, са най-добрият кандидат
  • Поправете липсващите индекси и N+1 заявките, преди да ги заобикаляте с кеш

Използвайте cache-aside като модел по подразбиране

При cache-aside приложението първо проверява Redis. При попадение връща кешираната стойност. При пропуск чете от базата данни, записва резултата в Redis с TTL и го връща.

Този модел запазва базата данни като източник на истината и отказва безопасно. Ако Redis е бавен или недостъпен, приложението се връща към базата данни, с кратък таймаут и прекъсвач (circuit breaker), за да не забавя затрудненият кеш всяка заявка.

Проектирайте ключовете обмислено. Включете типа на ресурса, идентификатора, версията на API и всеки параметър, който променя отговора, като езика или страницата. Предвидимата схема на ключовете е това, което по-късно прави възможно целенасоченото инвалидиране.

Задайте TTL според това колко остарели могат да са данните, после инвалидирайте при промяна

TTL е бизнес решение, записано като число. Запитайте се колко стари могат да са данните, преди да навредят на потребител или на система надолу по веригата, и определете TTL според отговора. Резултатът от мач на живо и архивирана статия търпят много различна степен на остаряване.

Ако разчитате само на изтичането, ще връщате остарели данни, докато TTL не изтече. За данни, които собственото Ви приложение променя, изтривайте или презаписвайте ключа, след като записът бъде потвърден (commit), в идеалния случай чрез събитие, излъчено след транзакцията, за да не съдържа кешът никога данни, които базата е отменила.

Добавете малко случайно отклонение (jitter) към TTL, за да не изтичат едновременно всички ключове, записани по едно и също време.

  • Кратък TTL от няколко секунди за бързо променящи се данни, при които леко остаряване е приемливо
  • По-дълъг TTL плюс инвалидиране при запис за данни, които контролирате и които рядко се променят
  • Версионирани ключове, при които увеличаването на номера на версията инвалидира наведнъж цяла група ключове

Защитете базата данни от cache stampede

Cache stampede настъпва, когато популярен ключ изтече и много едновременни заявки получат пропуск наведнъж, като всички изпращат една и съща скъпа заявка към базата данни. При API с висок трафик това може да претовари базата точно в момента на пиковия трафик.

Комбинирайте две или повече от тези защити за най-натоварените си ключове.

  • Обединяване на заявките: една заявка изгражда ключа наново под кратко заключване в Redis, зададено с NX и срок на изтичане, докато останалите изчакват за кратко или връщат предишната стойност
  • Stale-while-revalidate: съхранявайте „мек” срок на изтичане в самата стойност, продължавайте да връщате остарялото копие и след него и опреснявайте във фонов режим
  • Ранно вероятностно опресняване: от време на време изграждайте наново натоварен ключ, преди да изтече, като вероятността расте с наближаването на изтичането
  • Предварително загряване: попълнете известните натоварени ключове преди планиран пик на трафика, например голямо събитие на живо

Използвайте Elasticsearch за търсене и списъци, а не като общ кеш

Redis е най-подходящ за търсене по ключ и стойност: един обект, изчислен фрагмент, брояч за ограничаване на заявките. Elasticsearch върши друга работа: пълнотекстово търсене, фасетни филтри и сортирани списъци, които е скъпо да се изчисляват в релационна база данни.

Третирайте индекса на Elasticsearch като модел за четене, захранван от основната база данни чрез събития за промени или планирана синхронизация, и приемете, че той е консистентен в крайна сметка (eventually consistent). Базата данни остава меродавна за записите и за всичко, което трябва да е точно.

В спортната медийна платформа на NorthStar Network, която обслужва 50M+ потребители месечно, нашите инженери преработиха архитектурата на критични API и преосмислиха кеширането с Redis и Elasticsearch, което намали времената за отговор P95.

Знайте какво да не кеширате

Най-скъпата грешка при кеширането е да върнете данните на един потребител на друг. Проверете всяка кеширана крайна точка дали ключът включва самоличността и я тествайте с два различни акаунта преди пускане.

  • Отговори, които зависят от самоличността или правата на извикващия, освен ако ключът не включва потребителя или ролята
  • Данни, които трябва да са строго консистентни, като салда, наличности при поръчка или всичко, което се използва при решения за оторизация
  • Крайни точки за запис, еднократни токени и всичко със странични ефекти
  • Крайни точки с малък трафик, където кешът добавя сложност и нов начин на отказ срещу малка полза
  • Много големи отговори, които изместват много по-малки и по-натоварени ключове

Направете кеша наблюдаем, иначе не можете да му се доверите

Следете процента попадения (hit ratio) за всеки префикс на ключовете, използваната памет и изместванията в Redis, латентността на командите в Redis и натоварването на базата данни, заедно с P95 на API, на едно и също табло. Когато P95 се промени, трябва да можете за минути да кажете дали причината е кешът, базата данни или външна услуга.

Настройте известия при внезапен спад на процента попадения, което често означава, че деплой е променил формата на ключовете, и при нарастване на изместванията, което означава, че кешът е твърде малък за работния си набор.

Поемаме работата по производителността на API като ясно определен блок: измерване, преработка на кеширащия слой и предаване на таблата и ръководствата за експлоатация (runbooks) на екипа, който го поддържа.

Основното накратко

  • Измерете P95 за всяка крайна точка и поправете проблемите със заявките, преди да добавите кеш.
  • Cache-aside в Redis е най-безопасният избор по подразбиране, защото базата данни остава източник на истината.
  • Задавайте TTL според това колко остарели могат да са данните и инвалидирайте при запис данните, които контролирате.
  • Защитете натоварените ключове от stampede със заключване, stale-while-revalidate или предварително загряване.
  • Използвайте Elasticsearch като консистентен в крайна сметка модел за четене при търсене и списъци, а не като общ кеш.

Въпроси и отговори

Redis или Elasticsearch за кеширане на отговорите на API?

Използвайте Redis за кеширане по ключ и стойност на обекти, изчислени фрагменти и броячи, защото търсенето по ключ е бързо и лесно се инвалидира. Използвайте Elasticsearch, когато скъпата част е търсенето, филтрирането или сортирането в много записи, и третирайте индекса му като модел за четене, а не като кеш.

Какъв TTL е подходящ за отговорите на API?

Няма универсална стойност. Определяйте всеки TTL според това колко остарели могат да са съответните данни, без да навредят на потребител, използвайте няколко секунди за бързо променящи се данни и съчетавайте по-дългите TTL с инвалидиране при запис. Добавете случайно отклонение (jitter), за да не изтичат свързаните ключове едновременно.

Как да предотвратите cache stampede в Redis?

Позволете само на една заявка да изгради наново изтекъл ключ, като вземе кратко заключване със SET и опцията NX, а другите заявки да изчакат за кратко или да върнат предишната стойност. Stale-while-revalidate и ранното опресняване на натоварените ключове намаляват броя на твърдите изтичания още от самото начало.

Кажете ни от какво имате нужда.

Нещо за изграждане, хора за намиране или въпрос, който чака отговор. В 30-минутен разговор Ви изслушваме и казваме честно как можем да помогнем и какво ще е нужно.