Запросы поддиапазонов источника: передача больших файлов без настройки таких запросов

Источник: Cloudflare Blog•

Запросы поддиапазонов источника: передача больших файлов без настройки таких запросов

Наивное кэширование диапазонов байтов может тратить гигабайты пропускной способности источника на небольшие запросы. Мы перестроили нашу архитектуру кэширования в Pingora для автоматической координации частичных выборок, сократив p99 задержку R2 в два раза.

Видеоплеер, перематывающий видео в середину, запрашивает несколько мегабайт. При старом кэшировании диапазонов этот единственный запрос мог заставить Cloudflare загрузить 128 МиБ из источника и передать их между дата-центрами, иногда уже после того, как зритель ушел. Запросы диапазонов — это способ доставки больших файлов, и их наивное кэширование легко приводит к пустой трате сетевого трафика и дисковых операций при работе CDN в больших масштабах.

Поэтому мы переработали то, как наш кэш обрабатывает их. Для R2, объектного хранилища Cloudflare, время до первого байта (p99 TTFB) сократилось примерно вдвое, а последующее улучшение уменьшило трафик многоуровневого кэша для R2 и аналогичных рабочих нагрузок примерно на 6%.

Сегодня мы делаем эту новую реализацию доступной в качестве функции самообслуживания Origin Range Requests для всех тарифных планов. Здесь не нужно настраивать размер диапазона или обращаться в службу поддержки. Включите ее с помощью правила кэширования, а остальное сделает Cloudflare.

Запрос части файла

HTTP-запрос диапазона запрашивает часть ресурса вместо всего ресурса. Клиент отправляет обычный запрос GET с заголовком Range:

Если сервер поддерживает запрос, он возвращает статус 206 Partial Content и указывает возвращенные байты с помощью заголовка Content-Range:

Запросы диапазонов типичны для больших объектов. Виноплеер может запрашивать байты вокруг позиции перехода вместо всего файла. Менеджер загрузок может возобновить загрузку после прерывания. Менеджер пакетов может получить часть артефакта. Клиент может изучить конец файла, не скачивая все, что находится перед ним.

Клиент сам решает, запрашивать ли диапазон, и Cloudflare не изменяет этот запрос. Вместо этого Origin Range Requests меняет то, как Cloudflare его удовлетворяет.

Проблема размера диапазона

Cloudflare поддерживает кэширование запросов диапазонов уже много лет благодаря статической конфигурации зон.

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

Размер каждого диапазона создавал компромисс: маленькие диапазоны приводили к множеству мелких запросов к источнику и создавали нагрузку на память Cloudflare из-за объединения всех запросов в памяти, в то время как большие диапазоны заставляли Cloudflare скачивать гораздо больше данных, чем запрашивал клиент. Изменения самого размера диапазона приводили к недействительности записей в кэше, так как размер являлся частью ключа кэша.

Этот дизайн также означал, что пользователи, запрашивающие контент через нашу сеть, могли столкнуться с неудобствами при доступе к таким файлам. Параллельные запросы, попадающие в один и тот же диапазон, независимо от того, насколько далеко друг от друга находились запрошенные клиентами диапазоны, могли блокировать друг друга. Наш кэш на базе nginx также не мог объединять кэширование диапазонов с параллельной потоковой передачей — функцией, созданной нами для того, чтобы несколько клиентов могли читать файл из кэша во время его записи. Включение обеих функций одновременно сокращало ответы, поэтому каждый диапазон должен был быть полностью загружен, прежде чем кто-либо мог его прочитать, а популярным файлам требовалась отдельная выборка из источника для каждого диапазона.

Лишние байты также могли проходить через несколько частей нашего распределенного кэша: от источника до одного дата-центра, между уровнями кэша и затем к клиенту. Каждый переход умножал потери. Ни одно значение не работает одинаково хорошо для небольшого перехода, последовательной загрузки, большого медиафайла, а также для любого источника и топологии кэша.

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

К счастью, наш переход с nginx на сервер кэширования на базе Pingora создал возможность решить проблемы реализации, которые заставляли навязывать клиентам сложную в использовании настройку.

Чрезмерные фрагменты кэша

Решение пришло из того, что Pingora уже делала. Чтобы упростить возобновление прерванных загрузок, она хранит каждый закэшированный файл на диске в виде фрагментов фиксированного размера. Если кэш уже мыслит фрагментами, размер диапазона вообще не нужно делать настройкой — им может быть просто сам фрагмент!

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

Например, запрос байтов 150-250 может заставить Cloudflare получить выровненный фрагмент, содержащий эти байты. Ответ ниже по цепочке все равно будет содержать только байты 150-250.

Это не устраняет лишнюю работу для запросов, меньших размера нашего фрагмента — такой запрос все равно может потребовать загрузки всего содержащего его фрагмента. Мы также берем на себя затраты на дополнительную работу для невыровненных диапазонов. Разница в том, эта дополнительная работа ограничена и предсказуема и больше не определяется выбранным клиентом размером диапазона, который может вызывать непредсказуемые эффекты для нас и источников.

Координация закэшированных и выполняющихся данных

Анализ заголовка Range довольно прост. Более сложная задача — сформировать один корректный ответ, когда запрашиваемые байты разделены между закэшированными данными, ответом источника и уже выполняющимися загрузками.

На высоком уровне перигр:

  • Ищет заголовки ресурса и метаданные покрытия диапазонов.
  • Считывает ту часть ресурса, которую может, с диска или путем потоковой передачи в памяти.
  • Загружает недостающее покрытие из источника, используя выровненные диапазоны.
  • Записывает новые байты и фиксирует завершенный диапазон в метаданных.
  • Объединяет части и устанавливает Content-Range и Content-Length для клиентского ответа.

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

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

Конструкция использует существующую модель данных кэша. Заголовки идентифицируют версию ресурса, тело хранится в ограниченных фрагментах, а метаданные фиксируют, какие диапазоны были успешно записаны.

Проверка перед формированием

Скомпонованные ответы на запросы диапазонов действительны только тогда, когда все байты принадлежат одному и тому же представлению. Координатор проверяет, соответствует ли каждый ответ источника запрошенному диапазону, стабилен ли общий размер ресурса и остается ли сущностный тег (ETag) неизменным во всех подзапросах.

Заголовок If-Range позволяет клиенту запросить диапазон только в том случае, если представление по-прежнему соответствует известному валидатору. Если оно не совпадает, Cloudflare возвращает полное представление вместо объединения байтов из разных версий.

Cloudflare также избегает компоновки диапазонов для представления, которое он собирается распаковать, поскольку диапазон по набору сжатых байтов будет отличаться от диапазона, выбранного в распакованном ответе. В таких случаях Cloudflare передает преобразованный полный ответ, а не сообщает о диапазоне, который он не может корректно обработать.

Полученное поведение соответствует стандартному HTTP:

  • Удовлетворительный диапазон получает статус 206 Partial Content с точным заголовком Content-Range.
  • Неудовлетворительный диапазон получает статус 416 Range Not Satisfiable.
  • Ответ, к которому нельзя безопасно применить диапазоны, передается как полное представление.

Что изменилось в продакшене

Прежде чем открыть Origin Range Requests для всех, мы перевели существующих пользователей старой функции кэширования диапазонов на новую реализацию, начав с нагрузок, которые доставляли нам больше всего проблем.

Эти нагрузки характеризовались множеством одновременных запросов диапазона к очень большим файлам, часто при очень пиковом трафике. Настроенные размеры диапазонов в старой системе варьировались от десятков килобайт до десятков гигабайт. При большом размере диапазона запрос, который не заполнил бы дискету, мог заставить нас скачать объем данных, эквивалентный нескольким DVD, с исходного сервера и передать его через несколько дата-центров, иногда после того, как клиент уже сдался и ушел. При малом размере диапазона один запрос превращался в длинную цепочку крошечных запросов к источнику, каждый из которых удерживал память в ожидании. Оба паттерна вызывали у нас реальные операционные инциденты.

R2 был крупнейшим внутренним пользователем. Его настроенный размер диапазона составлял 64 МиБ, поэтому небольшое чтение, пересекающее границу диапазона, могло стоить нам 128 МиБ передачи данных между дата-центрами. После перевода R2 на новую реализацию мы увидели:

  • Более быстрые ответы. Во всех регионах хранения R2 показатель p99 времени до первого байта сократился примерно вдвое как при попаданиях в кэш, так и при промахах.
  • Меньше байтов в нашей сети. Наше самое последнее изменение научило координатор объединять байты, уже имеющиеся на диске, с байтами, полученными из вышестоящего источника, в одном ответе. Раньше запрос обслуживался либо полностью из кэша, либо полностью из вышестоящего источника. После того как мы включили это для R2, многоуровневый трафик кэша для R2 и аналогичных нагрузок сократился примерно на 6% неделю к неделе, что означает на петабайты меньше данных, перемещающихся между нашими дата-центрами каждый день.

Начало работы

Как только кэш перестал зависеть от выбранного клиентом размера диапазона, конфигурация продукта стала предельно простой. Клиенты выбирают соответствующий трафик с помощью Cache Rules. Cloudflare берет на себя разбиение на чанки и координацию кэша.

Origin Range Requests предназначена для трафика, где клиенты естественным образом запрашивают части большого кэшируемого объекта:

  • Прогрессивное и доступное для перемотки видео, такое как MP4
  • Возобновляемые крупные загрузки
  • Распространение программного обеспечения и репозитории пакетов
  • Образы дисков, архивы и другие объекты размером в несколько гигабайт
  • Рабочие нагрузки обучения и инференса ИИ, которые передают файлы больших моделей или извлекают выбранные области артефактов моделей
  • Клиенты, которые извлекают метаданные или небольшую область большего файла

Мы намерены сделать это стандартным способом обслуживания определенных типов файлов в нашей сети с возможностью изменения с помощью правил кэширования.

Эта функция не меняет стандартные требования для кэширования. Ее включение не делает в противном случае некэшируемый ответ кэшируемым. Источник также должен поддерживать байтовые диапазоны, чтобы частичные запросы к источнику были полезны.

Чтобы включить ее, создайте Cache Rule, соответствующее вашему трафику больших файлов, и установите для параметра Origin Range Requests значение On. Вы можете сделать это на панели управления в разделе Caching > Cache Rules или через API с помощью параметра origin_range_requests: { "mode": "on" }. Клиенты выбирают трафик; пограничный слой обрабатывает координацию кэша и требования корректности.

О чём эта статья

Ещё в разделе «Облака и инфраструктура»

Все →

Ещё от Cloudflare