Содержание

Кешировать в веб-приложении можно на четырёх уровнях — в браузере, на CDN, в приложении и в базе данных, — и чем ближе к пользователю кеш, тем быстрее ответ и тем сложнее его сбросить. Главный вопрос не «где кешировать», а «как узнать, что данные устарели»: для этого есть время жизни, сброс по событию и версионирование ключей.

Разберём уровни кеширования, стратегии сброса и ошибки, из-за которых пользователи видят старые данные.

На каких уровнях можно кешировать?

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

Уровень Что кешируется Как сбрасывается Подходит для
Браузер Статика, иногда ответы API Истечение срока, новые имена файлов JS, CSS, картинки, шрифты
CDN и прокси Страницы и файлы целиком Истечение срока, очистка по адресу или тегу Публичные страницы, каталоги, статьи
Приложение Результаты запросов и вычислений Время жизни, удаление ключа при изменении Справочники, агрегаты, тяжёлые отчёты
База данных Материализованные представления, агрегаты Обновление по расписанию или событию Аналитика, сводные таблицы

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

Как настроить кеширование в браузере и на CDN?

Кеширование в браузере и на CDN настраивают HTTP-заголовками: Cache-Control говорит, сколько и кому можно хранить ответ, а ETag позволяет переспросить сервер, изменилось ли что-то. Для статики лучший подход — длинное время хранения и имена файлов с хешем содержимого.

# Файлы сборки с хешем в имени: app.3f9a1c.js — хранить год, не перепроверять
Cache-Control: public, max-age=31536000, immutable

# Публичная страница: CDN хранит 10 минут, потом отдаёт старую и обновляет в фоне
Cache-Control: public, s-maxage=600, stale-while-revalidate=60

# Личный кабинет: не хранить в общих кешах
Cache-Control: private, no-store

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

Трёхмерная визуализация облачных вычислений Фото: Unsplash

Как сбрасывать кеш, чтобы не показывать устаревшие данные?

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

Три стратегии сброса

  1. Время жизни (TTL). Просто и надёжно, но данные могут быть устаревшими до конца срока. Подходит, когда несколько минут задержки допустимы.
  2. Сброс по событию. При изменении записи приложение удаляет связанные ключи. Данные свежие, но нужно помнить все места, где эти данные закешированы.
  3. Версионирование ключей. В ключ добавляется версия или время последнего изменения: product:42:v17. Новая версия — новый ключ, старый истекает сам.

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

$key = sprintf('post:%d:html:%d', $post->id, $post->updated_at->getTimestamp());

$html = Cache::remember($key, now()->addWeek(), fn () => $renderer->render($post));

Что такое лавина запросов и как её избежать?

Лавина запросов — это ситуация, когда популярная запись в кеше истекает и сотни одновременных запросов разом идут в базу за одними и теми же данными. База, которая спокойно работала с кешем, падает под внезапной нагрузкой.

Как защититься:

  • блокировка на пересчёт: только один процесс вычисляет новое значение, остальные ждут или получают старое;
  • отдача устаревшего значения на время обновления — как stale-while-revalidate на CDN;
  • случайный разброс TTL, чтобы тысячи ключей не истекали в одну секунду;
  • прогрев кеша после выкладки и сброса — заранее заполнить самые популярные записи.

Какие ошибки кеширования встречаются чаще всего?

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

Типичные ошибки:

  1. Страница с личными данными закеширована на CDN — пользователи видят чужие корзины.
  2. Изменение в админке не видно на сайте часами, потому что забыли про сброс.
  3. Кеш маскирует запрос без индекса — при промахе страница открывается десять секунд. Сначала индексы, потом кеш; об этом — в статье индексы в PostgreSQL.
  4. Ключи без префикса и версии — невозможно сбросить группу связанных записей.
  5. Нет мониторинга доли попаданий — непонятно, работает ли кеш вообще.

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

Коротко

  • Кешируйте на нескольких уровнях: браузер, CDN, приложение, база.
  • Для статики — долгий срок и хеш в имени файла, для публичных страниц — CDN с stale-while-revalidate.
  • Сбрасывайте кеш по событию, страхуйте временем жизни, для составных данных используйте версии ключей.
  • Защищайтесь от лавины запросов блокировками, выдачей устаревшего значения и разбросом TTL.
  • Никогда не кешируйте персональные данные в общих кешах и не прячьте кешем медленные запросы.

Частые вопросы

  • Нужно ли кешировать ответы API?

    Публичные и редко меняющиеся — да: справочники, каталоги, контент. Ответы с персональными данными кешируют только на стороне клиента или в приложении с ключом, привязанным к пользователю.

  • Как понять, что кеш работает?

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

  • Почему после выкладки пользователи видят старую версию сайта?

    Чаще всего потому, что файлы сборки закешированы без хеша в имени или HTML-страница закеширована надолго. Решение — хеш в именах статики и короткое время хранения для HTML.