Cache keys в стриминге и почему они ломаются

Автор: Николай СапуновОбновлено: август 202626 мин чтения
Содержание статьи +

TL;DR

Cache key – это короткий отпечаток, который Content Delivery Network (CDN) формирует из каждого входящего запроса и использует в качестве идентификатора кэшированного ответа. Проще говоря, это «имя файла» в кэше, и каждая деталь, которую CDN включает в ключ, разбивает один кэшированный объект на множество отдельных копий. Для видеостриминга cache key – самая критичная строка во всём конфиге CDN, потому что одна ошибка может незаметно поместить каждого зрителя в отдельную кэш-ветку, снизить hit ratio с 98% до однозначных значений и за считанные минуты увеличить нагрузку на origin в десять раз. Эта статья объясняет, что входит в стандартный cache key у каждого крупного CDN, разбирает семь типичных ошибок – токен в query string, ловушка Vary, разные hostnames, percent-encoding и регистр, cookies, избыточное географическое ключирование, перекрёстная конфигурация для сегментов и манифестов – и предлагает по одной-двум строкам конфигурации для каждого исправления. Арифметика в конце показывает, как 50 000-зрительский прямой эфир может увеличить исходящий трафик с 180 МБ/с до 6,25 ГБ/с только из-за лишнего токена в ключе; корректный ключ возвращает ситуацию в норму. Лучше прочитать эту статью до следующего запуска стрима, а не после.

Зачем это знать

Cache keys получают недостаточно внимания в литературе по стримингу. В центре внимания – бренд CDN, протоколы HLS, DASH, CMAF и стратегии multi-CDN, а вот cache key, та самая короткая строка, которая определяет, будет ли миллион зрителей использовать один кэшированный сегмент или каждый получит свою отдельную копию, остаётся в конфигурации, которую никто не трогает после первого деплоя. При этом почти каждое инцидентное обращение в продакшене за последние три года, где команда писала «origin горит», заканчивалось одной из трёх причин: cache key включал токен авторизации, query-параметр от плеера или унаследовал значение по умолчанию, разумное для e-commerce, но опасное для live-стрима. Эта статья даёт продакт-менеджеру модель для проверки инженерного плана («мы точно не положили user token в cache key?»), архитектору – названия конкретных «ручек» у каждого вендора, а оператору – семь пунктов чек-листа, которые нужно пройти перед запуском. К концу статьи вы сможете за тридцать секунд прочитать CloudFront Cache Policy, Cloudflare Cache Rule, Fastly VCL или Akamai property и понять, выдержит ли hit ratio стриминга день старта.

Что такое cache key, аккуратно

Cache key – это уникальный идентификатор, используемый для хранения и последующего извлечения данных из кэша. Он позволяет системе быстро определить, какие данные нужно вернуть, не выполняя повторных вычислений или запросов к источнику.

Например, если вы кэшируете результат API-запроса, cache key может быть построен на основе URL, параметров запроса и версии API. При повторном запросе с теми же параметрами система проверяет, существует ли запись с таким ключом в кэше. Если да – данные возвращаются мгновенно. Если нет – выполняется новый запрос, а результат сохраняется под этим же ключом.

Правильный выбор cache key критически важен: слишком общий ключ приведёт к коллизиям и потере актуальности данных, слишком специфичный – к избыточному использованию памяти.

«Важно: cache key должен однозначно описывать контекст запроса, но при этом быть достаточно стабильным, чтобы избежать ненужных промахов в кэше.»

Пример хорошего cache key: api/users/{id}/profile?lang=ru&v=2 → ключ: users:profile:123:ru:v2

Пример плохого cache key: просто users:profile, если не учитывается язык, версия или другие параметры.

Таким образом, cache key – это не просто строка, а продуманная часть архитектуры кэширования, влияющая на производительность и корректность работы системы.

Начнём с определения, которое уточним позже. Cache key – это детерминированный идентификатор, короткая строка, которую кэш формирует из выбранных частей запроса и использует для поиска сохранённого ответа. Когда плеер зрителя запрашивает видеосегмент, CDN не ищет в кэше по URL напрямую: вместо этого он строит cache key на основе URL, выбранных заголовков, cookies и query-параметров, хэширует результат и ищет этот хэш в хранилище. Если ключ совпадает с существующей записью, кэш возвращает сохранённый ответ. Если совпадений нет – кэш запрашивает контент у следующего уровня: shield, регионального кэша или, в крайнем случае, origin; сохраняет полученный ответ под новым ключом и отдаёт его клиенту.

Аналогия для нетехнического читателя – карточка в библиотечном каталоге. На карточке нет самой книги, а только те признаки, по которым библиотекарь её ищет. Библиотека, которая каталогизирует книги только по названию, складывает всех «Гамлетов» в один слот. Библиотека, которая использует для каталогизации название, автора, издание, язык, переплёт и дату последней выдачи, кладёт каждого «Гамлета» в отдельный слот – и библиотекарь не находит ни одной книги на полке, когда десять читателей одновременно спрашивают «Гамлета». Cache keys ведут себя так же. Чем меньше признаков в ключе, тем больше запросов попадают на один и тот же кэшированный объект и тем выше hit ratio. Чем больше признаков – тем чаще каждый запрос получает свою собственную частную копию (в литературе это явление называют cache fragmentation), и тем сильнее падает hit ratio.

В стриминге меняются ставки. Статический сайт может обслуживать сотни уникальных URL из одного cache key: если ключ дробится в десять раз – получается тысяча отдельных копий, и сайт продолжает работать. Live-стрим, напротив, отдаёт одни и те же несколько URL-сегментов всем зрителям. Если ключ дробится даже на небольшой коэффициент – по предпочитаемому языку, по варианту Accept-Encoding или по, казалось бы, безобидному session ID – кэш не вмещает все копии, hit ratio падает, и запросы уходят на origin. Origin перегружается, счёт за CDN резко растёт, плеер начинает буферизоваться в регионах, которые никто не планировал. Cache key – единственная точка во всём стриминг-стеке, где одна строка конфигурации стоит между «всё в порядке» и «всё горит».

Рисунок 1. Анатомия cache key. CDN выбирает поля из входящего запроса, хэширует их и использует результат для поиска кэшированного ответа.

Дефолтный ключ кэша у каждого крупного CDN

Дефолты важны: инженер, который ни разу не открывал конфиг, запускает в продакшн то, что ему выдал CDN в первый день. Дефолты у разных вендоров схожи, но различаются в ключевых деталях.

Cloudflare. Дефолт: {scheme}://{host}{path}?{sorted_query_string}. В документации Cloudflare это прямо указано: шаг сортировки внутри правил кэширования по умолчанию упорядочивает параметры строки запроса (query string) в алфавитном порядке, чтобы два URL с одинаковыми параметрами, но в разном порядке, использовали один и тот же кэш-объект. Заголовки и куки в ключе кэширования по умолчанию не участвуют. В разделе Caching на панели управления есть отдельный параметр Query String Sort, который можно включать или отключать, однако внутренняя алфавитная сортировка включена по умолчанию.

AWS CloudFront. Дефолт: URL плюс то, что включает активная Cache Policy. Cache Policy явно перечисляет, какие query strings, заголовки и cookies входят в ключ – это QueryStringsConfig, HeadersConfig, CookiesConfig. CloudFront предоставляет управляемые политики: Managed-CachingOptimized не включает ни одного заголовка, ни одной cookie, ни одного query-параметра в ключ; Managed-CachingDisabled отключает кэширование полностью. Отдельная Origin Request Policy управляет тем, какие заголовки и параметры CloudFront передаёт origin, не включая их в ключ кэша – это разделение особенно важно для стриминга, и мы к нему вернёмся ниже.

Fastly. Дефолт: URL и заголовок Host объединяются в подпрограмме vcl_hash. В документе VCL best practices Fastly прямо предупреждает инженеров о нежелательности изменения этого хэша без необходимости и рекомендует использовать Vary как более гибкий основной подход. Флаг req.hash_always_miss принудительно вызывает промах кэширования без отключения объединения запросов – полезен при отладке, но опасен в продакшене.

Akamai. Дефолт: URL; параметры запроса в ключе кэширования и модификации cache ID настраиваются индивидуально для каждого свойства в Property Manager. В Akamai это называется cache ID – по сути то же самое, что и cache key, но с использованием другой терминологии; поведение Cache ID Modification определяет, какие компоненты ключа добавляются или удаляются.

Google Media CDN. По умолчанию: URL; что дополнительно входит в ключ – задаётся в настройках кэширования.

Сводная таблица:

CDNДефолтный cache keyЧего НЕТ в дефолтном ключеVary header учитывается?
Cloudflarescheme + host + path + сортированный query stringHeaders, cookiesТолько для allow-list заголовков
AWS CloudFrontURL + поля Cache PolicyВсё, что Cache Policy исключаетУчитывается для Accept-Encoding, Accept-Language, Origin
FastlyURL + HostHeaders, cookiesПо умолчанию; собирается secondary key по спецификации
AkamaiURL (Cache ID)Headers, cookies, query strings (если не настроено)По умолчанию игнорируется; есть Remove Vary Header behaviour
Google Media CDNURLHeaders, cookies, query strings (если не настроено)По конфигурации

Картина похожая: все вендоры начинают с «URL плюс чуть-чуть» и дают возможность что-то к этому добавлять. Две самые дорогие ошибки в ключе кэша вырастают из того, что инженеры в эту простую основу добавляют слишком много.

Vary header – «половинный близнец» cache key в спецификации

У HTTP-спецификации есть своё мнение о cache keys, и стриминг-инженер, который его проигнорирует, рискует оказаться в неловком споре с поддержкой CDN. Главный документ – IETF RFC 9111, HTTP Caching, июнь 2022, который заменил RFC 7234.

Механизм – это заголовок ответа Vary. Когда сервер возвращает ответ с Vary: Accept-Language, кэш интерпретирует это как соглашение: ответ был выбран на основе заголовка запроса Accept-Language клиента, и кэш не должен отдавать этот сохранённый ответ другому запросу, у которого значение Accept-Language отличается от оригинального. RFC 9111 §4.1 называет получившийся расширенный ключ secondary cache key – первичный ключ остаётся URL + метод, а заголовки запроса, перечисленные в Vary, формируют вторичный ключ для каждого сохранённого ответа. Ответ с Vary: * никогда не совпадает, что заставляет кэш каждый раз выполнять ревалидацию; это прямо указано в спецификации.

Для стриминга последствия могут быть серьёзными и контринтуитивными. Если origin-акселератор ставит Vary: Accept-Encoding, Cookie, User-Agent на каждый манифест и сегмент, каждый зритель с другим User-Agent – будь то Safari, определённая версия Chrome или модель Roku – получает свою отдельную кэш-копию. Один неправильно настроенный заголовок Vary может раздробить кэш в пятьдесят раз по версиям браузеров. Защитная практика на стороне origin: держать Vary коротким, указывать только точные имена заголовков и ни в коем случае не включать User-Agent, Cookie или * в ответах стриминга. Общая рекомендация Apple HLS Authoring Specification по кэшированию говорит то же самое – ключи кэша для сегментов должны быть простыми; сегменты неизменяемы.

Поведение CDN с заголовком Vary различается. Cloudflare учитывает Vary только для небольшого списка разрешённых заголовков (классический пример – Accept-Encoding). Akamai по умолчанию игнорирует большинство заголовков Vary и рекомендует удалять их на edge, если это не Vary: Accept-Encoding. Fastly по умолчанию уважает Vary и формирует вторичный ключ кэша в соответствии со спецификацией. В результате: origin-сервер, полагающийся на стабильную работу Vary, может наблюдать разную hit ratio на разных CDN для одного и того же контента. В качестве защитной практики, которую мы внедряем по умолчанию, рекомендуется явно задавать cache key через правила кэширования CDN, а использовать Vary как страховку, а не основной механизм.

Семь ошибок cache key, которые ломают стриминг

Эти семь ошибок вызывают более девяти из десяти инцидентов с cache key, с которыми мы помогали стриминг-командам разобраться. Приведены в порядке убывания частоты.

Ошибка 1. Session token в query string

Типовой паттерн авторизации в ранних стриминговых стеках – подпись URL путём добавления токена в query: …/segment_4172.ts?token=eyJh…. Токен уникален для каждого зрителя, а иногда и для сессии, а по умолчанию ключ кэширования включает query string. В результате получается одна кэш-копия на зрителя на сегмент; коэффициент попаданий падает почти до нуля.

Исправление – убрать токен из query string и перейти на signed-cookie (Cloudflare Signed Cookies, CloudFront Signed Cookies, Cloud CDN Signed Cookies) или схему подписи URL-префикса, при которой CDN удаляет подписанный сегмент пути из cache key. AWS, Cloudflare, Google, Bunny и Akamai именно такую рекомендацию дают в документации по token-авторизации. Dual-токенная аутентификация в Google Media CDN – канонический пример: подпись мастер-URL краткосрочная и специфичная для зрителя, а подпути под ней подписаны более долгоживущим общим токеном, который исключается из cache key. В результате получается контроль доступа на уровне зрителя при глобально разделяемом кэше.

Ошибка 2. Authorization header в cache key

Вторая разновидность той же проблемы: origin или CDN-политику настроили так, что в ключ кэша включается заголовок запроса Authorization. А заголовок Authorization содержит JWT или непрозрачный токен, который уникален для каждого пользователя. В результате кэш дробится по пользователям, и показатель hit ratio падает. Решение – исключить заголовок Authorization из ключа кэша, но при этом продолжать передавать его на origin для проверки авторизации. Origin Request Policy в CloudFront – как раз тот инструмент, который это обеспечивает: заголовок доходит до origin для валидации, а ключ кэша остаётся чистым.

Ошибка 3. Ловушка Vary

Origin-пакеджер возвращает Vary: User-Agent манифест. CDN учитывает заголовок Vary и шардирует кэш по каждой уникальной строке User-Agent. Обновление версии Mobile Safari, выход новой версии Chrome или появление новой модели Roku – каждое из этих событий порождает отдельный кэшированный объект. В результате снижается hit ratio, а origin начинает сталкиваться с длинным хвостом запросов. Решение – ограничить заголовок Vary на origin небольшим allow-листом (Vary: Accept-Encoding и только его, если нет веской причины) и использовать поведение Akamai Remove Vary Header или аналогичный механизм на Cloudflare/FASTLY в качестве фильтра-страховки на edge.

Ошибка 4. Фрагментация по hostname

Стриминг-нагрузка распределяется между двумя CNAME – cdn1.example.com и cdn2.example.com, которые ведут на один и тот же CDN. Заголовок Host входит в ключ кэширования по умолчанию для каждого CDN. В результате один и тот же сегмент хранится дважды – по одной копии на каждый hostname. Если в нагрузке три hostname, получается три копии, и так далее. Решение – либо направить весь трафик через единый канонический хост, либо использовать нормализацию хоста в CDN (например, опция «Resolved host» в Cloudflare, кэширование только по пути в CloudFront, переписывание req.http.Host в vcl_hash в Fastly), чтобы объединить несколько hostname в один ключ кэширования.

Ошибка 5. Несовпадение percent-encoding и регистра

/segment%204172.ts, /segment 4172.ts и /Segment_4172.ts – это три разных ключа в кэше, даже если они ссылаются на один и тот же файл на origin. Разные версии плееров по-разному кодируют символы: Android-устройство, приводящее путь к верхнему регистру, не может делить кэш с iOS-устройством, использующим нижний регистр. Решение – принудительно применять каноническую форму URL на edge: использовать строчные пути и нормализовать кодировку. Это делается одной правкой через Cloudflare Transform Rules, CloudFront Functions, Akamai Modify Outgoing Request или одной строкой set req.url в vcl_recv у Fastly. Объём работ небольшой, а влияние на hit ratio заметно при каждом мультиплатформенном запуске.

Ошибка 6. Cookies в ключе кэширования

Cookie на сайте отслеживает последний просмотренный эпизод и устанавливается при каждом запросе, включая запросы на сегменты. Политика кэширования CDN включает Cookie в ключ кэширования. В результате каждый зритель получает отдельный кэшированный сегмент, и коэффициент попаданий (hit ratio) падает. Решение – исключить cookies из ключа кэширования для стримингового поддомена. CookiesConfig.CookieBehavior: none в CloudFront – пример такой настройки; Cloudflare по умолчанию не включает cookies в ключ кэширования, поэтому достаточно просто не активировать эту опцию через Cache Rule. Общий принцип: cookies относятся к origin приложения, а не к CDN для стриминга.

Ошибка 7. Перекрёстная конфигурация для манифестов и сегментов

Манифесты изменяемы, сегменты – нет. Разумная конфигурация трактует их по-разному: короткий TTL на .m3u8 и .mpd, длинный TTL на .ts и .m4s. Типичная ошибка – применить один и тот же cache key recipe к обоим. Cache key манифеста наследует long-lived caching от сегмента, апдейты не пропагируются. Или, наоборот, сегменты получают short-TTL recipe, кэш вытесняет их за секунды, и каждый ретрай идёт на origin. Исправление – два отдельных cache rules: один для *.m3u8 / *.mpd / *.dash с TTL 1–5 секунд и включённым serve-stale; второй для сегментов *.ts / *.m4s / *.mp4 с max-age в один год и директивой immutable – плюс проверка перед запуском, что оба правила матчатся на правильные URL-паттерны.

Рисунок 2. Та же нагрузка с чистым cache key и с ключом, в который попал per-viewer токен. Одного токена достаточно, чтобы сломать кэш.

Арифметика: один токен в query string, в десять раз счёт

Возьмём тот же пример из предыдущей статьи раздела. Прямой спортивный трансляция, 50 000 одновременных зрителей при скорости 3 Мбит/с, сегменты HLS длительностью 4 секунды. Система обрабатывает 12 500 запросов на получение сегментов в секунду; каждый сегмент составляет 1,5 МБ (3 Mbps × 4 s ÷ 8 = 1,5 МБ).

Чистый cache key. Ключ – только URL. Edge hit ratio 88%, shield hit ratio 92%, остаточный miss на origin – 0,96%. Арифметика из статьи 6.2:

origin-промахи в сек = 12 500 × 0,12 × 0,08 = 120 fetch/сек
origin egress         = 120 × 1,5 МБ        = 180 МБ/сек

Cache key с per-viewer session token. Та же нагрузка, но cache key включает ?token=…, и у каждого зрителя токен свой. URL каждого сегмента теперь уникален для каждого зрителя; кэш хранит 50 000 версий сегмента 4172 вместо одной. Edge не вмещает их все, shield не вмещает их все, и почти каждый запрос зрителя на сегмент промахивается мимо всех уровней. Hit ratio на edge падает примерно до 5% – остаточный hit достигается только тогда, когда тот же зритель повторно запрашивает тот же сегмент в пределах cache window. Арифметика:

origin-промахи в сек = 12 500 × 0,95 × 0,95 = 11 281 fetch/сек
origin egress         = 11 281 × 1,5 МБ    = 16,92 ГБ/сек

Origin egress колеблется от 180 МБ/с до 16,92 ГБ/с – это рост в 94× при той же нагрузке. По стандартному тарифу AWS – $0,09 за гигабайт для EC2-выхода в интернет – некорректный стабильный счёт может вырасти с $42 000 в месяц до $3,95 млн в месяц. Даже при 4 часах пикового трафика в день (реалистичный сценарий для спортивного стримера) ошибочный счёт составит около $660 000 в месяц, тогда как корректный – всего $7 000. Всё исправляется одной строкой в правиле кэша.

Смысл арифметики – не в точной цифре, а в порядке величины. Ошибка в cache key не вызывает 20%-ной регрессии. Она приводит к регрессии в 50–100 раз, и настолько быстро, что дежурный инженер успеет заметить рост счётчика на origin раньше, чем сработает алерт мониторинга.

«Типичная ошибка. «Мы уже мониторим cache hit ratio в дашборде». Hit ratio – правильная метрика, но окно обновления дашборда почти всегда длиннее, чем время, за которое деплой с токеном в query string спалит квартальный бюджет CDN. Проводите аудит cache key до деплоя; считайте дашборд страховкой, а не заменой. Пре-лонч-чек-лист ниже – та версия, которую мы используем внутри.»

Пре-лонч-чек-лист по cache key

Короткий, вендорно-агностичный список из семи вопросов, на которые стриминг-команда должна письменно ответить перед любым live-запуском. Эту же версию мы публикуем внизу страницы как компаньон для скачивания.

Семь вопросов:

  1. Что сейчас в cache key? Выведите точное правило из консоли CDN. Если не удаётся сформулировать его одной фразой – правило слишком сложное.
  2. Есть ли session token в query string? Если да – перенесите его в signed cookie или в подписанный path-префикс.
  3. Входит ли заголовок Authorization в cache key? Если да – перенесите его в Origin Request Policy или аналог.
  4. Что перечисляет заголовок Vary на стороне origin? Если что-то, кроме Accept-Encoding, – обсудите возможность удаления на edge.
  5. Сколько hostnames раздают один и тот же контент? Если больше одного – нормализуйте к единому хосту в cache key.
  6. Нормализован ли путь по регистру и percent-encoding? Если нет – добавьте одну строку нормализации на edge.
  7. Обрабатываются ли .m3u8/.mpd и сегменты отдельными правилами? Если нет – разделите правило.

На каждый вопрос – краткая фраза-ответ и ссылка на строку конфигурации. Ревьюер должен быстро прочитать список, понять логику формирования cache key и одобрить или отклонить его за десять минут.

Где Фора Софт вписывается

Мы строим и эксплуатируем стриминговые стеки с 2005 года – OTT / Internet TV, e-learning, телемедицина, видеонаблюдение, конференц-платформы на WebRTC + HLS-гибридах – и аудит конфигурации cache key перед запуском является обязательной частью передачи каждого проекта. В одном OTT-касе мы сократили месячный счёт оператора за CDN более чем на 80%, убрав session token из cache key. В другом случае запуск e-learning-платформы удалось спасти за три дня до старта когорты, объединив четыре hostnames в один канонический streaming-поддомен. Паттерн повторяется по всем вертикалам: настройка cache key находится на стыке платформенного инженеринга и SRE, и большинство команд недооценивают её важность до первого инцидента.

Шпаргалка по вендорам, 2026

ЗадачаCloudflareAWS CloudFrontFastlyAkamaiGoogle Media CDN
Дефолтный cache keyscheme + host + path + sorted query stringURL + поля Cache PolicyURL + Host (vcl_hash)URL (Cache ID)URL
Добавить query в ключCache Rule > Custom Cache KeyCache Policy > QueryStringsConfigset req.url в vcl_recvCache Key Query ParameterscdnPolicy.cacheKeyPolicy.includedQueryParameters
Передать заголовок без keyingWorkers / Transform RuleOrigin Request Policyreq.http.X = …; remove from vcl_hashModify Outgoing RequestOrigin request configuration
Нормализовать hostResolved host settingCache Policy > HeadersConfig (omit Host)Переписать req.http.Host в vcl_recvCache ID Modification > HostnameBackend bucket host rewrite
Срезать Vary на edgeWorkers / Response Headers Transform RuleResponse Headers Policyunset beresp.http.Vary в vcl_fetchRemove Vary HeaderCustom response headers

Шпаргалка не заменяет вендорную документацию – это индекс, по которому ревьюер за два клика находит нужный экран конфигурации.

Ключевые выводы

  • Ключ кэша – это «имя файла» в системе кэширования; содержимое определяет, сколько уникальных копий хранится в кэше.
  • По умолчанию у Cloudflare, CloudFront, Fastly, Akamai и Google Media CDN ключ кэша строится по принципу «URL плюс небольшие дополнения»; именно эти добавления сверху нарушают стриминг.
  • Семь типичных ошибок – токен в query-параметрах, включение заголовка Authorization в ключ, ловушка Vary, фрагментация по host, несоответствие кодировок, использование cookies, а также перекрёстные правила для сегментов и манифестов – покрывают более 90% инцидентов.
  • Одна ошибка с токеном в query-параметрах может увеличить исходящий трафик с origin в 50–100 раз без какого-либо предупреждения.
  • Механизм Vary из RFC 9111 – это как бы «половинный близнец» ключа кэша по спецификации; его нужно проектировать одновременно с явной конфигурацией CDN.
  • Перед каждым запуском в продакшн проходите чек-лист из семи вопросов; дашборды с hit ratio – это страховка, а не замена тщательной проверки.

Что читать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.