Новые директивы HTTP для кеширования с учётом CDN
© xkcd
В данный момент на рассмотрении IETF находятся два стандарта. Это новые поля в заголовке ответа HTTP: Cache-Status и таргетированный Cache-Control.
Новые поля должны упростить разработку веб-приложений, а именно: упорядочить кеширование статического контента. Сейчас с этим небольшой бардак, поскольку кеширование происходит в нескольких системах на нескольких уровнях, почти как в этом ↑ комиксе. А синхронизировать все уровни непросто, ведь текущие стандарты разрабатывались в те времена, когда ещё не существовало CDN.
Владелец любого сайта с более-менее приличным трафиком знает, что кеширование кардинально улучшает производительность. Кеш и CDN стоят перед сервером и быстро отбивают большинство запросов, не трогая бэкенд. Это решает массу проблем. Во-первых, сглаживаются скачки трафика, потому что статический кеш масштабируется проще, чем сервер приложения. Во-вторых, на порядок уменьшается количество обращений к серверу, и они поступают пачками, а не по одному. Наконец, в CDN контент физически распределяется по всему миру, уменьшая задержку для пользователей из любого региона.
Кеш снимает с сервера до 99% нагрузки. В результате стоимость хостинга может упасть до смехотворной величины. Например, известный специалист по безопасности Трой Хант платит за облачный хостинг своего популярного сайта Pwned Passwords (базы утёкших паролей) меньше 3 центов в день, а у него 19 ГБ файлов и 34,4 млн запросов к API в неделю (правда, это статистика за 2018 год, с тех пор размер баз наверняка вырос). Как видно на скриншоте, из 32 408 715 запросов к API только 122 566 пошли на сервер, а остальные 99,62% получили результат из кеша.
Конечно, такой высокий результат во многом объясняется спецификой сайта. Здесь исключительно статичный контент, а большинство запросов приходит через API от приложений, куда встроена проверка паролей на предмет утечки, чтобы оперативно информировать пользователей.
Кроме количества запросов, CDN на порядок снижает трафик с сервера. В данном случае общий трафик за неделю составил 477,63 ГБ, в том числе оплаченный трафик с облака — всего 945,96 МБ. Теперь понятно, почему хостинг обходится ему так дёшево.
Уровни кеширования
Так в чём проблема? Казалось бы, существующие механизмы кеширования работают отлично, на порядок ускоряют загрузку контента пользователям и экономят кучу денег на хостинге.
Но проблема в сложности существующих технологий. Кеширование по факту происходит в несколько уровней и осуществляется по-разному для различных путей поступления запроса.
Скажем, непосредственно перед бэкендом обычно работает какой-то балансировщик нагрузки, обратный прокси или гейт для обработки API-запросов с собственным кешем. В то же время сам сервер на бэкенде тоже кеширует некоторые внутренние данные.
Дополнительно на дальнем уровне обычно работает CDN-провайдер типа Cloudflare, который раздаёт контент пользователям через свою сеть серверов. Свои кеширующие прокси могут стоять также у интернет-провайдера или в корпоративной сети.
Наконец, кешированием занимаются и многие клиенты, особенно браузеры, зачастую с несколькими уровнями кеширования у себя внутри, как сервис-воркеры, что только добавляет путаницы.
Кто-то может сказать, что чем больше разных кешей — тем лучше. Ведь даже на системном уровне есть три уровня кешей процессора, кеш в памяти, на SSD и так далее. На каждом уровне свои кеши. Всё правильно, но в веб-сервисах иногда получается так, что эти кеши находятся на одном уровне, дублируют и мешают друг другу, затрудняя настройку сбалансированной системы. Каждому кешу нужна отдельная конфигурация, а головная боль разработчика — инвалидация кеша, чтобы максимально быстро доставить новый контент к пользователю, «пробив» все уровни кешей.
Проблемы с инвалидацией кеша
Инвалидация или очистка кеша — удаление всех закешированных объектов, связанных с изменениями в состоянии модели. Наиболее распространённый тип инвалидации — прямое удаление объектов. Но если мы имеем дело с пятью или десятью уровнями кеша на пути от бэкенда к конечным пользователям, то гарантировать инвалидацию не так просто.
Тут поджидает целый ряд скрытых угроз: из-за некорректной инвалидации мы можем просто поломать механизм кеширования, так что ничего не будет кешироваться вообще — и 100% запросов хлынут на бэкенд (как вариант, контент будет сохраняться только в локальном кеше, но не в CDN). Или, наоборот, ответы сохранятся там дольше, чем положено, а пользователям будет отгружаться устаревший контент. Из-за нескольких уровней кеширования может получиться так, что клиенты начнут получать смесь из новой и старой информации.
И главное, что сама конфигурация кеша из заголовка Cache-Control тоже кешируется, что может привести к большим трудностям в инвалидации.
Новые стандарты призваны всё это исправить.
Итак, что же предлагает Марк Ноттингем с соавторами новых спецификаций? Если кто-то не знает, Марк Ноттингем — очень авторитетный специалист, один из самых известных разработчиков инфраструктуры современного веба, входил в группу технической архитектуры W3C и в настоящее время является членом Совета по архитектуре Интернета.
Cache-Status
Спецификация Cache-Status определяет новое поле заголовка ответа HTTP со стандартизированным синтаксисом и семантикой для всех уровней кеширования, упомянутых выше.
Это поле показывает, каким образом система кеширования обработала данный ответ и соответствующий ему запрос. Синтаксис по стандарту RFC8941 (структурированные поля для HTTP, то есть точка с запятой между параметрами, запятая между кешами) и представляет собой список:
Cache-Status = sf-list
Каждый член списка — это одна из систем кеширования, которая обработала пакет по ходу его маршрута. Все они перечислены в порядке от бэкенда до пользователя (возможно, включая кеш самого агента пользователя, если он добавляет значение).
Примеры из спецификации:
Cache-Status: OriginCache; hit; ttl=1100,
"CDN Company Here"; hit; ttl=545
(два уровня кеша, где OriginCache ответил на предыдущий запрос с сохранённым значением, а CDN сохранил этот ответ и повторно использовал его для ответа на текущий запрос)
Cache-Status: Nginx; hit
Cache-Status: CDN; fwd=uri-miss; collapsed; stored
Cache-Status: BrowserCache; fwd=uri-miss
(здесь три уровня, два из которых пропустили запрос мимо кеша из-за отсутствия данного URI, а Nginx ответил из кеша)
Для каждого кеша спецификация предусматривает ряд параметров:
hit — параметр означает, что данный ответ пришёл из кеша, а оригинальный запрос дальше не передаётся fwd='reason' — это значит, что запрос передан дальше, с указанием причины (bypass, miss, uri-miss, vary-miss и другие, подробнее см. раздел 2.2 в спецификации) fwd-status='status' — указывает код fwd от предыдущего сервера ttl — оставшееся время жизни ответа stored — при наличии параметра fwd указывает, сохранён ли этот ответ на будущее collapsed — при наличии параметра fwd указывает, был ли запрос объединён с другим запросом, в случае положительного статуса можно повторно использовать ответ key — специфический ключ кеша, для внутренней реализации в разных приложениях detail — дополнительная информация, которая не охвачена другими параметрами в строке
Согласно спецификациям, каждый кеш самостоятельно определяет, добавить ли ему поле заголовка Cache-Status
. Некоторые могут добавлять его во все ответы, а другие могут делать это только тогда, когда специально настроены на это или когда запрос содержит поле заголовка, активирующее режим отладки.
Стирать предыдущие строки запрещено, чтобы можно было отладить всю цепочку кешей, обрабатывающих запрос.
Зачем это нужно?
Поле Cache-Status
вносит ясность и порядок во всю систему многочисленных кешей на пути HTTP-ответа. Сразу видно, пакет идёт от сервера или из кеша. Если из кеша, то из какого именно, как долго там будет храниться этот контент. Если не из кеша, то почему произошёл промах и сохранился ли в кеше этот новый ответ.
Это поле рассказывает всю историю пакета и всех кешей, через которые он прошёл, что исключительно полезно при отладке.
Таргетированный Cache-Control
Второй предлагаемый стандарт гораздо проще. Это улучшенная версия существующего заголовка Cache-Control
, который был разработан 20 лет назад и уже не удовлетворяет требованиям времени.
Cache-Control
— это список прямых инструкций кеширования, которые задаются сервером для запросов и ответов. Например, список инструкций для запросов:
Cache-Control: max-age=<секунд>
Cache-Control: max-stale[=<секунд>]
Cache-Control: min-fresh=<секунд>
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: only-if-cached
Cache-Control: immutable
Cache-Control: stale-while-revalidate=
Инструкции для ответов:
Cache-Control: must-revalidate
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: public
Cache-Control: private
Cache-Control: proxy-revalidate
Cache-Control: max-age=<секунд>
Cache-Control: s-maxage=<секунд>
… и другие.
Все они хорошо известны разработчикам. Проблема в том, что эти инструкции «слепые» и направлены всем кешам одновременно, что в наше время не очень эффективно.
Таргетированный Cache-Control просто указывает, какой конкретно системе предназначены инструкции. Например, можно обновлять контент только для запросов во внутренний балансировщик нагрузки, но не в CDN. Или включить кеширование объекта только в CDN, но в других внешних системах. Естественно, все они должны поддерживать эти новые спецификации, чтобы выполнять таргетированные инструкции.
Небольшое отличие в синтаксисе. Здесь предлагается использовать синтаксис структурированных полей (;
и ,
), такой же, как в предыдущем поле.
Примеры:
Cache-Control: max-age=60, s-maxage=120
CDN-Cache-Control: max-age=600
(инструкция для CDN: считать ответ свежим 600 секунд, другим общим кешам — 120 секунд, а всем остальным — 60 секунд)
CDN-Cache-Control: max-age=600
Squid-Cache-Control: max-age=60
Cache-Control: no-store
(разрешение на кеширование только в CDN и Squid, больше никому)
В данном виде спецификация предусматривает только одну «цель» для таргетирования — это CDN. То есть директива CDN-Cache-Control
относится ко всем CDN. Но в будущем в спецификации могут добавить другие классы систем. Например, ISP-провайдеров, браузеры или корпоративные сети.
Разумеется, выполнять директиву CDN-Cache-Control
может только тот CDN, который понимает эту спецификацию.
Принятие индустрией
Черновики этих стандартов были опубликованы только в июле и августе 2021 года, они ещё совсем свежие. В ближайшее время посмотрим, как индустрия их примет. Пока есть только первыепримеры поддержки Cache-Status
.
Кроме того, Cloudflare и Akamai поддерживают общий для них CDN-Cache-Control
, а также отдельные Akamai-Cache-Control
и Cloudflare-CDN-Cache-Control
. То есть это уже работает.
По идее, Cache-Status
должен объединить в себе поля ответа HTTP всех существующих провайдеров, которые зачастую несовместимы между собой, такие как X-Cache-Status от Nginx, CF-Cache-Status от Cloudflare, X-Served-By и X-Cache от Fastly и другие. Будем надеяться, что все они постепенно перейдут на Cache-Status
и мы не получим ситуацию как в другом комиксе xkcd со стандартами.
Обсуждение Cache-Status
и таргетированного Cache-Control продолжается. Можно вносить предложения в гитхабе рабочей группы IETF HTTP.НЛО прилетело и оставило здесь промокоды для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
— 20% на выделенные серверы AMD Ryzen и Intel Core — HABRFIRSTDEDIC.
Доступно до 31 декабря 2021 г.