# теория · шаг 3 из 6
Когда бэкенд лёг
Коротко
Кеш умеет не только разгружать бэкенд, но и подменять его, когда тот лёг. Это две разные настройки и два разных сюжета.
proxy_cache_use_staleотдаёт протухшее, пока бэкенд не отвечает. Замер: 200 иSTALEпротив 502 без этой строки.proxy_cache_lockсобирает набег на ещё не созданную запись в один запрос: замер даёт 1 обращение к бэкенду вместо 10.background_updateизбавляет от ожидания при обновлении,revalidate- от повторной передачи тела.- Всё это работает только при живой записи в кеше: пустой кеш ничем не поможет.
Если знаешь, что делают use_stale и cache_lock, - листай до «Теперь сам».
Сначала ответь сам
Бэкенд лёг. В кеше лежит ответ, который протух минуту назад. Что увидит пользователь?
Зависит от одной строки. Замер на двух одинаковых блоках:
с proxy_cache_use_stale: 200 X-Cache-Status: STALE
без неё: 502 X-Cache-Status: EXPIRED
Во втором случае у nginx на диске лежит вполне годный ответ - вчерашний, но рабочий, - и он честно отдаёт 502, потому что его не просили поступать иначе.
На что это похоже
Расписание на остановке. Автобусы перестали ходить, диспетчерская не отвечает. Можно снять расписание и повесить табличку «информации нет», а можно оставить вчерашнее с пометкой, что оно может быть неточным.
Второе почти всегда полезнее: человек хотя бы понимает, куда идёт маршрут. Кеш рассуждает так же - устаревший ответ лучше пятисотки.
Механизм: три способа пережить неответ
Правило
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 перечитывал всё заново. Каталог надо вычищать перед каждым прогоном; подробнее про такие ловушки - в главе про замеры.
Разбор: не тащить тело заново
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 (четвёртый урок
раздела), где он стоит пятым; в другом формате номер поля будет иным.
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 стоит держать на графике.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий