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

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

Слой браузера экономит повторные визиты без помощи сервера

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

app.3f9c2a.js -> Cache-Control: public, max-age=31536000, immutable

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

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

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

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

Сеть доставки ставит копии рядом с каждым посетителем

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

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

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

Правила кэширования при этом переезжают из сервера в конфигурацию сети: что считать статикой, как долго хранить, как сбрасывать при релизе. Основная ловушка новичков - закэшировать в сети то, что менять часто: страницы с ценами или личные ответы, замороженные на边缘 серверах, создают загадочные баги, которые не воспроизводятся на исходном сервере. Правило простое: сеть доставки держит то, что не зависит от того, кто спрашивает, а устаревшие копии на промежуточных точках создают расхождения, которые невозможно воспроизвести на исходном сервере разработчика.

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

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

Серверный кэш разгружает приложение и базу данных

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

async function getProduct(id) {
  const key = "product:" + id;
  const cached = await cache.get(key);
  if (cached) return cached;
  const product = await db.products.find(id);
  await cache.set(key, product, { ttl: 3600 });
  return product;
}

Иерархия серверного кэша строится по стоимости вычисления: сначала кэшируются страницы целиком или их фрагменты, потом результаты тяжёлых запросов, потом мелкие объекты.

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

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

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

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

Инвалидация - дисциплина которая делает кэш честным

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

Гигиена сброса при этом несложна, но требует постоянства: ключи строятся по соглашению из сущностей, чтобы сброс по префиксу был возможен; тесты покрывают сценарий "изменилось - обновилось"; мониторинг показывает долю ответов из кэша. Доля попаданий - главная метрика слоя: она показывает, работает ли стратегия или кэш греет память впустую. Возраст копий дополняет картину: высокая доля со старыми копиями означает устаревшие данные, свежие копии с низкой долей - неработающий кэш, и здоровье читается по двум цифрам вместе.

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

Стратегия собирается по бюджету и проверяется цифрами

Зрелая стратегия кэширования не максимизирует всё, а распределяет по выгоде: что кэшируется, где хранится, сколько живёт, как сбрасывается. Проверка стратегии сводится к списку:

  1. статические ассеты иммутаемы и живут в браузере и сети годами;
  2. персональное содержимое исключено из общих слоёв явно;
  3. сеть доставки держит географию аудитории рядом с каждым;
  4. серверный слой кэширует по стоимости вычислений с честными ключами;
  5. инвалидация событийная там, где свежесть важна, расписанная по сущностям;
  6. доля попаданий и возраст копий мониторятся постоянно;
  7. матрица допустимой устарелости написана и согласована с бизнесом.

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

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