Заголовок ответа Vary называют «the ugliest part of HTTP that we haven't yet improved.» В том же посте он описывается как «ужасный, неуклюжий механизм» с «весьма плачевной совместимостью» между промежуточными узлами. Обычно на этом этапе здравомыслящие инженеры медленно отступают назад, подняв руки.
Это не совсем комплимент в адрес Vary, но уродливый не означает бесполезный.
Один URL-адрес может иметь более одного правильного ответа. Например, сервер может отправлять разные форматы изображений разным браузерам. Если кэш игнорирует Vary, он рискует выдать неправильные байты в ответ на запрос. Но если он рассматривает каждое сырое значение заголовка как уникальное, горстка похожих запросов может превратиться в тысячи едва ли пригодных для повторного использования записей кэша. Vary сообщает кэшу, какие поля запроса могут повлиять на ответ, но не говорит кэшу, какие различия действительно важны.
Поддержка Vary теперь доступна в Cache Rulesправилах кэширования (Cache Rules){{/L1}} на всех тарифных планах. Источник по-прежнему указывает заголовки запроса, которые могут повлиять на ответ, но вы решаете, как Cloudflare обрабатывает каждый из них. Вы можете нормализовать известные заголовки согласования, передавать точные значения дальше, когда эти мелкие различия важны, или обходить кэш, когда вариативность слишком непредсказуема. Источник заявляет, что может изменяться, а вы решаете, какая степень вариативности действительно имеет значение для кэша.
Как работает Vary
Vary — это заголовок ответа HTTP, который сообщает промежуточным кэшам (таким как Cloudflare), какие поля запроса могут повлиять на ответ, отправленный источником. Сайты используют Vary для предоставления разных языков, форматов изображений, схем сжатия или регионального контента с одного и того же URL-адреса.
Возьмем один URL-адрес, который выдает два правильных представления. Браузер запрашивает веб-страницу:
Источник возвращает HTML и определяет Accept как поле, которое может повлиять на ответ:
Клиент API может запросить тот же URL с другими предпочтениями:
На этот раз правильным ответом является JSON. Заголовок Vary: Accept сообщает кэшу, что одного URL недостаточно для выбора между ответами. Значение Accept в запросе также должно учитываться.
Без Vary любой ответ, попавший в кэш первым, может быть отправлен обоим клиентам. Если побеждает HTML, клиент API получает разметку, и его парсер JSON дает сбой. Если побеждает JSON, браузер, ожидающий веб-страницу, получает ответ API.
Vary предотвращает выдачу неправильного ответа запрашивающему клиенту из кэша. Но это порождает более сложный вопрос: когда два запроса содержат разные значения заголовков, действительно ли им нужны разные ответы?
Когда правильное кэширование становится бесполезным
Vary может подсказать кэшу, какие поля запроса могут повлиять на ответ. Он не говорит кэшу, что представляет собой этот ответ. Например, возьмем источник, который обслуживает контент только на английском, французском и немецком языках. Клиент может отправить:
В то время как другой клиент может запросить:
В данном случае оба клиента предпочитают английский язык. Ответ источника может сопоставить оба запроса с абсолютно одинаковым ответом на английском языке. Но кэш, сравнивающий сырые значения, не может с уверенностью предположить, что они эквивалентны. У них разный порядок и языковые теги (которые источник не различает). Поэтому кэш может сохранить их как отдельные варианты, даже если тела их ответов содержат идентичные байты.
В этом заключается главная проблема Vary. Приложения часто создают небольшой конечный набор представлений из огромного набора возможных значений запроса. Источник понимает, что тысячи языковых предпочтений сводятся к трем поддерживаемым языкам, в то время как кэш обычно нет.
Эта проблема усугубляется, когда ответ варьируется по нескольким полям. Десять возможных значений в одном поле создают десять вариантов. Десять значений в трех поля могут создать 1000 комбинаций. Реальные заголовки могут иметь гораздо большую кардинальность: значения User-Agent исчисляются тысячами, файлы cookie могут быть уникальными для каждого отдельного посетителя, а заголовки предпочтений могут различаться по порядку, форматированию (пробелы и табуляция имеют значение!) и коэффициентам качества.
Результатом является кэш, который может быть абсолютно корректным и почти постоянно холодным (запись никогда не используется повторно). Идентичные ответы могут быть разбросаны по записям, которые получают слишком мало трафика, чтобы оставаться горячими и находиться в кэше. Они могут потреблять емкость, вытеснять друг друга, снижать коэффициенты попадания в кэш и отправлять больше запросов обратно на исходные серверы. Вытеснение может удалить холодные записи, но оно не может объединить их просто потому, что ответы идентичны.
Анализ более 120 миллионов ответов почти с 50 000 популярных сайтов показал, что почти 3 000 сайтов изменяют ответы по четырем и более полям. Некоторые варьировались по 10, 23 или даже 47 полям. Мы хотим убедиться, что у клиентов есть инструменты, необходимые для использования Vary, когда это уместно, но не до такой степени, чтобы создавать бесполезный кэш.
Некоторая вариативность с высокой кардинальностью является намеренной. CDN или обратные прокси-серверы могут внедрять значения, такие как географический регион, для предсказуемого разделения контента. Это работает, когда возможные значения контролируются, и каждый компонент согласен с их значением. Без этих ограничений кэш фрагментируется на варианты, которые он может никогда не использовать повторно.
Это была та проектная проблема, которую нам нужно было решить для поддержки Vary. Нам нужно было сохранить достаточно вариативности для выдачи правильного ответа, не позволяя случайным различиям между запросами разрушать эффективность кэширования.
Как правила кэширования (Cache Rules) управляют Vary
У клиентов Cloudflare уже было несколько способов обработки согласованного контента, аналогичного Vary. Они могли обходить кэш и позволять своему источнику разбираться с этим, воспроизводить логику согласования источника в пользовательском ключе кэша или другом правиле, использовать Worker или использовать такие функции, как Vary для изображений.
Эти варианты остаются полезными, но они либо отказываются от кэширования, дублируют логику приложения, требуют написания дополнительного кода, либо подходят для более узкого сценария использования. Vary в правилах кэширования может заполнить пробел между этими существующими функциями, разделив поддержку на два решения:
- Источник использует Vary для определения заголовков запроса, которые могут повлиять на ответ.
- Правило кэширования определяет, как Cloudflare обрабатывает значение каждого заголовка.
Правило кэширования (Cache Rule){{/L1}} не заставляет каждый ответ изменяться. Если источник не возвращает Vary, Cloudflare кэширует ответ в обычном порядке, хотя правило все равно может перезаписывать Accept и Accept-Language перед пересылкой запроса источнику.
Когда источник возвращает Vary, Cloudflare использует настроенное действие для каждого названного им заголовка. Заголовки без индивидуальной настройки используют действие по умолчанию для правила. Доступны три действия:
Действие
Что делает Cloudflare
Лучше всего использовать для
normalize
Нормализует заголовки запросов перед выбором закэшированного варианта, помогая эквивалентным запросам использовать общий закэшированный ответ. Применяет правила для Accept, Accept-Language и Accept-Encoding, специфичные для заголовков. Для других заголовков он удаляет лишние пробелы и объединяет повторяющиеся строки заголовков в исходном порядке, сохраняя регистр и внутренние пробелы.
Рекомендуемая отправная точка для заголовков согласования, где множество значений запроса сопоставляются с небольшим набором ответов.
passthrough
Использует необработанные байты заголовка запроса для сопоставления в кэше, сохраняя регистр, пробелы, порядок и дублирующиеся значения. Если заголовок появляется в нескольких строках, Cloudflare объединяет эти строки по порядку с помощью запятых для сопоставления в кэше. Действие Passthrough оставляет исходящие строки заголовка без изменений. Cloudflare по-прежнему может переписывать Accept-Encoding, если отключен параметр Respect Strong ETags.
Заголовки с контролируемым набором значений, где точное значение изменяет ответ.
bypass
Не сохраняет ответ в кэше, если сервер происхождения (origin) указывает этот заголовок в Vary. Существующие записи кэша не удаляются, поэтому выполните их очистку (purge), если их необходимо удалить.
Используйте для персонализированных, высококардинальных (high-cardinality) или неожиданных заголовков, таких как Cookie или User-Agent.
Мы рекомендуем использовать normalize по умолчанию. Для отдельных заголовков с персональными или неограниченными значениями используйте bypass. Используйте passthrough, когда точное значение меняет ответ.
Например, passthrough сохраняет различия в регистре, пробелах, порядке и дублирующихся значениях, даже если сервер происхождения относится к ним как к эквивалентным. При наличии Vary: X-View и действии passthrough эти три значения создают отдельные ключи кэша:
X-View: compact,full
X-View: Compact,full
X-View: compact, full
Достаточное количество случайных вариаций может превратить повторно используемый ответ во множество одноразовых вариантов в вашем кэше.
Независимо от настроенных действий, Vary: * всегда обходит кэш. Это означает, что любой аспект запроса, даже информация вне HTTP-сообщения (например, IP-адрес клиента), может повлиять на то, какой ответ выберет origin. Поэтому Cloudflare не может повторно использовать ответ для последующего запроса без обращения к origin.
Как ответ проходит через кэш
Давайте проследим за одним из запросов /catalog из примера выше через Cloudflare.
При первом запросе у Cloudflare нет сохраненных данных Vary для ресурса, поэтому поиск в кэше завершается промахом (cache miss). Соответствующее правило кэширования (Cache Rule) может нормализовать настроенные поля перед тем, как Cloudflare обратится к origin.
Это может произойти до того, как Cloudflare узнает, будет ли итоговый ответ содержать Vary. Cache Rule определяет разрешенную нормализацию; затем ответ определяет, станут ли эти поля частью закэшированного варианта.
Этот порядок имеет значение. Если бы Cloudflare сгруппировал несколько исходных значений под одним нормализованным ключом кэша, но origin все равно получил бы эти исходные значения, origin мог бы выдать разные ответы, которые кэш позже посчитал бы взаимозаменяемыми. Пересылка нормализованного значения согласовывает выбор origin с сопоставлением в кэше.
Origin отвечает:
Vary: Accept, Accept-Language
Cloudflare записывает эти имена заголовков и сохраняет ответ как закэшированный вариант. Значения заголовков, обработанные в соответствии с Cache Rule, отличают этот вариант от других для того же ресурса.
Когда поступает другой запрос на /catalog, Cloudflare начинает с базового ключа кэша ресурса: обычно это URL плюс любые другие настроенные поля ключа. Затем система считывает сохраненные поля Vary и применяет Cache Rule к этим заголовкам в новом запросе, чтобы определить соответствующий закэшированный вариант.
Предположим, они нормализуются до:
Accept: text/html
Accept-Language: en,fr
Cloudflare использует эти значения для прямого поиска соответствующего закэшированного варианта. Система не сравнивает запрос со всеми сохраненными вариантами по очереди.
Если соответствующий вариант существует и является актуальным, запрос считается попаданием в кэш (cache hit). Если нет, Cloudflare отправляет запрос на origin и может сохранить полученный ответ как еще один вариант.
Ответ origin завершает цикл. Для каждого заголовка, указанного в Vary, Cloudflare использует действие, настроенное для этого заголовка, или действие правила по умолчанию, если заголовок не указан индивидуально:
- Если он не содержит Vary, Cloudflare кэширует его в обычном режиме.
- Если каждый указанный заголовок сводится к normalize или passthrough, Cloudflare может сохранить ответ как закэшированный вариант.
- Если какое-либо из указанных полей использует bypass, Cloudflare не сохраняет ответ.
- Если ответ содержит Vary: *, Cloudflare не сохраняет его.
Это возлагает важную ответственность на origin. Каждый кэшируемый ответ, который может отличаться в зависимости от полей запроса, должен последовательно возвращать соответствующий заголовок Vary, включая ошибки и резервные ответы (fallback). Если в одном ответе он будет пропущен, Cloudflare может закэшировать этот ответ без вариативности, необходимой для его изоляции.
Ключи кэша на диаграмме являются концептуальными. Последующий запрос предполагает наличие актуального закэшированного ответа.
Любая очистка (purge), направленная на закэшированный ресурс, охватывает все его варианты Vary. Существующие requirements для очистки пользовательских ключей кэша продолжают действовать.
Изменение конфигурации Vary не приводит к автоматической очистке существующего содержимого. Новая политика может создавать другие ключи кэша: запросы могут приводить к промахам и заново наполнять кэш под новыми ключами, пока старые записи остаются до истечения их срока действия или очистки.
Нормализация объединяет эквивалентные запросы
Помните приведенные выше запросы с запросом английского и французского языков?
Accept-Language: en-US, fr;q=0.8
Accept-Language: fr;q=0.8, en-GB
Оба запроса предпочитают английский язык, но passthrough обработает их как разные варианты. Если Cache Rule разрешает en, fr и de, действие normalize сводит оба запроса к en,fr, позволяя им использовать один и тот же закэшированный ответ.
Для этого Cloudflare переводит значения в Accept, Accept-Language и Accept-Encoding в нижний регистр, а затем сортирует их по значению качества (quality value) по убыванию, используя алфавитный порядок для разрешения совпадений. Таким образом, порядок, заданный клиентом, не влияет на ключ кэша. После сортировки Cloudflare удаляет параметры из записей с ненулевым значением качества. Обозначение q=0 («неприемлемо») также может теряться при сокращении языковых тегов или фильтрации до настроенных форматов и языков. Например, en-US;q=0 может превратиться в en. Используйте passthrough для Accept или Accept-Language, если origin должен видеть эти исключения.
Вы также можете настроить правило так, чтобы в Accept и Accept-Language сохранялись только указанные типы медиа или языки. Региональные языковые теги, такие как en-US, сокращаются до их базового языка en, если не настроен полный тег. Это позволяет согласовать нормализацию с форматами и языками, которые фактически обслуживает ваш origin.
Чтобы согласовать выбор origin с сопоставлением в кэше, Cloudflare пересылает нормализованные значения Accept и Accept-Language на origin. Он также пересылает нормализованные значения Accept-Encoding, когда включен параметр Respect Strong ETags. Другие заголовки нормализуются только для сопоставления в кэше.
Настройка Vary в Cache Rules
В панели управления Cloudflare перейдите в Caching > Cache Rules, создайте или отредактируйте правило, сделайте ответ подходящим для кэширования и добавьте параметр Vary. Задайте поведение по умолчанию, затем добавьте заголовки, которые должен указывать ваш origin.
Эта же конфигурация доступна через Rulesets API на этапе http_request_cache_settings. Настройка по умолчанию выбирает резервное действие для заголовков, указанных вашим origin в Vary, которые не были настроены индивидуально.
В этом примере Accept и Accept-Language нормализуются до настроенного набора форматов и языков. Действие normalize по умолчанию также применяется к другим заголовкам, указанным в Vary:
Это полное тело запроса для метода PUT к точке входа этапа http_request_cache_settings. Метод PUT заменяет каждое правило в этой точке входа. Если у вас уже есть правила кэширования (Cache Rules), включите их в массив rules или используйте соответствующую операцию создания или обновления для одного правила.
Если источник возвращает по одному представлению для каждого сочетания типа медиа и языка, существует шесть возможных комбинаций контента. Это не ограничивает кэш шестью ключами. Порядок приоритетов, отсутствие заголовков и значения, которые нормализуются до пустого состояния, могут создавать большее количество ключей. Поддерживайте небольшой набор и четко определяйте границы правила. После развертывания протестируйте один и тот же URL с различными значениями заголовков, которые должны нормализоваться к одному и тому же кэшированному варианту. Отправьте тестовые запросы с одного клиента, убедитесь, что они возвращают ожидаемый формат и язык, и проверьте CF-Cache-Status. Ищите попадания (hits), как только кэш заполнится, и исследуйте постоянные промахи (miss) или неожиданные обходы кэша (bypass).
Сведения об ограничениях, дополнительные примеры и информацию о настройке в Terraform см. в документации по Vary.
Почему бы не использовать пользовательский ключ кэша?
В этот момент возникает закономерный вопрос: «почему бы не добавить Accept и Accept-Language в пользовательский ключ кэша?»
Это работает, когда эти поля всегда являются частью идентификатора ресурса. Но пользовательский ключ кэша добавляет настроенные параметры к каждому ответу, на который распространяется правило, независимо dari того, использовал их источник или нет.
Заголовок Vary управляется ответом, но кэшируемые ответы под одним базовым ключом требуют согласованного набора полей Vary.
Используйте пользовательский ключ кэша, когда свойство запроса всегда определяет ресурс. Используйте Vary, когда источник объявляет один и тот же набор полей запроса для кэшируемых ответов. Избегайте добавления одного и того же заголовка в оба места, если только это дублирование не является осознанным и протестированным.
Используйте Vary в правилах кэширования уже сегодня!
Vary помогает решить очевидную проблему: один URL может иметь более одного правильного ответа. Но это создает для кэша более сложную задачу: какие именно различия в запросах имеют значение? Источник знает, какие ответы он может предоставить. Кэшу нужно знать, какие запросы могут повторно использовать каждый ответ.
Vary в правилах кэширования связывает эти два представления. Источник определяет поля запроса, которые могут влиять на ответ. Вы решаете, нужно ли нормализовать значения, использовать режим passthrough для точных различий или исключить ответ из кэша.
Заголовок Vary никогда не был слишком «некрасивым», чтобы быть полезным. Однако ручная настройка поддерживаемых форматов и языков может подойти не для каждого приложения. Мы оцениваем, могут ли идеи из устаревшего черновика Availability Hints упростить эту задачу, позволяя источнику напрямую описывать предоставляемые им представления.
Vary в правилах кэширования доступен уже сегодня на тарифах Free, Pro, Business и Enterprise через панель управления Cloudflare, API наборов правил и Terraform.












