1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

# теория · шаг 3 из 6

Когда бэкенд лёг

Коротко

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

  • proxy_cache_use_stale отдаёт протухшее, пока бэкенд не отвечает. Замер: 200 и STALE против 502 без этой строки.
  • proxy_cache_lock собирает набег на ещё не созданную запись в один запрос: замер даёт 1 обращение к бэкенду вместо 10.
  • background_update избавляет от ожидания при обновлении, revalidate - от повторной передачи тела.
  • Всё это работает только при живой записи в кеше: пустой кеш ничем не поможет.

Если знаешь, что делают use_stale и cache_lock, - листай до «Теперь сам».

Сначала ответь сам

Бэкенд лёг. В кеше лежит ответ, который протух минуту назад. Что увидит пользователь?

Зависит от одной строки. Замер на двух одинаковых блоках:

↳  выводбэкенд остановлен, запись протухла (код ответа и X-Cache-Status)
с proxy_cache_use_stale:   200  X-Cache-Status: STALE
без неё:                   502  X-Cache-Status: EXPIRED

Во втором случае у nginx на диске лежит вполне годный ответ - вчерашний, но рабочий, - и он честно отдаёт 502, потому что его не просили поступать иначе.

На что это похоже

Расписание на остановке. Автобусы перестали ходить, диспетчерская не отвечает. Можно снять расписание и повесить табличку «информации нет», а можно оставить вчерашнее с пометкой, что оно может быть неточным.

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

Механизм: три способа пережить неответ

use_stale бэкенд не отвечает - отдаём протухшее клиент видит 200 вместо 502 cache_lock десять запросов на ещё не созданную запись на бэкенд уходит один, остальные ждут его ответа background_update протухшее отдаём сразу а обновление идёт фоном, никто не ждёт все три работают только при живой записи: пустой кеш не спасёт
Разные беды: недоступный бэкенд, набег на истёкшей записи и ожидание обновления.

Правило

/etc/nginx/conf.d/shop.local.confhttp server location /
proxy_cache_use_stale error timeout updating
                      http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;

Четыре строки, и каждая закрывает свою беду. use_stale перечисляет случаи, когда протухшее лучше ошибки; updating в списке означает «пока один запрос обновляет запись, остальным отдавать старое».

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

Разбор: набег на пустое место

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

настройка обращений к бэкенду статусы у клиентов
без proxy_cache_lock 10 десять MISS
с proxy_cache_lock 1 один MISS, девять HIT

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

Важная тонкость: proxy_cache_lock работает для новых элементов, которых в кеше ещё нет. Набег на уже существующую, но протухшую запись закрывает другая пара - слово updating в proxy_cache_use_stale плюс proxy_cache_background_update: старое отдаётся всем сразу, а обновляет запись один запрос.

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

Замер этой пары легко испортить

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

Разбор: не тащить тело заново

/etc/nginx/conf.d/shop.local.confhttp server location /
proxy_cache_revalidate on;

Когда запись протухла, nginx идёт на бэкенд с условным запросом (If-Modified-Since или If-None-Match из пятого раздела). Ответ не изменился - бэкенд отвечает 304, тело по сети не едет, а запись в кеше продлевается. Статус в логе - REVALIDATED.

Выигрыш заметен на больших ответах, которые меняются редко. Работает он только если бэкенд отдаёт ETag или Last-Modified; фреймворки часто не отдают, и тогда строка бесполезна.

Про баг связки с HTTP/2 к бэкенду

proxy_cache_revalidate вместе с proxy_http_version 2 до версии 1.31.3 давал неверные условные запросы. Если пользуешься обеими вещами сразу - обновись.

Что ломается без этого

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

Протухшее нельзя отдавать вечно. У use_stale нет срока: пока запись жива в кеше (то есть пока не истёк inactive и её не вытеснили), она будет уходить клиентам. Если бэкенд лежит сутки, сутки же люди и будут видеть вчерашние цены. Ограничивают это inactive и мониторингом доли STALE - её всплеск означает, что бэкенд недоступен, даже когда сайт «работает».

Зачем это в работе

Доля STALE в логе - лучший показатель того, что кеш работает щитом, а не украшением. Считается одной командой:

$  команда
awk '{print $5}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

Пятое поле здесь - $upstream_cache_status из формата perf (четвёртый урок раздела), где он стоит пятым; в другом формате номер поля будет иным.

↳  выводawk '{print $5}' … | sort | uniq -c | sort -rn
  84210 HIT
   9820 MISS
    412 EXPIRED
     31 STALE

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

Теперь сам

Настроили proxy_cache_use_stale error timeout http_502, проверили - работает. Через месяц бэкенд лёг на десять минут, и половина пользователей всё равно увидела 502. Почему половина?

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

Главное

proxy_cache_use_stale отдаёт протухшую запись, пока бэкенд не отвечает - замер даёт 200 и STALE вместо 502. proxy_cache_lock собирает набег на ещё не созданную запись в одно обращение к бэкенду вместо десяти, а для уже протухшей ту же работу делают updating в списке use_stale и background_update. proxy_cache_revalidate продлевает запись условным запросом, не таща тело заново, и требует ETag или Last-Modified от бэкенда. Все три работают только при живой записи: пустой кеш не защищает никого, поэтому долю STALE стоит держать на графике.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.