awesome-everything EN
↑ Обратно к восхождению

Кеширование

Stale-while-revalidate и CDN request coalescing

Суть Директива stale-while-revalidate из RFC 5861 немедленно возвращает устаревшее значение из кеша, обновляясь в фоне — полностью устраняя ожидание. CDN расширяет это с request coalescing, который форвардит ровно один origin-запрос на каждый cache miss.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 12 min

Lock-based кеш даёт 10 000 ожидающим запросам одно из двух: перестроенное значение (после 400 мс ожидания) или fallback. Stale-while-revalidate немедленно даёт всем 10 000 ожидающим старое значение — и ставит в очередь ровно один фоновый refresh. Ноль ожидания, ноль спайка БД.

RFC 5861: стандарт

RFC 5861 (2010) определяет два расширения Cache-Control:

  • stale-while-revalidate=N — отдавать устаревший (истёкший) кешированный ответ до N секунд, инициируя фоновую ревалидацию. Пользователь видит ответ без ожидания.
  • stale-if-error=N — отдавать устаревший кешированный ответ до N секунд, если ревалидация падает (5xx, таймаут). Держит сайт доступным при сбоях origin.

Пример заголовка:

Cache-Control: max-age=60, stale-while-revalidate=30, stale-if-error=3600

Это означает: свежий 60 с, отдавать stale ещё 30 с при фоновом обновлении, отдавать stale до 1 часа при ошибке origin.

Что происходит на истечении TTL

С включённым SWR на кеше (CDN или application-level):

  1. T=60.0 с — TTL срабатывает. Ключ теперь «устаревший, но в рамках stale-while-revalidate».
  2. Запросы 1–N приходят в T=60.001 с.
  3. Все N запросов немедленно получают stale-значение. Без ожидания.
  4. В очередь ставится ровно один фоновый refresh (кеш выбирает первый запрос или использует отдельный background task).
  5. T=60.4 с — фоновый refresh завершается. Новое значение сохранено.
  6. Будущие запросы получают свежее значение.

Нагрузка на БД на границе: 1 запрос, не N.

МитигацияОжидание пользователя на TTL-границеЗапросы к БД на границе
Нет (наивный TTL)Нет — но БД падаетN параллельных
Только локДо rebuild p99 (ожидающие в очереди)1 (сериализованный)
Single-flightДо rebuild p99 (подписчики ждут)1 на ноду
XFetchНет — кеш никогда не истекает под трафиком~1 (ранний rebuild)
SWRНет — stale отдаётся немедленно1 (фоновый)

Компромисс: ограниченный staleness

SWR явно принимает, что читатели будут видеть устаревшие данные до stale-while-revalidate длительности после истечения max-age. Это нормально для:

  • Контент-страниц, новостных лент, product listings
  • Баннеров главной страницы, навигационных меню
  • Любых данных, где лаг 30–300 с незаметен пользователям

Это неправильно для:

  • Баланса счёта, счётчиков голосов, всего, что влияет на бизнес-решения в реальном времени
  • Всего, где два пользователя должны видеть согласованное состояние одновременно

CDN-уровень: request coalescing

CDN расширяют SWR с request coalescing (Cloudflare) или request collapsing (Fastly). Когда cache miss приходит на edge:

  1. Edge входит в состояние «stitching» — он выпустил один upstream fetch и ждёт ответа.
  2. Любые дополнительные запросы на тот же путь в состоянии «stitching» не генерируют дополнительных upstream fetch.
  3. Все ожидающие запросы получают ответ одновременно, когда единственный upstream fetch завершается.

Вирусный контент-event с 10 миллионами зрителей, хитящими один URL, производит один origin-fetch, не 10 миллионов. Cloudflare и Fastly публикуют, что request coalescing превращает sudden-traffic инциденты в low-impact события на origin.

Framework-уровень: Next.js ISR

Next.js Incremental Static Regeneration (ISR) — SWR на уровне фреймворка. Страница с revalidate: 60 обслуживается из кеша 60 секунд; первый запрос после revalidate-окна триггерит фоновую регенерацию, пока stale-страница продолжает обслуживать. Форма идентична RFC 5861 — фреймворк просто реализует это без HTTP Cache-Control заголовков.

Почему это работает

Кеширование GraphQL в Apollo использует SWR-семантику для нормализованных cache-записей. Результат запроса отдаётся из нормализованного кеша, пока фоновый refetch сверяет устаревшие поля. Тот же принцип распространяется на gRPC response caching и даже DNS TTL — паттерн «отдать stale, обновить в фоне» универсален везде, где существуют TTL-based кеши.

Викторина

В T=60.001 с приходит 2 000 запросов для ключа с TTL=60 с и stale-while-revalidate=30 с. Сколько из них ждут завершения rebuild?

Викторина

Какой use case НЕПОДХОДЯЩИЙ для stale-while-revalidate?

Викторина

На CDN edge срабатывает Cloudflare request coalescing во время вирусного события. 50 000 одновременных запросов приходят на один URL, который только что истёк. Сколько upstream origin-запросов делается?

Вспомните перед уходом
  1. 01
    Что означает на практике HTTP заголовок Cache-Control: max-age=60, stale-while-revalidate=30, stale-if-error=3600?
  2. 02
    Как Next.js ISR реализует ту же гарантию, что RFC 5861 stale-while-revalidate?
Итог

Stale-while-revalidate (RFC 5861) устраняет очереди ожидания на TTL-границах, немедленно возвращая stale-значение всем запросам и запуская ровно один фоновый refresh. Пользовательская латентность на границе падает до нуля; нагрузка на БД падает до одного rebuild-запроса. CDN-level request coalescing расширяет тот же принцип глобально: edge выпускает один origin-fetch на cache miss-событие независимо от числа параллельных запросов. Компромисс — явный ограниченный staleness, приемлемый для контента, неподходящий для strong-consistent бизнес-данных. Комбинируй SWR на CDN edge с XFetch или распределённым локом на application cache-слое для defence-in-depth на каждом тире.

Связанные уроки
встречается в202
Продолжить восхождение ↑Детектирование stampede и дизайн TTL для продакшена
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.