Релизный поезд. Доклад Яндекса
Релизные процессы в разных командах Яндекса (да и в любых больших IT-компаниях) устроены похожим образом, но отличаются во многих деталях. У мобильных разработчиков своя специфика: на их релизы влияет порядок выкладки в App Store и Google Play. Android-разработчик Дмитрий Поляков рассказал о процессах вокруг себя — как его команда отправляет по расписанию релизный поезд, как запускать внеплановые релизы, добавлять вагончики в уже уехавший релиз и что делать, чтобы не сойти с рельс.
— Всем привет, я Дмитрий Поляков, Android-разработчик мобильного приложения Беру.
Сейчас в двух командах — Android- и iOS-разработки Беру — по 13 человек. Это позволяет нам делать много классных задач параллельно и быстрее докатывать их до пользователей. В докладе я расскажу, как мы научились работать с git и жить в одном репозитории.
Дальше расскажу, как у нас устроен релизный цикл. К нам приходят менеджеры и говорят, что мы хотим выкатить вот эту фичу как можно скорее. Так, наверное, любой менеджер хочет, поэтому мы настроили релизный процесс так, что мы катаемся в App Store и Google Play каждую неделю. Также я расскажу про инструменты, которые у нас есть и которые я бы порекомендовал вам попробовать, они классные.
Git Flow
Начнем с Git Flow. За основу мы брали классический Git Flow, правда, он сильно изменился под наши условия, под нашу команду. Вы тоже посмотрите, и, может, что-то вам из этого подойдет, а что-то нет. У каждой команды свой подход к работе с git.
Как это устроено у нас? Epic — корневая ветвь для вашей фичи, для какой-то большой функциональности. Чтобы было более наглядно, давайте рассматривать сразу на продуктовом примере.
Приходит менеджер и говорит — разработчик, сделай нам функциональность вишлиста с избранными товарами в приложении. Разработчик заводит новую ветку epic и называет ее wishlist.
Дальше он ее декомпозирует на более мелкие задачи, которые заводит в трекере. Возможно, это работа с сетью, отрисовка UI, написание тестов. Для каждой такой фичи он заводит таск в трекере. И как только он приступает к выполнению задачи, он заводит соответствующую фичу-ветку. Бранчует ее от этого же эпика.
Как только он заканчивает работу над одной такой фичей, он ее вливает в epic через пул-реквест. Пул-реквест — такой механизм, когда другие разработчики из вашей команды проверяют ваш код. Если им что-то не понравится, то ваш код может не доехать до вашего эпика, а значит — не выехать в релиз, пока вы не найдете взаимосогласие с ними.
В нашей команде таких проверяющих двое. Они назначаются случайным образом. Это автоматизированный процесс, он выбирает из тех разработчиков, которые недавно работали с теми же файлами, которые вы изменили в своем пул-реквесте.
Таким образом получается, что в проекте параллельно живут несколько эпиков со своим набором фич. У эпика может быть и всего одна фича. Так может получиться, если мы хотим эту фичу заливать через пул-реквест в epic.
Как только работа над всеми фичами в рамках одного epic завершается, задача впервые уходит команде тестирования, команде QA, и они функционально тестируют ветку. Как только они нашли баги, вы правите их в рамках этого epic. Как только все баги пофикшены, вы заливаете этот epic в develop. Здесь уже никакого дополнительного код-ревью со стороны вашей команды не проводится, потому что весь код уже был посмотрен на этапе мёржа фичи в epic.
Наш develop — такая ветка, в которую заезжает уже протестированный код, потому что на этапе epic его тестируют и код прошел через пул-реквест.
Это позволяет нам безопасно создавать новую ветку релиза в начале релизного цикла. Не опасаясь, что там будет много багов, которые еще не были протестированы. Поэтому мы создаем новую ветку релиза от develop, ее тестируют. Как только релиз протестирован и мы готовы уезжать дальше в стор, то эта ветка вливается в master.
Иногда у нас случается hotfix, и под него есть отдельный тип ветки. Это очень коротенький релиз, который надо выкатить быстро. У нас нет времени ждать три-четыре дня, чтобы начался очередной релизный цикл. Обычно это что-то небольшое.
Например, если какой-то баг попал в продакшен и является очень критичным, нам надо его срочно пофиксить. Поэтому мы останавливаем текущий релиз, запускаем hotfix на этот баг и обновляем наш релиз. Но hotfix — не всегда история про баги. Иногда у нас возникает и продуктовая необходимость.
Например, как только в Москве ввели режим самоизоляции, у нас возникла продуктовая необходимость, чтобы пользователи могли бесконтактно заказывать себе товары. Теперь пользователь при оформлении заказа в нашем приложении может выбрать функцию «Оставить у двери». Курьер приедет, оставит посылку под дверью и бесконтактно вручит ее вам.
С этой задачей в hotfix мы также включили вот такие различные информеры, которые призывают вас оставаться дома и заказывать товары курьером. Не стоит сейчас ездить в пункты самовывоза. Эти задачи мы выкатили через hotfix, чтобы как можно быстрее их донести пользователю, потому что мы их считаем важными.
Когда у нас случается hotfix, мы его бранчуем от master. От develop его нельзя бранчевать, потому что в develop к этому моменту могли уже прийти новые epic, еще не попавшие в релиз. Но мы не хотим эти epic забирать с собой в hotfix, чтобы они случайным образом нас не зааффектили и не заблокировали наш hotfix. После завершения hotfix мы его вливаем в master и также подливаем его в develop, так как в develop этого кода еще не было.
Master — это кодовая база, соответствующая последней версии приложения, которая сейчас находится в сторе у пользователя. Она чистая, без багов, потому что там уже прошло и функциональное, и регрессионное тестирование, это backup-веточка.
Когда у нас завершается релиз, мы его тоже вливаем обратно в develop. Потому что в рамках релиза могут быть найдены баги, которые не нашлись на функциональном тестировании, да и различные epic между собой могут конфликтовать. Поэтому релиз мы тоже вливаем в develop, чтобы эти фиксы были у нас и в develop.
Над epic может вестись долгая работа, и чтобы не отставать от кода в develop, разработчик иногда подливает его в свой epic, чтобы потом было меньше мёрж-конфликтов.
Так как мы в epic подлили develop, то epic по тем же причинам нужно подлить и в вашу фичу.
В названиях эпичных и фичевых веток у нас есть номер тикета в таск-трекере. Это классно, потому что именно по номеру тикета мы можем совершенно по любой части нашего приложения узнать, в рамках каких тикетов она изменялась, кто написал этот код и с какой целью он был написан.
Релизная и hotfix-ветка имеет в своем названии текущий номер релизной версии приложения. Некоторые команды совмещают номер hotfix с номером релиза, обосновывают это тем, что hotfix — нечто маленькое и, возможно, не очень важное для пользователя. Поэтому они не увеличивают версию приложения. Мы таким подходом не пользуемся, потому что к нам прилетают различные crash reports от производителей, и мы хотим точно понимать, был ли этот report в hotfix или в релизе, чтобы знать, где искать проблему.
Master и develop — ветки, которые постоянно живут в нашем репозитории. Как один раз создались, так и живут. Поэтому названы так лаконично.
Вот по такой схеме мы и живем. Сейчас нам комфортно, удобно.
Release Train
Переходим к нашим релизным процессам. Но прежде чем рассказать про релизы и как мы их строим, я расскажу про роли, которые у нас есть для поддержки релиза.
У нас есть дежурный разработчик, который в рамках одной рабочей недели перестает заниматься продуктовыми задачами и занимается фиксом багов из текущего релиза. Если у него задач по текущему релизу больше нет, он фиксит какой-нибудь технический долг, который у нас накопился, берет задачи из бэклога и правит их.
Еще есть дежурный тестировщик. Он тоже в течение одной рабочей недели перестает тестировать продуктовые задачи и проверяет текущий релиз. Если у него задач по текущему релизу нет, он тестирует то, что поправили в рамках техдолга.
Релиз у нас начинается в пятницу. Именно в этот день у нас есть жесткий дедлайн. В 18 часов вечера дежурный тестировщик нажимает на кнопку «Старт релиза». После этого момента всё, что будет влито в develop, уже не попадет в текущий релиз, потому что после нажатия на кнопку создалась релизная ветка, develop подлился и больше develop туда подливаться не будет.
В пятницу происходит еще один важный процесс, еще один пункт по текущему релизу, о котором я расскажу позже.
На выходных мы отдыхаем, поэтому второй релизный день — это понедельник. Дежурный разработчик начинает день с проведения анализа. Он смотрит, что в текущем релизе было изменено с точки зрения кода. Берет git diff между текущей релизной веткой и master. И по этому diff он смотрит, какие компоненты были затронуты. Возможно, затронут процесс оформления заказа или корзина, а работа с постаматами не затронута.
Тем самым он формирует список различных кейсов, которые будут проверяться на тестировании. Это помогает нам ускорить наш регресс, проверять не все приложение. Когда список составлен, команда тестирования проверяет приложение, а дежурный разработчик правит баги. Если у него очень много багов, он может делегировать часть другим разработчикам, которые недавно работали с этими фрагментами кода.
Во вторник мы продолжаем тестировать наш релиз и править релизные баги. Ближе ко второй половине дня наше регрессионное тестирование заканчивается и мы готовы уезжать в стор. Мы запускаем наш релизный поезд — на самом деле, даже несколько релизных поездов, потому что мы недавно мы вышли в новый для нас маркетплейс. Вам я тоже рекомендую попробовать опубликоваться не только в Google Play, но и в каких-нибудь других. Плюс не только в том, что вы получаете новую лояльную аудиторию.
Как-то мы опубликовали наш релиз и через несколько часов обнаружили, что количество багов у пользователей сильно выросло. Мы посмотрели эти баги, проанализировали их и увидели, что они возникают только на устройствах Huawei. Мы сразу не поняли, в чем дело, но у нас были Huawei, мы потестировали на них, нашли багу, поправили и пошли обновляться.
Как только мы пришли с обновлением в Google Play — увидели большой баннер, в котором говорилось, что из-за текущей ситуации в мире у Google Play очень большая нагрузка и они не успевают проверять приложения так же быстро, как в обычное время. Получилось, что наше приложение еще не успели проверить, мы не доехали до пользователей Google Play, а опубликовались только в Huawei AppGallery. И именно это стало причиной, почему у нас баги были только на Huawei. Тем самым удалось еще до публикации в Google Play обнаружить и пофиксить критичный баг.
Дальше я расскажу, как у нас устроены публикации именно через Google Play, потому что там у нас очень большая доля пользователей. А в Huawei AppGallery мы совсем недавно вышли и еще пытаемся понять, как там все устроено.
Мы не публикуемся сразу на всех пользователей в Google Play, чтобы какой-то случайный баг не зааффектил всю нашу аудиторию. Мы публикуемся только на всех тестировщиков, которые подписались под то, что у них может быть баг, но они будут первыми получать наши изменения и релизы. Кроме того, мы публикуем всего лишь на пять процентов аудитории.
В среду дежурный разработчик смотрит crash-free нового релиза. Нам важно, чтобы не было новых crash и чтобы старых было не очень много. Если все в норме, он еще проверяет продуктовые метрики. Например, чтобы количество заказов не упало по сравнению с аналогичным периодом. Если продуктовые метрики и crash-free у нас хорошие, мы раскатываемся еще на 5%, суммарно 10%.
В четверг дежурный разработчик проверяет отзывы в сторе. На самом деле, он и в среду их смотрит. Правда, в среду у нас еще аудитория небольшая, один-два отзыва. Но в четверг отзывов уже больше, чтобы судить о релизе. Их может быть 10–15 штук.
Зачем он вообще смотрит на отзывы, если у нас много метрик и графиков? Приложение может не крешиться, даже метрики могут быть в порядке. Но возможно, у пользователя поехали шрифты или, может, у него не работает какой-нибудь фильтр. Мы стараемся сделать пользование приложением для пользователя как можно удобнее и анализируем такие отзывы, правим баги или проблемы, с которыми пользователь сталкивается.
Если отзывы в порядке, crash-free тоже нормальный и продуктовые метрики не просели, мы раскатываемся уже на 20%.
И вот начинается пятница, день запуска нашего релиза. Третий пункт, о котором я хотел рассказать, — это то, что в пятницу мы завершаем текущий релиз. Мы его раскатываем с 20% сразу на 100%. Кажется, это очень большой прыжок и очень рискованно. Но это зависит от команды и вашей аудитории.
20% нашей аудитории позволяет нам с большой вероятностью судить о стабильности релиза. И если на 20% все хорошо, и в пятницу мы не увидели проблем, то сразу катимся на соточку.
Знаю команды, которые используют лайфхак в Google Play — возможно, он тоже вам поможет. Вы можете раскатываться не на 100%, а на 99,9%. Тем самым у вас будет оставаться кнопка в Google Play для экстренной остановки релиза. А если вы раскатились на 100%, эта кнопка пропадает. Но, как я уже сказал, по двадцати процентам нашей аудитории мы можем точно судить о стабильности релиза. Поэтому мы спокойно раскатываемся на 100%, это избавляет нас от дополнительных шагов. А то потом потребуется раскатить еще на 0,01%.
Таков наш процесс, так мы каждую неделю катаемся и стараемся не сбиваться.
Какие у нас еще есть инструменты, чтобы поддерживать хорошую жизнь пользователя на его стороне? Это Force Update, Soft Update и Feature Toggle.
Force Update — механизм, который блокирует пользование приложением, если его версия сильно устарела. Версия, которая считается устаревшей, задается в админке на сервере. И как только там чиселку изменили, у некоторых приложений появится вот такая коробочка, которая не даст зайти. Будет только кнопка «Обновить», пользователь будет вынужден обновиться.
Мы стараемся как можно реже использовать этот механизм, но иногда он очень важен. Например, если мы сломали обратную совместимость, выкатили новую фичу, которая не поддержана в старом коде. Тогда пользователь устаревшей версии приложения может попасть в неконсистентное состояние. Он пройдет в корзину, а у него, например, заказ не будет оформляться. Он не будет понимать, почему, хотя в новой версии все ошибки прописаны и будет понятно, почему не удается оформить заказ.
В помощь к Force Update есть Soft Update. Это нативная штука от Google, которая просто внедряется в ваше приложение и не блокирует пользование. Но она говорит — в Google Play есть обновление, установи и у тебя будут новые классные штуки.
Изначально она внедряется таким диалогом. Это нативный дизайн от Android. А дальше его можно внедрить в ваше приложение. Например, мы внедрили его в наш виджет «Обновите приложение» в нашей цветовой гамме.
Soft Update позволил нам очень сильно уменьшить хвост версий, и внедряется он просто по документации. Попробуйте, если у вас много релизов.
Еще один немаловажный инструмент — Feature Toggle. Он позволяют регулировать часть функциональности на стороне пользователя, изменяя ее в админке. Есть набор фич, которые мы можем включать и выключать с нашего сервера без дополнительных обновлений приложения.
Давайте расскажу, как работает Feature Toggle на примере стороннего приложения — транспортного средства. Изначально у разработчиков есть вот такой велосипед, у которого уже поддержано два Feature Toggle: мотор и большой размер. Клиенты пользуются велосипедом, потом команда тестирования говорит: мы протестировали мотор, он работает, драйв, круто, давайте его включать на пользователя.
Заходим в админку, включаем Feature Toggle и велосипед у пользователя без обновления, прямо на ходу превращается в мопед. Пользователю комфортно, это позволяет ему передвигаться быстрее и удобнее.
Продукт развивается, аудитории становится много, она уже не помещается на одном мопеде. Пользователи хотят возить с собой семью, ездить вместе. Разработчики и менеджеры предусмотрели дополнительный Feature Toggle — большой размер. Мы его включаем в админке, и транспортное средство у пользователя на ходу становится большим.
Кажется, классно: Feature Toggle нам помогает. Это так, но есть проблемы, с которыми вы можете столкнуться. Например, нужно следить за обратной совместимостью и за совместимостью Feature Toggle. Предположим, в какой-то момент у приложения сломается мотор, произойдет креш или баг. Или этот мотор будет есть очень много наших ресурсов, и мы не сможем поддерживать слишком много пользователей с мотором. Тогда нам придется его отключить.
Но мы не хотим, чтобы приложение у пользователя пропало. Мы хотим дать ему возможность пользоваться приложением, несмотря на то, что у него остается Toggle с большим размером. Поэтому когда мы отключаем мотор, у нас должен быть fallback-механизм для управления транспортным средством. В данном случае это вот такой гибрид.
Возможно, еще стоило подумать над тем, чтобы крыша откидывалась. Возможно, водителю будет неудобно сидеть на таком сиденье. Но он все равно сможет управлять транспортным средством, пользоваться приложением, а не ходить пешком.
Как еще мы используем Feature Toggle? Предположим, бэкенды еще разрабатываются и не готовы к релизу. Тогда мы можем разработать часть функциональности, поддержать по контракту API все связи с бэкендом, поддержать UI и выкатиться с выключенным Feature Toggle. Как только бэкенды будут готовы, мы протестируем, что все работает хорошо, и если да — включим Feature Toggle. Тогда пользователю не надо будет обновляться, чтобы получить новые возможности. То есть у нас уже будет аудитория, мы сразу появимся у этой аудитории. Так тоже здорово.
Сейчас у нас, как я уже сказал, по 13 человек в командах Android- и iOS-разработки. Мы работаем по одному Git Flow, в одном репозитории, наладили наш плановый релизный процесс, уменьшили time to market и катаемся в сторы каждую неделю. Недавно вышли в Huawei AppGallery, смотрим и на другие сторы. Научились изменять приложения пользователей без обновлений за счет Feature Toggle. Спасибо за внимание.