Как эффективно релизить монолит, в который коммитят 150+ разработчиков из разных офисов

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

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

Серверные релизы

Наш backend — это монолитное Java-приложение, которое может быть запущено с разными ролями для выполнения разных задач. Для работы backend мы используем AWS инстансы (CPU 4 ядра, RAM 16 ГБ). Большая часть backend-серверов – приложение, которое держит постоянное веб-сокетное подключение с клиентом, чтобы пользователи всегда видели реальное состояние досок в Miro. Для этих серверов мы используем роль Board-сервер (пользователи попадают на них при работе на досках). Для работы с бизнес-логикой и API-запросами используем роль API-сервер.

Релизы мы делаем бесшовными (graceful deploy) и стараемся проводить их во время наименьшей нагрузки на сервис. Во время планового релиза у нас в среднем 60.000 онлайн-пользователей и 50 работающих board-серверов.

Мы считаем релиз успешным, если он вышел в срок и в него попали все задачи, которые были готовы к релизу на момент его запуска. Соответственно, релиз считается неуспешным, если что-то пошло не так, потому что ошибки, которые потребовали остановки или отката релиза, увеличивают время доставки (time to market).

Любые изменения в процессе проведения релизов мы оцениваем исходя из того, насколько они приближают нас к успешному релизу.

Успешный релиз — это релиз, который вышел в срок и в который попали все задачи, готовые к релизу на момент его запуска.

Процесс подготовки релиза:

  1. На каждый пулл-реквест прогоняется релевантный набор e2e тестов. Добавить изменения в мастер можно только при успешном прохождении всех тестов. Внутри автотестов есть маппинг соответствия тестов и кода продукта. Набор e2e-тестов для пулл-реквеста определяется нашим инструментом, который выбирает тесты, основываясь на этом маппинге и анализируя изменённые файлы в пулл-реквесте.

  2. Каждый собранный мастер проходит полную регрессионную проверку. Релиз возможен, если все тесты прошли успешно. Упавшие тесты правят команды, ответственные за функциональность.

  3. Для того чтобы релиз вышел автоматически, мы используем Allure Enterprise Edition, в котором отмечаем false-positive тесты как Resolved.

Процесс релиза:

  1. Ищем сборку со 100% успешных тестов и версией, которая больше текущей версии на продакшене.

  2. Запускаем канареечный релиз.

  3. Мониторим метрики релиза в течение 4х часов.

  4. Ставим статус Approved или Broken по завершении канареечного релиза. При статусе Approve основной релиз автоматически запускается следующим утром, при Broken запуска не произойдет.

  5. Для релиза на API- и board-серверах создаём инстансы с новой версией. Количество инстансов рассчитываем, исходя из текущей нагрузки и добавляем 20%, чтобы не допустить высокой нагрузки во время или сразу после релиза.

  6. Пользователи постепенно переходят на новые сервера, старые сервера мы выключаем и удаляем.

Релиз от создания инстансов до полного перехода на новую версию занимает полтора часа.

Канареечный релиз

Канареечный релиз нужен для того, чтобы валидировать изменения на небольшой случайной выборке пользователей. В ходе него мы поднимаем несколько серверов с новой версией и наблюдаем за ситуацией. Если на канареечном релизе всё идёт хорошо — релизимся на все сервера.

Процесс канареечного релиза
Процесс канареечного релиза

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

Для быстрой реакции на ошибки в канареечном релизе мы ввели роль дежурного серверного разработчика, которую выполняет каждый разработчик по очереди. Дежурный разработчик в течение четырех часов работы канареечного релиза реагирует на новые ошибки в Sentry и на общие предупреждения из Grafana, может остановить релиз самостоятельно при необходимости. После завершения канареечного релиза он обновляет статус релизной сущности в Bamboo: Approved или Broken.

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

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

Выкатить канареечный релиз на определённую выборку пользователей мы можем, только если функционал был написан с Feature Toggle, а это уже реализация через код, а не через релизы.

Hot Fix в канареечном релизе

Раньше, находя ошибку в канареечном релизе, мы блокировали мердж в мастер и весь релиз. Это было неудобно, так как блокировало работу других команд и задерживало время выхода релиза.

Нам хотелось найти подход, при котором мы могли задерживать выход релиза минимально. Мы изучили существующие подходы (Trunk-Based Development, GitFlow и т.д.) и остановились на GitLab Flow.

Как мы работаем с Hot Fix по GitLab Flow:

  1. Отводим релизные ветки от версии из канареечного релиза.

  2. Мерджим фикс в мастер.

  3. Выполняем git cherry-pick в релизной ветке.

  4. Запускаем канареечный релиз на релизной ветке.

  5. Запускаем следующий плановый канареечный релиз на версии мастера с фиксом или выше.

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

Предсказуемость и прозрачность процесса релиза

Для повышения качества релиза мы поддерживаем его прозрачность и предсказуемость. Используем для этого автоматические уведомления и дашборды с ключевыми метриками.

Раньше мы публиковали большой changelog один на все команды: в общем канале со всеми изменениями в релизе. Командам было трудно и больно ориентироваться в нём. Поэтому к общему changelog мы добавили командные changelog, в котором каждая команда видит статусы только по своим задачам и версию релиза, в которой они реализованы.

Дашбордами в Grafana мы пользуемся при работе с внеплановым релизами, чтобы быстрее их завалидировать. Во время плановых релизов нам хватает алертов из Grafana на основе метрик из Prometheus.

Всю статистику по релизам из Jira и Bamboo мы собираем и визуализируем в Looker, чтобы на основе исторических данных принимать решения о качестве процессов и улучшать их.

Данные по ошибкам, количество созданных и закрытых задач.
Данные по ошибкам, количество созданных и закрытых задач.

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

Изменения, которые увеличили долю успешных релизов

  1. Канареечные релизы помогли сократить количество откатов релизов на 95%.

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

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

  4. Подход GitLab Flow для hotfix позволил задерживать выход релиза минимально и исправлять ошибку, не блокируя работу других команд. Автоматические релизы стимулируют команды держать мастер всегда готовым к релизу.

  5. Сбор и анализ всей истории релизов в Looker помогает проверять гипотезы и постоянно улучшать процесс.

Ближайшие планы

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

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

  2. Увеличить скорость релиза. Сейчас релиз на board-серверах занимает час, релиз на API-серверах — полчаса. Мы хотим быстрее.

  3. Дать командам инструмент для автономного управления релизами. Сейчас есть возможность запустить канареечный релиз для hotfix, но команды не могут воспользоваться GitLab Flow полностью самостоятельно. Например, не могут самостоятельно отвести релизную ветку. У нас по умолчанию включена функция "Branch merging enabled", поэтому ветки при сборке содержит код мастера, а для релизных веток командам нужна помощь со стороны для ручного отключения этой фичи.

  4. Сократить время от момента нахождения ошибки до вывода фикса на канареечный релиз. Сейчас у нас это может занимать до 6 часов рабочего времени в худших случаях из-за сложностей в коммуникациях или процессах.

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

  6. Добавить пользовательские метрики в валидацию релиза. Пока используем только технические метрики и метрики с багами.

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

Miro
Online collaborative whiteboard platform

Комментарии 12

    +2
    Это замечательно, но вопрос. Что делаете если есть изменения в БД и, например, невозможно сделать новую схему базы данных совместимой со старым кодом?
      +3
      я не автор, но если ты хочешь иметь возможность откатить новую версию, то ты не ломаешь совместимость никаких api(а схема в бд, это в своем роде api), хочешь удалить колонку? удаляешь через версию, и т.п. да это долго и больно, но по другому нельзя.
        +2

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

          0
          Видимо имелся в виду подход проэктирования БД когда никто с таблицами напрямую не работает, только через хранимки. Часто сам такое использовал и всем рекомендую для больших долгоживущих проэктов. Делать такие изменеия в БД проще: легче отслеживать зависимости и делать онлайн апдейты так как следишь только над совместимостью хранимок.
            +1
            Помогу коллеге с ответами.

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

            Это конечно, не очень надежно и по-хорошему необходимо проводить автоматическое тестирование на обратную совместимость, чтобы четко ограничивать набор версий, которые могут быть выведены на прод совместно. В данный момент этого у нас пока нет.
        0

        Да, тоже интересно — а что с накатом и откатом изменений в БД

          +1
          Тут можно почитать, как мы работаем с БД. В том числе про миграции схемы данных
          habr.com/ru/company/miro/blog/512990
          +2

          Так как такие огромные монолиты (сужу по моему представлению о том, сколько могут 150+ человек вносить ценности в продукт) обычно крайне медленно и тяжело развиваются, есть ряд вопросов:


          1. Как делаются изменения в БД?
          2. Что происходит, если появляется баг на фоне исправленного старого? Мастер уходит далеко вперед и опять нестабильность?
          3. Как в таком варианте вы умудряетесь поддерживать фронт в актуальном состоянии?
          4. Как вы избегаете поломки в рефакторинге/оптимизации, если столько разных команд и монолит?
          5. Как именно организовано ручное и аатоматическое тестирование (есть ли ручное тестирование API — отдельный подвопрос; автоматическое — это вэб-драйвер, юнит-темты, функциональные, другое)?
          6. Как организовано обновление библиотек, если они задействованы в коде разных команд?
          7. Как организована постановка и синхронизация задач между командами?
          8. Как дежурный сотрудник не вешается от багов, которые летят из кода от полутора сотни людей?
          9. Как организован репортинг о багах со стороны пользователя?
          10. Как у вас принимаются архитектурные решения на уровне монолита?
          11. Как вы делаете нагрузочные?
          12. Сколько процентов от времени у вас занимает багфиксинг? Между какими задачами распределено обычно время и в каких процентах?
          13. Есть ли у вас какой-то дизайн/макет/требования к внешнему виду приложения и как вы этого придерживаетесь?
          14. Как именно описывается API на уровне всего монолита (коллекции, Swagger, другое)?
          15. Есть ли у вас техподдержка и как у нее организована работа (приемка нового функционала — отдельный подвопрос)?
          16. Как вы поддерживаете документацию и какие критерии багов (соответствие докам, мнению, ору пользователей, другое)?
          17. Сколько лет проекту/приложению?
          18. Как вы решаете проблемы производительности для БД при монолите?
          19. Проходите ли вы тесты (аудит) безопасности?
          20. Какой порог вхождения и как организовано?
          21. Через что организован кэнэри? Как вы откатываете действия пользователя в случае фейла? Есть ли у вас зависимость от внешних систем в таком случае?
            +1
            1. Изменения в БД проиходят посредством миграций. Мы имеем большое количество шардов, которые логически объединены в одну единую базу. Миграции накатываются автоматически при старте первого сервера приложения на новой версии. Тут надо добавить, что у нас нет бизнес логики на стороне базы (процедур, тригеров и т.д), только структура таблиц с индексами, ключами и констрейнтами
            2. Мы можем релизиться дальше, потихоньку догоняя последний мастер. К счастью. не бывает большого количества блокирующих проблем, и мы даже в такой схеме с таким большим количеством разработчиков еще не лочим наши релизы напрочь
            3. Чаще всего у нас разные версии сервера и клиента совместимы, мы специально стараемся так делать. Когда мы понимаем, что с новым сервером не будут работать старые клиенты, у нас есть механизм, который задает минимальную поддерживаемую версию клиента на сервере. Команды мониторят версию сервера на проде, и релизять свою фронтовую часть только когда серверная стабильна на проде
            4. Главным образом, мы стараемся разделять предметные области команд. Это монолит, но команды стараются не перескаться в коде в один момент времени. Это позволяет нам не иметь больших проблем с конфликтами (что обычно часто бывает), и помогает нам реже ломать функционал друг друга. Хотя эта проблема есть, и мы прямо сейчас работаем над ней. Одна из идей, шарить технический дизайн документ, результат экзампл маппинга и другие артефакты до разработки. На ревью проблемы бывает найти сложнее, чем при ревью логики работы (с бизнесовой и технической стороны)
            5. Я думаю частично ответ на вопрос здесь. У нас нет ручного регресса, он полностью автоматизирован, там есть и API тестирование. И нет ручного тестирования выделенным QA инженером, реализованных задач. Разработчик сам проверяет за собой по заранее продуманным тест-кейсам (созданным QA инженером чаще всего), и в процессе разработки пишутся тесты, мы стараемся тесты смещать на более низкие уровни на сколько получается (покрывать логику работы юнит тестами, взаимодействия между сервисами и частями сервисов интеграционными тестами и т.д.)
            6. Хороший вопрос. Аналогично крупному рефакторингу. Всегда (я думаю всегда), такой рефакторинг делает одна команда, остальные учавствуют в тестировании и ревью
            7. Еще один хороший вопрос). Пока нам удается разделять команды по реализуемой логике, даже в рамках одного направления. Мы пока не используем фреймворки вида Less и т.п. Наибольшее общение по планируемым изменениям сейчас происходит на уровне QA разных команд и направлений, и на продуктовом уровне. Также за взаимодействия с другими командами отвечает тим лид команды, если он видит пересечения, он договаривается как будут производиться изменения. Разработчики проактивно также могут общаться между собой, но это не процесс, скорее от случая к случаю
            8. А там нет большого количества. Это единичные случаи. Сентри показывает только новые, и даже грязная консоль не проблема для такого анализа
            9. Support команда создает тикеты на продуктовые команды. Команды исследуют эти тикеты, и либо отвечают на вопрос, либо создают фиче-реквест, либо создают багу с привязкой к обращению пользователя
            10. Все крупные изменения сначала описываются в документе и получают апрувы у релевантных команд. Нет выделенных архитекторов, в каждом направлении есть несколько архитекторов работающих в командах
            11. Огромный вопрос. Немного об этом здесь и здесь. Сейчас немного поменялся инструментарий, мы выкинули Jmeter, но идиалогия осталась
            12. Это сильно зависит от команды. Даже не получится дать какой то усредненной картины. Есть команды, где баг фиксинг — доли процентов, есть команды где может уходить 50% времени спринтов на баги
            13. Мы сильно заморачиваемся на этот счет. Есть дизайн система
            14. Я бы сказал нет единого формата и договоренности, что не есть хорошо. Нам придется прийти к этому при распилке монолита
            15. Конечно. Техподдержка состоит из 2 линий. Первая отвечает на все обращения, вторая иссследует сложные технические случаи. Там много разных процессов, в том числе, есть процесс по фиксации фиче реквестов. Думаю это тема для целой статьи, и кого из саппорта. Кстати возможно она даже есть, можно поискать…
            16. Тут тоже тема очень большая, как бы на нее ответить комментом… наверное требуется уточнение вопроса, чтобы на него ответить. Может быть интересует что то конкретное?
            17. Почти 10 лет
            18. Шардирование в первую очередь помогает нам распределять нагрузку на разные инстансы. Есть и другие варианты, например вынос чтения на слейв
            19. Конечно, ежегодно, SOC2
            20. Не понял вопрос. Какой имеется ввиде порог вхождения?
            21. Своя реализация. Для бекенд части, канарейка, это новые сервера на новой версии, мы их просто останавливаем. И новые и старые версии всегда совместимы, это правило, по другому у нас нельзя. Для клиенсткой сейчас нет канарейки, только бета
            +1
            Спасибо!
            вообще впечатляет что вы смогли реализовать такую схему для монолита с 150+ разработчиками. Я подозреваю, что у вас все же основной ресурс для улучшения метрик — это переход на микросервисы. Подозреваю, что сборки у вас идут очень долго (десятки минут, часы), возможно, автотесты прогоняются также долго, если вы используете ручное тестирование и регресс — то это также жутко долго. В итоге, возможно, реально ттм у вас измеряется месяцами и возможность выкроить лишние минуты оптимизируя процесс релизов смысле не имеет.
            У нас вот 16 разработчиков и вы в какой -то момент пришли к тому, что сборки и автотесты выполняются очень долго (под 15 мин) и ничего с этим поделать не особо не можем. В итоге стали выделять микросервисы.

            Но может я не прав, тк вы не привели значения метрик. Ситуацию понять сложновато.
            Подскажите пожалуйста.
            Как часто вы делаете релизы?
            Как часто у вас обнаруживаются проблемы в канареечных релизах?
            Упомянуто что вы используете е2е тесты — те это что-то типа selenium? используете ли вы модульные тесты (unit)?
            какой у вас ттм? и что вы под ттм понимаете?
              0

              Автор, вы не могли бы ответить? Правда интересно).

                +2
                -> Как часто вы делаете релизы?
                Каждый день выходит новая версия клиента и новая версия сервера. Стараемся не чаще чем раз в день, бывает чаще
                -> Как часто у вас обнаруживаются проблемы в канареечных релизах?
                К сожалению, часто. На вскидку до 20% релизов находят баги на проде или на бете. Мы постоянно работаем надо улучшением наших процессов обеспечения качества, и это приносит плоды
                -> Упомянуто что вы используете е2е тесты — те это что-то типа selenium? используете ли вы модульные тесты (unit)?
                Мы стараемся не использовать е2е тесты, то есть страраемся больше покрывать на юнит уровне и интеграционном чаще всего. Компонентых тестов практически нет. Есть API тесты. Почти нет контрактных тестов, и мы будем серьзно решать эту проблему при распилке монолите. Но так или иначе е2е тестов достаточно много, сейчас около 2000. Причем они тяжелые, так как мы используем canvas, это дает некоторую сложность в e2e тестировании, много скриншотных тестов. Мы много думаем над оптимизацией тестов, сейчас полный набор е2е тестов гоняется порядрка 17 мин.
                -> какой у вас ттм? и что вы под ттм понимаете?
                ттм это t2m? Я думаю это классический термин. Не смогу сказать какой у нас t2m, потому что сильно зависит от команды

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

            Самое читаемое