Всем привет! Не так давно завершился HighLoad Cup. От многих участников поступала масса вопросов об устройстве чемпа изнутри. Мы, команда разработки чемпионатов и образовательных проектов Mail.Ru Group, в данной статье расскажем об устройстве чемпа, о внутренних механиках и немного об истории проведения первого HighLoad Cup!
В первую очередь от лица разработчиков благодарим всех участников чемпионата. Да, это были три недели жёгова не только для вас. Несколько ночей прошли в ДЦ, несколько выходных — в офисе. Огонь! Спасибо вам!
Идея чемпионата возникла из жизни, из быта. Большая часть (наших) web-продуктов — общедоступные «публичные» проекты. Их жизнь в интернетах несколько отличается от их жизни на компьютере разработчика. То поисковик притопает, то посетители толпой.
При разработке серверной части приложения приходится прорабатывать быстродействие приложения. Бывает, что-то упускается из виду, и проект оказывается на пределе серверных мощностей (или за пределами). Закидывать дорогостоящим оборудованием плохо написанное приложение — как лечить здорового, не лучшее решение. Верное решение — грамотно писать приложение, правильно реализовать архитектуру и бэкенд.
Мы проводим Russian Code Cup, MLBootCamp, Russian Ai Cup, Russian Design Cup и ряд других чемпионатов. Всё в них хорошо, только вот той реальной жизни реального бэкенд-разработчика мало. В HighLoad Cup мы постарались привнести в массы именно эту реальность разработки.
Как это всё работает?
Основной вопрос при разработке чемпионата был такой: где и как крутить решение? Мы рассматривали несколько вариантов:
- Участник сам находит себе железо.
- Предоставляем участнику виртуалки с полным доступом.
- Взводим git и просим писать скрипт, который запустит решение.
- Docker.
- Vagrant.
- Ejudge.
- Участник предоставляет образ KVM или VirtualBox.
- Производные от версий 1—7 и их комбинации.
При рассмотрении реализации ключевыми были два момента:
- Одинаковые условия для всех участников.
- Обеспечение безопасности.
Нельзя, чтобы результат зависел от железа. В нашем случае, выходит, требовалось крутить все решения на абсолютно одинаковых машинах. Лучше на одной, но её хватит на 48 решений (участников) в сутки. Поэтому нужен парк одинаковых машин.
Да, выбрали Docker. Как компромисс между работоспособностью, простотой и удобством, одновременно обеспечивающий безопасность. Варианты без докера получили меньше голосов на консилиуме разработчиков. Либо не соответствовали условиям, либо требовали больших усилий для реализации, либо были избыточно сложны. Хотя и сам докер не очень прозрачен: три варианта конфига docker compose уже чего стоят.
Архитектура сложилась следующая:
- Site. Веб-приложение. Отдаёт вам (посетителям) странички.
- Storage. На базе docker-registry + специальный proxy-сервер.
- Runner. Специальные стиральные машинки, включают в себя контейнеры с Tank’ом.
- Hub. Приложение, реализующее всю механику. Сайт смотрит в хаб. Хаб смотрит в сторадж и раннеры. Запускает что надо где надо и отдаёт результаты сайту.
Меньше всего вопросов с сайтом. Регистрация, авторизация, задача, раунд. Всё стандартно и типично.
Сторадж. Docker-registry из коробки хорошо работает, но не реализует разграничений доступов и лимитов по объёму хранений. Запускать его безлимитным здорово, но мы не можем себе этого позволить. Решения одного участника не должны быть доступны другим. Мы разработали прокси к стораджу для контроля доступов и лимита на объёмы хранения.
Хаб. Собственно, данная компонента рулит всем, что происходит. Начиная от формирования очереди на «обстрел» и заканчивая процессингом результатов. Она знает обо всех раннерах, обращается к ним для запуска проверки и за результатами. Отдаёт их сайту по его запросу.
Раннеры. Здесь происходит запуск всего вместе, сбор и обработка результатов, их временное хранение. Отдельно стоящие машины, одинаковые вплоть до вендора шасси, отрезанные от всего мира и работающие в одну сторону. Да, запросы могут быть только к ним и только от хаба. Б — безопасность. Одиночные компоненты запущены в двух копиях с репликацией БД и аплоада. О — отказоустойчивость. Раннеров в первоначальной конфигурации — два.
Теперь подробнее.
Раннеры cо стороны разработки представляют собой набор серверов, взаимодействующих с «хабом» по JSON и с docker-registry. По командам хаба набор серверов формирует docker-compose и запускает требуемые пары танк-решение, процессит и складирует результат, предоставляет его по требованию. Наша задача — обеспечить одинаковые для всех условия. Одинаковые и достаточно хорошие для хайлоада. Для этого мы:
- отключаем Intel TurboBoost;
- фиксируем частоты процессоров чуть ниже номинальных значений;
- прибиваем процессы гвоздями (vcpupin) к физическим ядрам, отделяя танк от решения;
- выделяем отдельный раздел на диске для разворачивания контейнеров;
- тюним sysctl, ulimit;
- для пущего фэншуя фиксируем процессы к numanode’ам.
Данные меры в совокупности обеспечивают большую стабильность, большую производительность и меньшие джитеры в результатах обстрела.
Разворачивая и тестируя данный комплекс, мы решили завернуть всё это в KVM-контейнер. Это даёт следующие профиты:
А. Подстраховаться на случай kernel panic (ребут виртуалки, но не хоста), попыток перепрошить bios, оборудование, попыток изменить что-либо на хосте и т. п.
Б. Лимитировать диск по read и write kbps. Не бывает двух одинаковых дисков. Они всегда работают по-разному. Для обеспечения одинаковости можно воспользоваться лимитами iotune виртуальных дисков.
В. Быстро перевзвести, перенести или склонировать контейнер.
Г. Дополнительно сверху лимитировать сеть на хостовой машине.
Польза от такого решения — инфраструктурная. Никаких дополнительных плюшек участникам не даёт, а для эксплуатации упрощает ряд вопросов. Оверхед на kvm-виртуализацию для нашего случая — не потеря: главное, чтобы условия были одинаковы.
В docker-контейнерах также сделали ремап в непривилегированного пользователя. Вы ведь не запускаете веб-сервер с правами рута, правда?
Время на разработку медленно, но верно заканчивалось. Мы совсем не знали, какой будет интерес к чемпионату и нужно ли на него тратить больше сил. Задач, помимо данного чемпионата, вагон и маленькая тележка. Решили — запускаем. Запустили первый, пробный. Постараемся негромко. MVP готов. Работает. Попробуем, а там посмотрим. Может, всё это зря и неинтересно?
Поехали.
Готовим простую задачу, немного данных. Генератор данных. Анонсируем, делаем пару постов. Смотрим на пустой рейтинг. Переживаем. Мы любим всё мониторить. От напряжений в розетках до smartctl параметров. Такой был день анонса.
Поддавшись сомнениям, что «что-то сломалось», пушим своё решение, видим его в топе и боимся, что оно и победит. Снова переживаем. Появляются первые участники. Радуемся! Но наше решение всё ещё в топе. Всё ещё переживаем.
Переживать по поводу «не взлетит» нам пришлось недолго. Поскольку технологии относительно нестандартные для IT-чемпионата, участники начали активнейшим образом пробовать тестовые прогоны. Рейтинговые ограничены одним в сутки и занимают полчаса, а тестовые не ограничены никак. Как-то несправедливо их ограничивать.
Образовалась очередь из тестовых прогонов. Двух раннеров стало не хватать. Уже утром следующего дня мы искали оборудование для разворачивания большего количества раннеров, уже вечером следующего дня мы сидели в ДЦ и сетапили две новые машины, стараясь не упустить ничего из тонких настроек каждой. Четыре машины в строю. Пока мы сетапили вышеозвученные две, стало понятно, что их нужно уже точно не меньше восьми.
Место на сторадже заканчивается. 200 Гб должно было хватить на 40 планируемых участников в расчётных условиях. Участников в первые пять дней пришло в десять раз больше. Но всё хуже, практически перед запуском изменились условия: увеличено количество запросов к решению и объём данных. Понимаем, что нужна пара терабайт, заказываем. Тем временем место под хранение результатов потекло. Докидываем чутка из запасов, но этого хватает ненадолго.
Скромная виртуалка с сайтом начинает дымиться, докидываем нон-стоп на процев. Тем временем получаем диски под сторажд. На горячую их поменять совсем никак. Не предусмотрено. Нет слотов. Готовим новую машину, просим полчаса даунтайма и переливаем.
Участники просят больше данных. Как бы да. Там всего-то 20 Мб. Это ж ни о чём. Нужен 1 Гб. Звучит просто. На деле это стократное увеличение как требуемых для обработки ресурсов, так и места для хранения результатов.
Понимаем, что восьми раннеров уже мало. Ищем ещё. Снимаем с другого проекта четыре машины в точно такой же конфигурации и взводим их. Двенадцать раннеров. Всё. Больше таких же машин нет.
Несколько раз оказываемся на гране фола. Несколько раз спасаемся на ходу. А участников всё больше и больше.
Пики
Похоже, это самое частое слово в чате чемпионата. Можно было бы и не придавать им значения. Это вполне может быть из-за кривой реализации приложения. Тот самый GC, например. Тем временем RPS’ы достигли значений 10K, и на таких оборотах любой чих в системе способен привести к микровсплескам в работе приложений. Пики были почти у всех.
На этом этапе мы вспомнили (на самом деле нам подсказали участники) об архитектурном упущении: танк пишет на диск. Пишет много. Дисковые операции, производимые самим танком, с большой вероятностью могут вызывать те самые чихи в системе. Какие были варианты это решить? Вынести в RAM-диск:
а) весь диск раннера;
б) раздел, куда пишет танк.
Первым делом на тестовой машине мы вынесли в RAM весь диск раннера. Это гарантировало отсутствие дисковых операций, но большая часть решений в такой конфигурации падала с OOM. Нам так и не удалось выяснить причины. Памяти было с запасом. Вероятно, решения каким-то образом ссылались на общую доступную память на машине, которой становилось в четыре раза больше, когда лимит на решение не менялся. Это могло приводить к ошибочному потреблению решением памяти и её последующему OOM Kill.
Параллельно провели эксперимент с выносом раздела с танком в RAM-диск. Он справился, топ-50 неизменно работал. Это и стало решением. Такой ход не решил все проблемы с пиками. Их стало меньше, они стали не столь значительными, но их наличие по-прежнему изучалось.
Тем временем сроки истекают. В топе C/C++ велосипеды, мозолит глаз PHP в топ-25, финал через день, и мы не спим уже три недели.
Финал
Первоначально финал планировался как обычный раунд, поочерёдный запуск каждого решения и выявление лучшего. Это не гарантировало справедливости: одно и то же решение в разных запусках показывало разбег с приличным результатом. Чтобы честно и справедливо выявить лучшее решение был принят предложенный самими участниками способ провести финал: прогнать все решения по много раз (волнами). За результат берём лучшее (минимальное) время. Таким образом мы уравниваем шансы каждого. День на написание, ночь на тестирование. Запускаем. Это был самый длинный финал среди наших чемпионатов. Решения финалистов тестировались полтора дня.
Подводя итоги, отметим:
- Ряд ошибок в планировании вычислительных (железных) ресурсов сделал нашу жизнь веселее. Мы стали чуть более опытными.
- Пики неизбежны на больших RPS. Нужно, конечно, их нивелировать.
- Исходно придуманная задача оказалась слишком проста. Думаем.
А вот, собственно, и всё. Глазами команды разработки это были три недели лютого хардкора. От лица разработчиков хочу поблагодарить участников! Без вас мы не получили бы столько драйва. Мы участвовали в чемпионате по ту сторону экрана вместе с вами! Спасибо вам! Для вас старались Максим xammi-1 Кисленко, Борис bkolganov Колганов, Егор geoolekom Комаров, Илья liofz Лебедев, Илья sat2707 Стыценко.