# теория · шаг 2 из 6
Ключ и что кешировать нельзя
Коротко
Кеш включили, доля HIT выросла, все довольны. Через день на второй странице каталога показывается первая, а в личном кабинете - чужое имя. Обе истории про ключ.
- Ключ по умолчанию -
$scheme$proxy_host$request_uri, то есть вместе со строкой параметров. - Замена
$request_uriна$uriсхлопывает?page=1и?page=2в одну запись. Замер показывает это прямо. Set-Cookie,no-storeиprivatenginx отсеивает сам - и это защита, а не помеха.proxy_cache_bypassиproxy_no_cacheпишут парой: иначе личный ответ уедет следующему анониму.
Если помнишь, что в ключе по умолчанию есть строка запроса, и знаешь пару bypass/no_cache - листай до «Теперь сам».
Сначала ответь сам
Популярная «оптимизация» из чужих конфигов - убрать из ключа строку запроса, «чтобы метки рекламных кампаний не плодили записи»:
proxy_cache_key $scheme$proxy_host$uri;
Что увидит человек, открывший вторую страницу каталога?
/cat?page=1 X-Cache-Status: MISS бэкенд вернул path=/cat?page=1
/cat?page=2 X-Cache-Status: HIT бэкенд вернул path=/cat?page=1
Первую страницу. $uri - это путь без параметров, поэтому обе страницы
получили один ключ. Пользователь листает каталог и видит одно и то же; в логе
nginx при этом честный HIT, а бэкенд молчит - жаловаться некому.
На что это похоже
Гардероб, где номерки выдают по фамилии, а не по человеку. Пока однофамильцев нет, схема работает. Первый же второй Иванов уносит чужое пальто - и никакой ошибки при этом не происходит: система отработала ровно так, как её попросили.
Ключ кеша - это номерок. Всё, что не попало в ключ, для кеша не существует.
Механизм: что входит в ключ
Правило
Персональные ответы не кешируются. Не «кешируются с ключом по cookie», а не кешируются вовсе - редкий сайт может позволить себе хранить персональные страницы всех пользователей.
proxy_cache_bypass $http_authorization $cookie_session; # не читать из кеша
proxy_no_cache $http_authorization $cookie_session; # не писать в кеш
Обе директивы принимают список переменных и срабатывают, если хотя бы одна не
пуста и не равна нулю. Разница принципиальная: bypass не берёт ответ из кеша,
no_cache не кладёт его туда. Пишут их парой - иначе авторизованный
пользователь обойдёт кеш при чтении, а его личный ответ прекрасно туда запишется
и уедет следующему анониму.
Замер, как это выглядит в статусах:
/mu?nocache=1 X-Cache-Status: BYPASS
/mu X-Cache-Status: HIT ← прежняя запись цела
Разбор: что nginx не кеширует сам
Кое-что он отсеивает без всяких настроек - по заголовкам ответа бэкенда. Проверено на стенде, два запроса подряд к каждому адресу:
| ответ бэкенда | статусы |
|---|---|
Set-Cookie: sid=abc |
MISS, MISS - обращений к бэкенду 2 |
Cache-Control: no-store |
MISS, MISS - обращений к бэкенду 2 |
| обычный | MISS, HIT - обращений к бэкенду 1 |
Тот же список полностью: Set-Cookie, Cache-Control со значением no-cache,
no-store или private, Expires в прошлом, Vary: *.
Это разумная защита. Отключать её можно, только когда точно знаешь, почему бэкенд шлёт эти заголовки:
proxy_ignore_headers Cache-Control Set-Cookie Expires;
proxy_hide_header Set-Cookie;
/cookie MISS, затем HIT - обращений к бэкенду 1
/nocache MISS, затем HIT - обращений к бэкенду 1
Обрати внимание на вторую строку конфига. Без proxy_hide_header ты закешируешь
ответ вместе с чужой сессионной cookie и раздашь её всем - это хуже, чем
отсутствие кеша. Классический повод так делать один: фреймворк выдаёт сессионную
cookie каждому анониму на публичной странице.
Разбор: не кешировать сразу
proxy_cache_min_uses 3;
запрос 1 MISS бэкенд: 1
запрос 2 MISS бэкенд: 2
запрос 3 MISS бэкенд: 3
запрос 4 HIT бэкенд: 3
Ответ сохраняется только на третьем обращении к тому же ключу. Приём против засорения зоны «хвостом»: миллион уникальных адресов, каждый из которых запрошен однажды, вытеснит действительно горячие записи. Плата видна в замере - до третьего раза бэкенд работает как без кеша.
Рядом стоит proxy_cache_methods (по умолчанию GET HEAD): кешировать POST
можно, но почти никогда не нужно.
Что ломается без этого
Сокращённый ключ ломает не только страницы. Всё, что влияет на содержимое, но не
попало в ключ, склеивается в одну запись: язык из Accept-Language, версия API
из заголовка, мобильная и десктопная вёрстка, сжатая и несжатая версия ответа.
Последнее особенно коварно и решается не ключом, а заголовком: Vary:
Accept-Encoding (первая глава раздела) говорит кешам на пути, что версий
несколько. Свой кеш nginx это понимает сам, а вот CDN и корпоративный прокси -
только по заголовку.
Зачем это в работе
Первое, что стоит сделать после включения кеша, - проверить его на чужом пользователе. Открой страницу под авторизацией, потом ту же страницу в приватном окне и сравни: если во втором окне видно имя первого пользователя, у тебя ровно та ошибка, ради которой написана эта глава.
Второе - посмотреть в лог на число разных ключей. Если у страницы каталога сотни тысяч ключей из-за меток рекламных кампаний, правильный ход не «убрать параметры из ключа», а вырезать конкретные метки:
map $request_uri $cache_uri {
~^(?<clean>[^?]*)\?(.*&)?utm_[^&]*(&(?<rest>.*))?$ $clean?$rest;
default $request_uri;
}
Разница с $uri в том, что здесь ты убираешь известные бесполезные
параметры, а не все подряд.
Теперь сам
В кеш попал ответ с чужой сессией: аноним видит имя другого пользователя. В
конфиге стоит proxy_cache_bypass $cookie_session;, а proxy_no_cache нет. Как
это произошло?
Авторизованный пользователь пришёл с cookie - bypass честно отправил его мимо
кеша на бэкенд. Но запретить запись было нечем: персональный ответ уехал в
кеш и стал общим. Пара директив пишется всегда вместе; проверяется это тем же
опытом с приватным окном.
Главное
Ключ по умолчанию содержит $request_uri вместе с параметрами; замена его на
$uri схлопывает ?page=1 и ?page=2 в одну запись, и замер показывает
честный HIT с чужим содержимым. Персональные ответы не кешируются вовсе:
Set-Cookie, no-store и private nginx отсеивает сам, а для авторизованных
ставят парой proxy_cache_bypass и proxy_no_cache - без второй директивы
личный ответ попадёт в общий кеш. Отключая защиту через proxy_ignore_headers,
обязательно снимай Set-Cookie через proxy_hide_header.
proxy_cache_min_uses бережёт зону от одноразовых адресов ценой первых
обращений.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий