Паттерны кэширования

Что это такое

Место кэша и способ его наполнения. Стоять он может рядом с приложением, перед базой данных или опрашиваться самим приложением, а наполняться — при промахе чтения, при записи либо заранее, фоновой задачей. От расположения зависит ответ на три вопроса: что именно хранится, кто наполняет кэш и что происходит, когда он пуст.

Почему это важно

От устройства зависит не только производительность, но и поведение при отказах. Кэш, который опрашивается на каждом чтении и никогда не наполняется, корректен и бесполезен; наполняемый при записи (write-through, сквозная запись) и не имеющий срока жизни — быстр и в конце концов неверен; тот, что приложение обходит при промахе, быстр ровно до всплеска трафика, когда все промахи разом приходят в базу. Решить это заранее дёшево, а обнаружить во время инцидента — нет.

Ключевые идеи

Кэш рядом с чтением, наполняемый при промахе

Самый распространённый паттерн — cache-aside (кэш в стороне): кэш читается первым, при промахе запрос уходит в источник, а результат сохраняется с истечением и возвращается. Никакого согласования с записью такой кэш не требует и сам наполняется только теми данными, которые действительно запрашивают. Слабых мест у него два: первый запрос после истечения платит полную стоимость, а всплеск одновременных промахов по одному ключу целиком доходит до источника.

читать кэш -> попадание? вернуть
           -> промах -> читать источник -> сохранить с ttl -> вернуть

Оградите источник от лавины промахов

Когда популярный ключ истекает, все одновременные запросы промахиваются по нему разом, и источник получает всплеск ровно тогда, когда хуже всего его выносит. Уберечь его можно двумя способами: подать одному вызывающему, а остальных заставить подождать, либо добавить к сроку жизни небольшой случайный разброс, чтобы ключи не истекали вместе. Именно так кэш чаще всего делает отказ хуже, а не лучше.

Истечение — основной механизм безопасности, и это должен быть осознанный выбор

Срок жизни ограничивает, как долго устаревшее значение может выжить, даже если инвалидацию пропустили. Его выбор — заявление о том, какую устарелость приложение терпит: секунды для цены, часы для списка стран. Кэш без срока жизни превращается в копию, способную расходиться бесконечно, и расхождение обнаружится в самый неподходящий момент.

Храните ту форму, которая нужна читателю

Кэшировать строку как она получена и кэшировать готовый ответ — решения разные: готовый ответ снимает больше работы, но переиспользовать его можно, только если в нём нет ничего, что зависит от того, кто запросил, а кэшированная сущность переиспользуется шире, зато её ещё нужно собрать в ответ.

Кэш — не база данных и никогда не должен быть единственной копией

Хранилище живёт в памяти, и перезапуск или вытеснение способно убрать любой ключ в любой момент. Запись, существующая только в кэше, будет потеряна — это неизбежно. Значение, которое должно выжить, держат в хранилище, где оно действительно сохраняется, а кэш оставляют лишь копией. Однако путаница между ними превращает инструмент производительности в инцидент с потерей данных.

Доля запросов, обслуженных из кэша, говорит, делает ли он вообще что-нибудь. Низкая доля попаданий означает, что ключи не переиспользуются, записи истекают слишком быстро или кэш обходят, — и для каждого случая исправление своё.

Сессии и счётчики — другое применение того же хранилища

Сессия в кэше даёт каждому экземпляру приложения общий взгляд на то, кто вошёл в систему, а срок жизни даёт ей естественный конец. Счётчики и ограничение частоты опираются на атомарный инкремент; ради него и удобно хранилище с операциями, а не с одними значениями. Применения законные, но кэшированием они как раз не являются.

Практика

Поставьте кэш и измерьте долю попаданий

Поставьте кэш перед дорогим чтением, задайте срок жизни и запишите долю попаданий за период на реалистичном трафике.

Готово, когда: вы можете назвать долю попаданий и ту часть запросов, которую теперь получает источник, и у вас есть одно число, показывающее изменение задержки.

Сделайте всплеск истечений безопасным

Возьмите популярный ключ и опишите, что происходит, когда он истекает под нагрузкой, затем измените срок жизни так, чтобы одновременных промахов не было.

Готово, когда: вы можете объяснить, почему источник не получает всплеск, когда ключ истекает.

Частые ошибки

  • Кэш без срока жизни — копия может расходиться бесконечно; исправить: задайте срок жизни по допустимой устарелости.
  • Все запросы промахиваются одновременно после истечения — источник получает всплеск в худший момент; исправить: подайте ответ одному, а остальным велите подождать, или разбросайте сроки жизни.
  • Кэш как единственная копия — перезапуск теряет данные, которые нигде не сохранялись; исправить: сначала пишите в источник истины.
  • Кэширование ответа, зависящего от пользователя, без пользователя в ключе — данные одного пользователя отдаются другому; исправить: включите в ключ всё, от чего зависит ответ.
  • Долю попаданий никто не измеряет — кэш может добавлять риск и стоимость без всякой пользы; исправить: инструментируйте долю попаданий и просматривайте её.

Что читать дальше