Кэширование HTTP: строгий кэш, согласование и стратегии CDN
Кэш HTTP: первая линия защиты оптимизации производительности
Правильная настройка кэша позволяет достичь нулевых сетевых запросов при повторном посещении, снижая время загрузки с секунд до миллисекунд.
| Тип кэша | Механизм | Запрос к серверу? | Скорость |
|---|---|---|---|
| Строгий кэш | Cache-Control / Expires | Нет | Самая быстрая (0мс) |
| Кэш с согласованием | ETag / Last-Modified | Да (условный запрос) | Быстрая (ответ 304) |
| Без кэша | no-cache / no-store | Да (полный запрос) | Самая медленная |
I. Строгий кэш
Директивы Cache-Control
Cache-Control: max-age=31536000, immutable
| Директива | Действие | Применимый сценарий |
|---|---|---|
max-age=N |
Кэшировать N секунд | Статические ресурсы |
s-maxage=N |
CDN кэширует N секунд (переопределяет max-age) | Сценарии CDN |
immutable |
Не перепроверять даже при истечении | Имена файлов с хешем |
public |
Разрешить промежуточный кэш | CDN |
private |
Только кэш браузера | Персонализированный контент |
no-cache |
Обязательно проверять перед каждым использованием | HTML-страницы |
no-store |
Полностью не кэшировать | Конфиденциальные данные |
must-revalidate |
Обязательно проверять после истечения | Ответы API |
stale-while-revalidate=N |
После истечения вернуть старое значение, обновить в фоновом режиме | Прогрессивное обновление |
Expires (старый стандарт)
Expires: Thu, 02 Jun 2027 00:00:00 GMT
Приоритет: Cache-Control: max-age > Expires.
II. Кэш с согласованием
ETag / If-None-Match
# Первый запрос
GET /api/data
→ 200 OK
ETag: "abc123"
Cache-Control: max-age=0
# Повторный запрос (условный)
GET /api/data
If-None-Match: "abc123"
→ 304 Not Modified (без тела ответа, экономит полосу пропускания)
Last-Modified / If-Modified-Since
# Первый запрос
GET /style.css
→ 200 OK
Last-Modified: Mon, 01 Jun 2026 12:00:00 GMT
# Повторный запрос
GET /style.css
If-Modified-Since: Mon, 01 Jun 2026 12:00:00 GMT
→ 304 Not Modified
ETag vs Last-Modified
| Измерение | ETag | Last-Modified |
|---|---|---|
| Точность | Байтовый уровень | Секундный уровень |
| Производительность | Требует вычисления хеша | Прямое чтение времени файла |
| Надёжность | Высокая | Низкая (множественные изменения за 1 секунду теряются) |
| Распределённый | Необходимо обеспечить согласованность хеша | Естественно согласован |
Рекомендация: Приоритет ETag.
III. Матрица стратегий кэширования
HTML-страницы
Cache-Control: no-cache
HTML — входной файл, должен проверяться каждый раз для обеспечения пользователей последней версией.
Статические ресурсы с хешем
Cache-Control: max-age=31536000, immutable
Имя файла содержит хеш (напр., app.abc123.js), при изменении содержимого хеш меняется → URL меняется → новый запрос. Старый URL никогда не меняется, можно кэшировать на год и пометить immutable.
Статические ресурсы без хеша
Cache-Control: max-age=86400, stale-while-revalidate=604800
После истечения сначала возвращается старое значение, фоновое обновление происходит незаметно для пользователя.
Ответы API
Cache-Control: private, max-age=0, must-revalidate
Ответы API обычно содержат пользовательские данные, private запрещает кэширование CDN, must-revalidate гарантирует, что после истечения старые значения не возвращаются.
IV. Заголовок Vary: расширение ключа кэша
Vary: Accept-Encoding, Accept-Language
Vary сообщает кэшу: помимо URL, различать версии кэша по значениям этих заголовков запроса.
| Значение Vary | Значение | Сценарий |
|---|---|---|
Accept-Encoding |
Кэшировать по методу сжатия | gzip vs br |
Accept-Language |
Кэшировать по языку | Многоязычные сайты |
Cookie |
Кэшировать по Cookie | Персонализированные страницы |
* |
Каждый запрос с отдельным кэшем | Эквивалент отсутствию кэша |
Внимание: Vary: Cookie приводит к крайне низкой частоте попадания в кэш, используйте с осторожностью.
V. Стратегия кэширования CDN
Управление обращением к источнику
# CDN кэширует 1 день, браузер кэширует 5 минут
Cache-Control: public, s-maxage=86400, max-age=300
| Сценарий | s-maxage | max-age | Описание |
|---|---|---|---|
| Статические ресурсы | 31536000 | 31536000 | CDN и браузер с длительным кэшем |
| Ответы API | 300 | 0 | CDN кэширует 5 минут, браузер не кэширует |
| HTML | 60 | 0 | CDN кэширует 1 минуту, браузер не кэширует |
| Персонализированный контент | — | 0 | CDN не кэширует, браузер не кэширует |
Обновление кэша CDN
| Метод | Скорость | Применимо |
|---|---|---|
| Предварительный прогрев URL | Быстро | Публикация нового контента |
| Полное обновление | Медленно | Срочное исправление |
| Обновление каталога | Средне | Пакетное обновление |
| Cache-Control: no-cache | Автоматически | Сценарии API |
Stale-While-Revalidate на практике
Cache-Control: max-age=60, stale-while-revalidate=86400
Секунда 0: Запрос → Кэш MISS → Обращение к источнику → Возврат свежего значения
Секунда 30: Запрос → Кэш HIT → Возврат кэшированного значения
Секунда 90: Запрос → Кэш STALE → Немедленный возврат старого значения + фоновое обращение к источнику
Секунда 91: Обращение к источнику завершено → Кэш обновлён
Секунда 120: Запрос → Кэш HIT → Возврат нового значения
Эффект: Пользователь никогда не ждёт обращения к источнику, тихое обновление в фоновом режиме.
VI. Стратегия инвалидации кэша
Хеш содержимого (рекомендуется)
<!-- Хеш генерируется при сборке -->
<script src="/app.abc123.js"></script>
<link href="/style.def456.css">
// Конфигурация webpack
output: {
filename: '[name].[contenthash:8].js',
}
Параметр запроса версии
<!-- Просто, но CDN может не кэшировать параметры ?v= -->
<script src="/app.js?v=2.0.0"></script>
Проблема: Некоторые CDN игнорируют параметры запроса, что приводит к необновлению кэша.
Метка времени (не рекомендуется)
<!-- Меняется каждый раз, полностью разрушает кэш -->
<script src="/app.js?t=1717315200"></script>
VII. Кэш Service Worker
Стратегия Cache First
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
Стратегия Stale While Revalidate
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic').then(cache =>
cache.match(event.request).then(cached => {
const fetchPromise = fetch(event.request)
.then(response => {
cache.put(event.request, response.clone());
return response;
});
return cached || fetchPromise;
})
)
);
});
Выбор стратегии кэширования
| Стратегия | Приоритетный источник | Применимо |
|---|---|---|
| Cache First | Кэш | Шрифты, изображения |
| Network First | Сеть | API, HTML |
| Stale While Revalidate | Кэш + фоновое обновление | CSS, JS |
| Network Only | Сеть | Не-GET запросы |
| Cache Only | Кэш | Офлайн-страницы |
VIII. Отладка кэша
Chrome DevTools
- DevTools → Панель Network
- Проверить столбец
Size:from disk cache: Попадание в дисковый кэшfrom memory cache: Попадание в кэш памяти- Фактический размер: Промах
- Проверить заголовок ответа
Age: Время нахождения в кэше - Отметить Disable cache: Отключить кэш (когда DevTools открыт)
Проверка с curl
# Посмотреть заголовки ответа
curl -I https://example.com/style.css
# Отправить условный запрос
curl -I -H 'If-None-Match: "abc123"' https://example.com/style.css
# 304 означает попадание в кэш с согласованием
# Имитировать другой Accept-Encoding
curl -I -H 'Accept-Encoding: br' https://example.com/style.css
IX. Контрольный список лучших практик
- HTML: no-cache, обеспечить актуальность входного файла
- JS/CSS с хешем: max-age=1 год + immutable
- API: must-revalidate, обязательно проверять после истечения
- CDN: s-maxage для контроля частоты обращения к источнику
- Vary: Accept-Encoding, различать методы сжатия
- Использовать ETag вместо Last-Modified
- stale-while-revalidate для незаметного обновления
- Service Worker для дополнения офлайн-возможностей