Бенуа Ростикус, Габриэль Хартманн

Шумные соседи

У всех нас когда-то были шумные соседи. Будь то в кафе или сквозь стену квартиры, это всегда мешает. Потребность в хороших манерах в общих пространствах оказывается важной не только для людей, но и для ваших контейнеров Docker.

Когда вы работаете в облаке, ваши контейнеры находятся в общем пространстве; в частности, они разделяют иерархию памяти ЦП экземпляра хоста.

Поскольку микропроцессоры настолько быстры, дизайн компьютерной архитектуры эволюционировал в сторону добавления различных уровней кэширования между вычислительными модулями и основной памятью, чтобы скрыть задержку передачи битов в мозг. Однако ключевой вывод здесь заключается в том, что эти кеши частично разделяются между процессорами, а это означает, что идеальная изоляция производительности совместно размещенных контейнеров невозможна. Если контейнер, работающий на ядре рядом с вашим контейнером, внезапно решит получить много данных из ОЗУ, это неизбежно приведет к большему количеству промахов в кеше (и, следовательно, к потенциальному снижению производительности).

Linux приходит на помощь?

Традиционно планировщик задач операционной системы был обязан смягчить эту проблему изоляции производительности. В Linux текущим основным решением является CFS (Completely Fair Scheduler). Его цель - «справедливо» назначить запущенные процессы на временные интервалы ЦП.

CFS широко используется и поэтому хорошо протестирован, а Linux-машины по всему миру работают с приемлемой производительностью. Так зачем с этим связываться? Как оказалось, для подавляющего большинства случаев использования Netflix его производительность далека от оптимальной. Titus - контейнерная платформа Netflix. Каждый месяц мы запускаем миллионы контейнеров на тысячах машин на Titus, обслуживая сотни внутренних приложений и клиентов. Эти приложения варьируются от критически важных сервисов с малой задержкой, обеспечивающих работу нашей службы потокового видео, ориентированной на клиентов, до пакетных заданий для кодирования или машинного обучения. Обеспечение изоляции производительности между этими различными приложениями имеет решающее значение для обеспечения хорошего опыта для внутренних и внешних клиентов.

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

Идея

CFS работает очень часто (каждые несколько микросекунд), применяя набор эвристик, которые инкапсулируют общую концепцию передовых методов использования аппаратного обеспечения ЦП.

Вместо этого, что, если мы уменьшим частоту вмешательств (до нескольких секунд), но будем принимать более основанные на данных решения относительно распределения процессов для вычислительных ресурсов, чтобы минимизировать шум коллокации?

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

Оптимизация размещений с помощью комбинаторной оптимизации

Планировщик задач ОС по сути решает проблему распределения ресурсов: у меня есть X потоков для запуска, но доступно только Y процессоров, как мне распределить потоки между процессорами, чтобы создать иллюзию параллелизма?

В качестве наглядного примера рассмотрим игрушечный экземпляр из 16 гиперпотоков. Он имеет 8 физических гиперпоточных ядер, разделенных на 2 сокета NUMA. Каждый гиперпоток делится своими кешами L1 и L2 со своим соседом и делит свой кеш L3 с 7 другими гиперпотоками в сокете:

Если мы хотим запустить контейнер A в 4 потоках и контейнер B в 2 потоках в этом экземпляре, мы можем посмотреть, как выглядят «плохие» и «хорошие» решения о размещении:

Первое размещение интуитивно плохо, потому что мы потенциально создаем шум коллокации между A и B на первых 2 ядрах через их кеш L1 / L2 и на сокете через кеш L3, оставляя весь сокет пустым. Второе размещение выглядит лучше, поскольку каждому ЦП предоставляются свои собственные кеши L1 / L2, и мы лучше используем два доступных кеша L3.

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

Мы формулируем задачу как смешанную целочисленную программу (MIP). Учитывая набор из K контейнеров, каждый из которых запрашивает определенное количество процессоров в экземпляре, имеющем d потоков, цель состоит в том, чтобы найти матрицу двоичного назначения M размера (d, K), чтобы каждый контейнер получил количество процессоров, которые он запросил. Функция потерь и ограничения содержат различные термины, выражающие априори хорошие решения о размещении, такие как:

  • избегать распределения контейнера по нескольким сокетам NUMA (чтобы избежать потенциально медленного доступа к памяти между сокетами или миграции страниц)
  • не используйте гиперпотоки, если в этом нет необходимости (чтобы уменьшить перегрузку L1 / L2)
  • попытаться выровнять нагрузку на кеши L3 (на основе возможных измерений использования оборудования контейнера)
  • не перекладывайте слишком много вещей между решениями о размещении

Учитывая требования системы к низкой задержке и малым вычислительным ресурсам (мы, конечно же, не хотим тратить слишком много циклов ЦП на выяснение того, как контейнеры должны использовать циклы ЦП!), Можем ли мы реализовать эту работу на практике?

Реализация

Мы решили реализовать эту стратегию через cgroups Linux, поскольку они полностью поддерживаются CFS, путем изменения cgroup cpuset каждого контейнера на основе желаемого отображения контейнеров в гиперпотоки. Таким образом, процесс пользовательского пространства определяет забор, внутри которого CFS работает для каждого контейнера. Фактически мы устраняем влияние эвристики CFS на изоляцию производительности, сохраняя при этом ее основные возможности планирования.

Этот процесс в пользовательском пространстве представляет собой подсистему Titus под названием titus-isolate, которая работает следующим образом. В каждом случае мы определяем три события, которые запускают оптимизацию размещения:

  • добавить: планировщик Titus выделил этому экземпляру новый контейнер, и его необходимо запустить.
  • remove: запущенный контейнер только что завершился.
  • ребалансировка: использование ЦП в контейнерах могло измениться, поэтому нам следует пересмотреть наши решения по размещению.

Мы периодически ставим в очередь события ребалансировки, если никакое другое событие в последнее время не инициировало принятие решения о размещении.

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

Затем эта служба запрашивает локальную модель GBRT (переобучаемую каждые пару часов на основе данных, собранных со всей платформы Titus за несколько недель), прогнозируя использование ЦП P95 каждым контейнером в ближайшие 10 минут (условная квантильная регрессия). Модель содержит как контекстные функции (метаданные, связанные с контейнером: кто его запустил, изображение, конфигурация памяти и сети, имя приложения ...), так и функции временных рядов, извлеченные из последнего часа исторического использования ЦП контейнера, регулярно собираемые хост из ядра Контроллер учета ЦП.

Прогнозы затем передаются в MIP, который решается на лету. Мы используем cvxpy как хороший общий символический интерфейс для представления проблемы, которая затем может быть передана в различные серверные части решателя MIP с открытым исходным кодом или проприетарные программы. Поскольку MIP являются NP-сложными, необходимо соблюдать некоторую осторожность. Мы накладываем жесткий бюджет времени на решающую программу, чтобы перевести стратегию ветвления и разрезания в режим с малой задержкой, с ограждениями вокруг разрыва MIP для контроля общего качества найденного решения.

Затем служба возвращает решение о размещении хосту, который выполняет его, изменяя cpusets контейнеров.

Например, в любой момент времени r4.16xlarge с 64 логическими процессорами может выглядеть следующим образом (цветовая шкала отображает загрузку процессора):

Полученные результаты

Первая версия системы дала удивительно хорошие результаты. Мы сократили общее время выполнения пакетных заданий в среднем на несколько процентов, но, что наиболее важно, уменьшили дисперсию времени выполнения заданий (разумный прокси для изоляции), как показано ниже. Здесь мы видим реальное распределение среды выполнения пакетных заданий с улучшенной изоляцией и без нее:

Обратите внимание, как мы в основном решили проблему давно выпадающих значений. Правого хвоста неудачливых шумных соседей больше нет.

В сфере услуг прибыль была еще более впечатляющей. Одна конкретная промежуточная служба Titus, обслуживающая потоковую службу Netflix, сократила емкость на 13% (сокращение более чем на 1000 контейнеров), необходимую при пиковом трафике для обслуживания той же нагрузки с требуемым SLA с задержкой P99! Мы также заметили резкое сокращение использования ЦП на машинах, поскольку ядро ​​затратило гораздо меньше времени на логику аннулирования кеша. Наши контейнеры теперь более предсказуемы, быстрее, а машина используется реже! Нечасто можно съесть пирог и съесть его.

Следующие шаги

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

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

Мы также хотим использовать события PMC ядра для более прямой оптимизации для минимального шума кеша. Одним из возможных путей является использование голых железок на базе Intel, недавно представленных Amazon, которые обеспечивают глубокий доступ к инструментам анализа производительности. Затем мы могли бы передать эту информацию непосредственно в механизм оптимизации, чтобы перейти к более контролируемому подходу к обучению. Это потребует надлежащей непрерывной рандомизации мест размещения для сбора непредвзятых противоречий, чтобы мы могли построить своего рода модель интерференции (какова была бы производительность контейнера A в следующую минуту, если бы я разместил один из его потоков на то же ядро, что и контейнер B, зная, что прямо сейчас на том же сокете работает C? ).

Заключение

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