Перейти к основному содержимому

[Medium] 📄 HTTP Caching

1. Что такое HTTP-кеширование и почему оно важно?

Что такое HTTP-кеширование? Почему оно важно?

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

Кеш vs Временное хранилище (Cache vs Temporary Storage)

В техническом контексте эти два термина часто путают, но они служат разным целям.

Кеш (Cache)

Определение: сохранённая копия, используемая для оптимизации производительности, с фокусом на повторное использование и быстрый доступ.

Характеристики:

  • ✅ Цель: улучшение производительности
  • ✅ Данные предполагается повторно использовать
  • ✅ Явная стратегия истечения/ревалидации
  • ✅ Обычно копия исходных данных

Примеры:

// HTTP Cache — кеширование ответов API
Cache-Control: max-age=3600 // кеш на 1 час

// Memory Cache — кеширование результата вычисления
const cache = new Map();
function fibonacci(n) {
if (cache.has(n)) return cache.get(n); // повторное использование кеша
const result = /* вычисление */;
cache.set(n, result);
return result;
}

Временное хранилище (Temporary Storage)

Определение: данные, сохранённые для временных рабочих нужд, с акцентом на короткий жизненный цикл.

Характеристики:

  • ✅ Цель: временное хранение
  • ✅ Повторное использование не обязательно
  • ✅ Обычно более короткий жизненный цикл
  • ✅ Может содержать промежуточное состояние

Примеры:

// sessionStorage — временные данные формы
sessionStorage.setItem('formData', JSON.stringify(form));

// временный путь загруженного файла
const tempFile = await uploadToTemp(file);
await processFile(tempFile);
await deleteTempFile(tempFile);

Сравнение (Comparison)

ХарактеристикаКешВременное хранилище
Основная цельПроизводительностьВременное хранение
Повторное использованиеДа, часто многократноеНе гарантировано
Жизненный циклУправляется политикойОбычно короткий
Типичное использованиеHTTP cache, memory cachesessionStorage, временные файлы
Английский терминCacheTemp / Temporary / Buffer

Практическое разграничение

// ===== Сценарии кеширования =====

// 1) HTTP cache: повторное использование ответа API
fetch('/api/users').then((response) => response.json());
fetch('/api/users').then((response) => response.json());

// 2) мемоизация (memoization cache)
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
};

// ===== Сценарии временного хранения =====

// 1) сохранение черновика при закрытии страницы
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('formDraft', JSON.stringify(formData));
});

// 2) временная обработка загрузки
async function handleUpload(file) {
const tempPath = await uploadToTempStorage(file);
const processed = await processFile(tempPath);
await deleteTempFile(tempPath);
return processed;
}

// 3) временные промежуточные результаты
const tempResults = [];
for (const item of items) {
tempResults.push(process(item));
}
const final = combine(tempResults);

В веб-разработке

// HTTP Cache (долгоживущий, повторно используемый)
Cache-Control: public, max-age=31536000, immutable

// sessionStorage (временный, привязан к вкладке)
sessionStorage.setItem('tempData', data)

// localStorage (постоянное хранение, не является в первую очередь слоем оптимизации кеша)
localStorage.setItem('userPreferences', prefs)

Почему это различие важно

  1. Проектные решения:
    • Нужна оптимизация производительности -> кеш
    • Нужно временное хранение -> временное хранилище
  2. Управление ресурсами:
    • Кеш фокусируется на проценте попаданий и стратегии истечения
    • Временное хранилище фокусируется на времени очистки и ограничениях размера
  3. Ясность на собеседовании:
    • Вопрос о производительности -> обсуждайте стратегию кеширования
    • Вопрос о временных данных -> обсуждайте стратегию временного хранения

Эта статья в основном фокусируется на кеше, особенно HTTP-кешировании.

Преимущества кеширования (Benefits of Caching)

  1. Меньше сетевых запросов
  2. Ниже нагрузка на сервер
  3. Быстрее загрузка страниц
  4. Меньше использование полосы пропускания
  5. Лучший пользовательский опыт

Уровни кеша браузера (Browser Cache Layers)

┌─────────────────────────────────────┐
│ Уровни кеша браузера │
├─────────────────────────────────────┤
│ 1. Memory Cache │
│ - Быстрейший, малая ёмкость │
│ - Очищается при закрытии вкладки│
├─────────────────────────────────────┤
│ 2. Disk Cache │
│ - Медленнее, большая ёмкость │
│ - Постоянное хранение │
├─────────────────────────────────────┤
│ 3. Service Worker Cache │
│ - Полный контроль приложения │
│ - Обеспечивает offline-поведение│
└─────────────────────────────────────┘

2. Какие стратегии HTTP-кеширования существуют?

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

Категории стратегий (Strategy Categories)

Стратегии HTTP-кеширования
├── Сильный кеш (Strong Cache / Fresh)
│ ├── Cache-Control
│ └── Expires
└── Кеш с валидацией (Validation Cache / Negotiation)
├── Last-Modified / If-Modified-Since
└── ETag / If-None-Match

1. Сильный кеш (Strong Cache)

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

Cache-Control (HTTP/1.1)

Cache-Control: max-age=3600

Распространённые директивы:

// 1) max-age: время свежести в секундах
Cache-Control: max-age=3600

// 2) no-cache: разрешить кеширование, но требовать ревалидацию перед использованием
Cache-Control: no-cache

// 3) no-store: не кешировать совсем
Cache-Control: no-store

// 4) public: может кешироваться браузером/CDN/прокси
Cache-Control: public, max-age=31536000

// 5) private: кеш только в браузере
Cache-Control: private, max-age=3600

// 6) immutable: контент не изменится в течение срока свежести
Cache-Control: public, max-age=31536000, immutable

// 7) must-revalidate: после истечения необходимо ревалидировать
Cache-Control: max-age=3600, must-revalidate

Expires (HTTP/1.0, устаревший)

Expires: Wed, 21 Oct 2025 07:28:00 GMT

Проблемы:

  • Использует абсолютное время
  • Зависит от корректности часов клиента
  • В основном заменён Cache-Control

2. Кеш с валидацией (Validation Cache)

Поведение: браузер спрашивает сервер, изменился ли ресурс.

Last-Modified / If-Modified-Since

# первый ответ
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# последующий запрос
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

Процесс:

  1. Первый запрос: сервер отправляет Last-Modified
  2. Следующий запрос: браузер отправляет If-Modified-Since
  3. Не изменился: сервер возвращает 304 Not Modified
  4. Изменился: сервер возвращает 200 OK + новое тело

ETag / If-None-Match

# первый ответ
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# последующий запрос
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

Преимущества:

  • Более точный, чем Last-Modified
  • Основан на контенте (хеш или токен версии)
  • Может обнаруживать изменения, невидимые в секундных метках времени

Last-Modified vs ETag

ХарактеристикаLast-ModifiedETag
ТочностьУровень секундТокен/хеш контента, более точный
СтоимостьНижеМожет требовать дополнительных вычислений
Подходит дляОбщих статических файловТочного контроля валидации
ПриоритетНижеВыше (ETag предпочтительнее при наличии обоих)

3. Как работает кеширование браузера?

Каков рабочий процесс кеша браузера?

Полный рабочий процесс

┌──────────────────────────────────────────────┐
│ Процесс запроса ресурсов браузером │
└──────────────────────────────────────────────┘

1. Проверить Memory Cache

┌───────┴────────┐
│ Попадание? │
└───────┬────────┘
Да │ Нет

2. Проверить Disk Cache

┌───────┴────────┐
│ Попадание? │
└───────┬────────┘
Да │ Нет

3. Проверить Service Worker

┌───────┴────────┐
│ Попадание? │
└───────┬────────┘
Да │ Нет

4. Проверить свежесть

┌───────┴────────┐
│ Устарел? │
└───────┬────────┘
Да │ Нет

5. Ревалидация с сервером

┌───────┴────────┐
│ Изменён? │
└───────┬────────┘
Да │ Нет (304)

6. Запросить новый контент

┌───────┴────────┐
│ Вернуть 200 │
│ с новым телом │
└────────────────┘

Практический пример

// первый запрос
GET /api/data.json
Response:
200 OK
Cache-Control: max-age=3600
ETag: "abc123"

{ data: "..." }

// в течение 1 часа
// попадание в сильный кеш -> локальное использование, без запроса
// статус в devtools: from disk cache или from memory cache

// после 1 часа
GET /api/data.json
If-None-Match: "abc123"

// не изменился
Response:
304 Not Modified

// изменился
Response:
200 OK
ETag: "def456"

{ data: "new data" }

4. Какие распространённые стратегии кеширования существуют?

Распространённые практические стратегии кеширования

1. Долгоживущие статические ресурсы

// HTML: без длительного кеширования, всегда валидировать
Cache-Control: no-cache

// CSS/JS с хешем: длительный неизменяемый кеш
Cache-Control: public, max-age=31536000, immutable
// имя файла: main.abc123.js

Принцип:

  • HTML должен оставаться свежим для ссылки на последние хеши ресурсов
  • Статические ресурсы с хешами могут кешироваться надолго
  • Изменение контента -> изменение имени файла -> новая загрузка

2. Часто обновляемые ресурсы

// Данные API: короткий кеш + ревалидация
Cache-Control: max-age=60, must-revalidate
ETag: "abc123"

3. Стратегии для изображений

// аватары пользователей: средний кеш
Cache-Control: public, max-age=86400

// логотипы/иконки: более длительный кеш
Cache-Control: public, max-age=2592000

// динамические изображения: валидация
Cache-Control: no-cache
ETag: "image-hash"

4. Рекомендуемые политики по типу ресурса

const cachingStrategies = {
html: 'Cache-Control: no-cache',
staticWithHash: 'Cache-Control: public, max-age=31536000, immutable',
staticAssets: 'Cache-Control: public, max-age=2592000',
apiData: 'Cache-Control: private, max-age=60',
userData: 'Cache-Control: private, no-cache',
sensitive: 'Cache-Control: no-store',
};

5. Кеширование с Service Worker

Кеширование с Service Worker

Service Worker даёт полный контроль над кешированием во время выполнения и offline-поведением.

Базовое использование

// регистрация Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png',
];

// install: предварительное кеширование статических ресурсов
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});

// fetch: пример cache-first
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

// activate: очистка старых кешей
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});

Распространённые стратегии SW

1. Cache First

// лучше всего для статических ресурсов
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

2. Network First

// лучше всего для API-запросов
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
return caches.match(event.request);
})
);
});

3. Stale While Revalidate

// лучше всего для быстрого ответа + фоновое обновление
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});

return cachedResponse || fetchPromise;
})
);
});

6. Как реализовать cache busting?

Как реализовать cache busting?

Cache busting гарантирует, что пользователи получают последние ресурсы при изменении контента.

Способ 1: хеширование имени файла (рекомендуется)

// с Webpack/Vite
// вывод: main.abc123.js

// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
};
<script src="/js/main.abc123.js"></script>

Плюсы:

  • ✅ Новое имя файла принуждает к загрузке
  • ✅ Старые файлы остаются кешируемыми
  • ✅ Лучшая практика индустрии

Способ 2: версия через query-параметр

<script src="/js/main.js?v=1.2.3"></script>
<link rel="stylesheet" href="/css/style.css?v=1.2.3" />

Минусы:

  • ❌ Некоторые CDN/прокси по-разному обрабатывают кеширование с query-строкой
  • ❌ Ручное управление версиями

Способ 3: метка времени

// обычно используется только в разработке
const timestamp = Date.now();
const script = document.createElement('script');
script.src = `/js/main.js?t=${timestamp}`;
document.body.appendChild(script);

Случай использования:

  • обход кеша при разработке
  • не идеально для продакшна

7. Распространённые вопросы по кешированию на собеседовании

Распространённые вопросы по кешированию на собеседовании

Вопрос 1: Как предотвратить кеширование HTML?

Нажмите, чтобы увидеть ответ
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

или HTML мета-теги:

<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate"
/>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

Вопрос 2: Почему стоит использовать ETag вместо одного Last-Modified?

Нажмите, чтобы увидеть ответ

Преимущества ETag:

  1. Более точный
  2. Валидация на основе контента
  3. Избегает пограничных случаев с меткой времени (повторный деплой с тем же контентом)
  4. Лучше работает в распределённых системах с несинхронизированными часами

Пример:

// контент не изменился, время деплоя изменилось
// Last-Modified меняется, но контент идентичен

ETag: 'hash-of-abc'; // стабилен, если контент не изменился

Вопрос 3: разница между from disk cache и from memory cache?

Нажмите, чтобы увидеть ответ
ХарактеристикаMemory CacheDisk Cache
ХранениеОЗУДиск
СкоростьОчень быстрыйМедленнее
ЁмкостьМеньшеБольше
ПостоянствоОбычно кратковременныйПостоянный
ПриоритетВышеНиже

Типичный порядок загрузки (концептуально):

1. Memory Cache
2. Service Worker Cache
3. Disk Cache
4. Ревалидация / Сеть

Вопрос 4: как принудительно обновить ресурсы в браузере?

Нажмите, чтобы увидеть ответ

Разработка:

// Hard Reload
// Отключить кеш в DevTools

const script = document.createElement('script');
script.src = `/js/main.js?t=${Date.now()}`;

Продакшн:

// хешированные имена файлов (лучше всего)
main.abc123.js

// версия через query
<script src="/js/main.js?v=2.0.0"></script>

// политика кеширования
Cache-Control: no-cache
Cache-Control: no-store

Вопрос 5: как реализовать offline-кеш в PWA?

Нажмите, чтобы увидеть ответ
// sw.js
const CACHE_NAME = 'pwa-v1';
const OFFLINE_URL = '/offline.html';

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
OFFLINE_URL,
'/styles/offline.css',
'/images/offline-icon.png',
]);
})
);
});

self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(OFFLINE_URL);
})
);
}
});

Полная стратегия PWA обычно сочетает:

// 1) статические ресурсы: cache-first
// 2) API: network-first
// 3) изображения: cache-first
// 4) HTML-навигация: network-first + offline fallback

8. Лучшие практики (Best Practices)

Лучшие практики

✅ Рекомендуется

// 1. HTML: без длительного кеша для получения актуального входного документа
Cache-Control: no-cache

// 2. CSS/JS с хешем: длительный неизменяемый кеш
// пример имени файла: main.abc123.js
Cache-Control: public, max-age=31536000, immutable

// 3. Изображения: средний/длительный кеш
Cache-Control: public, max-age=2592000

// 4. Данные API: короткий кеш + валидация
Cache-Control: private, max-age=60
ETag: "api-response-hash"

// 5. Service Worker для offline-поддержки

❌ Избегайте

// плохо: длительный кеш для входного HTML-документа
Cache-Control: max-age=31536000

// плохо: полагаться только на Expires
Expires: Wed, 21 Oct 2025 07:28:00 GMT

// плохо: нет явных заголовков кеширования

// плохо: одна политика для всех типов ресурсов
Cache-Control: max-age=3600

Дерево решений по стратегии кеширования

Это статический ресурс?
├─ Да -> имя файла содержит хеш?
│ ├─ Да -> длительный неизменяемый кеш (max-age=31536000, immutable)
│ └─ Нет -> средний/длительный кеш (напр. max-age=2592000)
└─ Нет -> Это HTML?
├─ Да -> no-cache
└─ Нет -> Это API?
├─ Да -> короткий кеш + валидация (max-age=60 + ETag)
└─ Нет -> решайте по частоте обновлений

Ссылки (Reference)