Как мы развивали «Автосборку»: Опыт оптимизации высоконагруженных систем
Привет! Меня зовут Иннокентий Корнилов, я ведущий инженер-разработчик в Bercut, где работаю с 2013 года.
Развиваю и поддерживаю систему «Автосборка», которая берет начало в 2007 году. Это система управления конфигурациями, автоматизирующая процессы сборки, версионирования и выпуска релизов; ключевой инструмент, который обеспечивает единообразие разработки и выпуска программного обеспечения в нашей компании. Благодаря «Автосборке» мы можем управлять процессом сборки компонентов и систем, поддерживать версионность и статус готовности продуктов, а также обеспечивать их корректную доставку заказчикам.
Статья посвящена истории преодоления некоторых технических сложностей, возникших на различных этапах проекта, и описанию ключевых моментов, которые определили наш подход к работе над системой.
На проекте я выступал в разных ролях — от аналитика и архитектора до специалиста по разработке и тестированию, руководителя команды (также участвую в таких коммерческих проектах, как Business Rules Engine, Mobile Number Portability, Service Profile Management и других).
Ключевые возможности системы «Автосборка»
«Автосборка» обеспечивает принятый в Bercut единый процесс разработки и выпуска программного обеспечения, предоставляя следующие возможности:
Обеспечивает требуемые качество и эффективность разработки, внедрения и сопровождения наших решений.
Однозначно идентифицирует все артефакты, используемые или создаваемые в ходе проекта.
Обеспечивает выпуск пригодных для сопровождения без нашего участия решений.
Идентифицирует и контролирует состояние проектных артефактов в любой момент времени, в том числе их пригодность и степень готовности.
Управляет процедурой сборки компонентов и систем, обеспечивая ее воспроизводимость, эффективность и необходимый контроль.
Управляет изменениями, обеспечивая необходимую целостность и прослеживаемость для конфигураций проектных артефактов на всех стадиях разработки, поставки и сопровождения.
Если задать точный идентификатор версии системы/компонента, то всегда можно получить точную инструкцию по сборке этой версии, и пользуясь этой инструкцией:
Собрать идентичную копию с нуля из исходных кодов на «чистом» оборудовании и получить полный набор точной и актуальной эксплуатационной и технической документации для этой версии;
Воспроизвести любое изменение для любого из проектных артефактов от версии к версии, включая изменения всех артефактов, входящих в данный.
Обеспечивает единый workflow разработки и единое версионирование артефактов.
Гарантированная процессом версионность исключает возможность возникновения проблем, например, связанных с перезаписью меток в Git. «Автосборка» контролирует и поддерживает этот процесс. В Bercut установлена строгая политика обратной совместимости для разрабатываемых компонентов. Изменения версии в пределах первых двух цифр гарантирует пользователям, что компонент останется работоспособным, не потребует регресс тестирования, что это только bugfix.
Обеспечивает единообразие конфигураций артефактов. Не требуется создавать собственные скрипты сборки, так как у нас используются централизованные компиляторы и механизмы сборки. Отсутствует «зоопарк» различных подходов к сборке в разных командах. Единый процесс разработки и сборки исключает ситуации, когда ушел какой-то специалист и никто не знает, как собрать компонент. Сборка всегда происходит гарантированно.
Декларативно описывает конфигурацию компонентов и систем.
В Bercut имеется множество типов компонентов, которые собираются под различные операционные системы и с использованием различных компиляторов, от специфических аппаратных платформ до NodeJS. «Автосборка» обеспечивает единую декларативную конфигурацию, что значительно упрощает процесс ввода новых сотрудников и переход между проектами.
Собирает наши решения под различные платформы и компиляторы.
Платформы:
WIN32, WIN64, SOLARIS 9, SOLARIS 10, SOLARIS 10×64, LINUX POWER PC, LINUX SUSE, JAVA, MULTIPLATFORM, LINUX SUSE x32, RHEL 5.5, RHEL 5.5×32, RHEL 7, CENTOS 7, ALMA 9
Компиляторы:
DELPHI 6.0, DELPHI 10.0, DELPHI 10.3, DELPHI 11.0, DELPHI 11.1, MSVC 6.0, MSVC 8.0, MSVC 17.0, GCC 3.0, GCC 3.2, GCC 3.3, JDK 14, JDK 15, GCC 4.1, SUNC 5.8, LDC 2.0, TAB 1.4, TAB 1.6, TAB 2.0, TAB 2.1, JDK 16, JDK 17, JDK 18, JDK 19, JDK 130, JCDK 2.1, LIFERAY 5.1, NETBEANS 6.5, NETBEANS 8.2, OPEN ESB STUDIO 3.1, GCC 4.7, GCC 4.8.3, WSDL 3.1, WSDL 3.2, WSDL 3.3, EXTENSION*.
*EXTENSION — это точка расширения, которая позволяет собирать артефакты с использованием любых внешних сборщиков, компиляторов и инструментов командной строки, таких как Gradle, Maven, Angular CLI, npm, Sencha Cmd и других.
Обеспечивает переиспользование кода. В Bercut установлено четкое разделение между выпускаемыми артефактами: компоненты и системы. Мы собираем наши продукты как конструкторы, формируя системы из компонентов, при этом каждый артефакт может иметь свой жизненный цикл. Используем единый реестр и репозиторий артефактов. При разработке новых компонентов и систем активно применяем уже созданные ранее артефакты.
Обеспечивает одновременную работу с разными системами управления версиями.
Исторически «Автосборка» способна одновременно взаимодействовать с различными системами управления версиями (StarTeam, SVN, Bitbucket, GitLab и др.).Автоматизирует рутинные задачи: управление ветками в системе контроля версий (VCS) при выпуске новых версий артефактов, создание меток для релизов и многие другие операции.
Обеспечивает трекинг использования компонентов. Мы можем наглядно отслеживать, в каких артефактах используется каждый компонент, а также в какие системы он входит. Это особенно важно в случае выявления ошибки в компоненте, т.к. можно точно определить, в какие системы он интегрирован. Данная информация позволяет определить, какие системы требуют перевыпуска или обновления для заказчика.
Поддержка локальной сборки дает разработчикам возможность самостоятельно собрать компонент или систему на своих устройствах, гарантируя идентичный результат с централизованной сборкой через портал «Автосборки».
Система может использоваться для проведения escrow-процедур, позволяя на основе отгруженных релизов создать репозиторий с исходными кодами, из которых на площадке заказчика можно создать идентичные релизы.
Позволяет внести изменения в артефакт, собранный несколько лет назад, пересобрать и получить точную копию с внесёнными исправлениями.
Даже если участники команды ушли, любой другой разработчик может без проблем подключиться, внести необходимые изменения, собрать артефакт и гарантированно получить тот же продукт, что и раньше, только с учетом внесенных изменений.
Собирает метрики и анализирует качество кода.
Обеспечивает жизненный цикл develop, pre-release и release.
На этапе разработки артефакт переводится в статус pre-release, который представляет собой замороженный срез, не подлежащий пересборке. Артефакт в статусе pre-release передаётся на тестирование, и, если тесты проходят успешно, продукт выпускается в релиз.
Обеспечивает механизмы выпуска патчей.
Позволяет выпустить минорный патч на уже выпущенный релиз системы.
Описание системы
Что из себя представляет «Автосборка» с технической точки зрения? Она состоит из основного сервера и агентов сборки. Каждая операционная система обслуживается отдельным build-агентом.
Изначально на основном сервере, помимо ядра системы и портала, находилась база данных PostgreSQL и агент сборки.
В «Автосборке» есть несколько типов артефактов, например, компоненты и системы. Важно отметить, что наши продукты и решения имеют компонентную структуру. Мы не пишем каждый новый продукт с нуля, а разрабатываем отдельные компоненты, отвечающие за определённую функциональность. Можем повторно использовать разработанные ранее компоненты: новые компоненты используют уже существующие.
Система — конечный продукт, которая состоит из компонентов и может включать другие системы. Релизная сборка системы отгружается заказчику.
«Автосборка» — ещё и единый процесс разработки и выпуска ПО на уровне компании. Любой артефакт в системе управления конфигурациями имеет свою уникальную версию. К артефактам относятся версии систем и компонентов, сборки, оперативные исправления и т.д. Каждая сборка имеет свой статус готовности. В «Автосборке» исключается ситуация, при которой к заказчику может попасть артефакт с одной и той же версией, но с разными вариантами исходного кода.
Компоненты и системы конфигурируются по единому принципу, независимо от того, под какие операционные системы, какими компиляторами собирается артефакт, на каком языке он написан. Разработчик просто указывает, как этот артефакт должен собираться, а «Автосборка» выполняет все остальное.
Для создания нового компонента в «Автосборке» разработчик заходит на портал, создаёт компонент, указывает версию и название компонента, указывает компиляторы и операционные системы, зависимые компоненты, которые необходимы для сборки нового артефакта. При создании компонента «Автосборка» автоматически формирует путь в системе управления версиями, в котором будет храниться исходный код. По такому же принципу создаются и системы.
Когда разработчик ставит свой компонент на сборку (через GUI, IDE или по коммиту), то задание попадает в общую очередь «Автосборки». Помимо собираемого компонента в очередь также попадают зависимые компоненты, использующие этот компонент, если они не находятся в статусе предварительного релиза или релиза.
Очередь отображает задания на сборку, поставленные всеми разработчиками, информирует о прогнозируемом времени окончания сборок.
После того как компонент собран, его сборка, которая имеет свою уникальную версию, выкладывается во внутренний репозиторий «Автосборки». После чего компонент может быть использован для сборки других компонентов или систем.
Оптимизация базы данных: индексы и разнесение на отдельные машины
С увеличением числа артефактов, с ростом количество заданий на сборку процесс стал замедляться.
Для увеличения производительности и уменьшения нагрузки на центральной сервер мы решили:
• вынести агент сборки на отдельную машину с центрального сервера;
• вынести базу данных на отдельную машину;
• виртуализировать часть физических машин и добавить им ресурсов.
Также мы проанализировали существующие индексы, выявив те, которые не участвовали в запросах или использовались слишком редко. Эти индексы были удалены, чтобы уменьшить нагрузку на операции обновления таблиц, т.к. каждый индекс требует переиндексации при добавлении, обновлении или удалении строк, что приводит к замедлению этих операций.
Затем, базируясь на статистическом анализе наиболее частых и «тяжёлых» запросов, мы создали новые индексы. Ключевым моментом стала работа с B-tree индексами, которые обеспечивают эффективный поиск диапазона значений и значительно сокращают время поиска за счет уменьшения количества страниц, которые необходимо прочесть. Целью было уменьшить количество последовательных сканирований таблиц (sequential scans), заменив их сканированием индексов (index scans). Параллельно с работой над индексами мы провели ревизию и оптимизацию SQL-запросов; часть запросов переписали. Особенно в той части логики, где шло множественное обращение к базе данных. Мелкие множественные запросы заменили запросами, которые за один раз возвращают все нужные данные.
Все эти меры позволили увеличить скорость сборки систем и компонентов в 2 раза.
Масштабирование с использованием пула агентов и параллельные сборки
На следующем этапе «Автосборка» продолжила наращивать функционал:
Наряду с SVN, добавилась интеграция с Bitbucket;
Появилась интеграция с Jira;
Добавилось формирование истории сборок, которое независимо от используемой VCS показывало изменения кода в рамках каждой сборки, каждого релиза.
Увеличился список поддерживаемых операционных систем. На тот момент «Автосборка» умела собирать артефакты под различные версии и различную битность таких операционных систем, как Solaris SPARC, RHEL, Windows, LinuxPWPC, LinuxSUSE, CentOS и т.п. Всего было более 12 вариантов сочетаний ОС и битности. Также «Автосборка» поддерживала более 30 версий различных компиляторов.
Существенно возросло количество выпускаемых продуктов, и как следствие, нагрузка на систему.
Сборка каждого компонента представляет собой комплексную задачу, которая подразумевает не один, а несколько процессов сборки. Например, для некоторых типов компонентов, для каждой операционной системы и компилятора, указанных в конфигурации, производятся два типа сборок — одна предназначена для отладки, а вторая является основной. Если артефакт предполагается использовать в пяти разных операционных системах и для каждой требуется две версии под два компилятора, в итоге с учетом наличия версии для отладки получаем 20 бинарных файлов, созданных в рамках одной сборки.
При этом сборка компонента тянет за собой и сборку зависящих от него компонентов, если требуется их пересобрать, находящихся в статусе Develop. Изначально функционал пересборки зависящих компонентов был введен для С++ приложений. Постановка одного компонента может привести к проведению сотни сборок в целях поддержания консистентности и актуальности всех связанных артефактов.
В ходе анализа было выявлено две значимые проблемы, решение которых позволило в разы ускорить весь процесс. Первое узкое место заключалось в последовательной сборке артефакта под различные операционные системы, когда сборка для следующей ОС начиналась только после завершения сборки для предыдущей. В качестве решения была реализована функциональность, распараллеливающая этот процесс, что позволило ускорить сборку одного артефакта, собирающегося под разные ОС, более чем в 4 раза.
Второй вопрос был связан с одновременной сборкой различных артефактов под одну операционную систему.
Как уже говорил выше, для сборки под одну ОС в системе был выделен один агент сборки. По мере роста нагрузки одного агента стало не хватать. Распараллеливание сборки на одном хосте тоже не являлось полноценным решением, так как при одновременной сборке 3 — 4 компонентов время сборки одного компонента возрастало кратно. В качестве решения была добавлена поддержка пула агентов для каждой ОС. Это улучшение позволило одновременно собирать несколько артефактов под одной ОС на разных агентах.
Также был добавлен механизм балансировки нагрузки, который предоставлял наименее загруженный агент из пула. Сборка под одну операционную систему стала выполняться на разных хостах, параллельно, в 10 потоков. Это ускорило обработку очереди сборок более чем в 10 раз.
Новые вызовы, интеграция с GitLab и оптимизация процессов
Возникли новые задачи.
Потребовалось внедрить новые требования со стороны производства. Была добавлена интеграция с GitLab, и основная разработка переехала на использование этой системы управления версиями. Параллельно часть систем и компонентов оставались в SVN и Bitbucket. Были проведены масштабные миграции разрабатываемых проектов из Bitbucket и SVN в GitLab. Добавлена функциональность непрерывной интеграции и доставки CI/CD, благодаря которой любой push в GitLab мог инициировать сборку соответствующих компонентов, их зависимостей и систем, в которые эти компоненты входят. В «Автосборке» появился функционал автоматической отгрузки релизов заказчикам. Для взаимодействия с внешними системами был добавлен брокер сообщений RabbitMQ, был организован автоматический анализ качества исходных кодов собираемых компонентов. Также была добавлена пофичная разработка и интеграция с Intellij IDEA, которая позволяет управлять состоянием и сборкой фич и артефактов прямо из IDE.
Увеличилось количество связей между артефактами. Выросло количество артефактов в системе. Проанализировав логику работы, мы нашли новую точку для оптимизации. Когда собирается артефакт под различные платформы, для сборки формируется дерево зависимости этого артефакта. А так как зависимостей может быть довольно много, то создание такого дерева может быть весьма затратной операцией.
Мы добавили in-memory cache дерева зависимостей. То есть в процессе сборки дерево зависимостей формировалось один раз, а дальше при сборке под различные операционные системы оно переиспользовалось. Наличие такого дерева позволило ускорить сборку в десятки раз.
Ранее агенты сборки использовали компиляторы, находящиеся на главном сервере. Для улучшения производительности компиляторы были размещены непосредственно на каждом агенте сборки. Кроме того, для обеспечения актуальности состояния компиляторов была разработана специальная программа, осуществляющая их синхронизацию. Также на агентах созданы RAM-диски, которые представляют собой участки оперативной памяти, используемые как сверхбыстрая альтернатива жёсткому диску. На них перенесены процессы, требующие частого и интенсивного доступа к диску, что дополнительно ускорило процесс сборки.
Заключение
На данный момент «Автосборка» — высоконагруженная система, обеспечивающая одновременную работу сотни разработчиков, которые ежедневно собирают свои артефакты. Например, за прошлый месяц на сборку было поставлено > 10 000 заданий. В репозитории «Автосборки» сейчас хранится > 1 000 000 артефактов, это число продолжает расти.
За последние годы она прошла через множество изменений. Впереди — новые задачи. В ближайших планах несколько ключевых фокусов внимания:
Оптимизация запросов к базе данных и улучшение индексов. Продолжим повышать производительность базы данных, чтобы ускорить обработку данных и улучшить общую скорость работы системы.
Внедрение шардирования базы данных. Это поможет более эффективно распределить нагрузку, избегать узких мест при росте объёмов данных и повысит отказоустойчивость системы.
Использование Redis для кэширования. Это позволит уменьшить время доступа к ключевой информации и снизить нагрузку на БД, что ускорит процесс сборки и обработки артефактов.
Динамический пул агентов сборки с контейнеризацией с использованием Docker и Kubernetes. Это позволит динамически увеличивать число агентов в зависимости от текущей нагрузки на систему, обеспечивая гибкость и масштабируемость пула для обработки пиковых задач.
Переход к событийно-управляемой архитектуре с RabbitMQ. Это позволит системе реагировать на события в реальном времени, улучшит параллельную обработку задач и повысит гибкость взаимодействия между компонентами, что обеспечит лучшую масштабируемость и устойчивость к высоким нагрузкам.
Интеграция с отечественными сервисами. Планируем расширить интеграцию с отечественными сервисами, включив системы управления проектам, репозитории, планировщики задач и т.д.
С каждым новым этапом «Автосборка» продолжит развиваться и обеспечивать управление конфигурациями и сборками в нашей компании.