В последнее время микросервисы стали настоящим хитом. Это подтверждает и статистика сервиса Google Trends:
Когда я работал в SoundCloud, то отвечал за миграцию с монолитного приложения на Ruby on Rails на целый ряд микросервисов. Техническую часть этой истории я уже рассказывал в презентациях и постах в инженерном блоге компании. Обычно людям интереснее всего как раз такие технические подробности, и недавно я понял, что никогда не рассказывал, почему вообще мы решили начать использовать микросервисы.
Должен расстроить фанатов технологий — мы осуществили переезд из соображения продуктивности, а не технических. Объясню, почему так.
Проект Next
Когдая присоединился к компании, то наиболее важный проект, над которым мы работали, носил внутреннее название v2. Он подразумевал полную перезагрузку сайта, релиз получил название The Next SoundCloud.
Изначально я начал работать в команде бэкенда, она называлась App team. Мы отвечали за то самое монолитное приложение на Ruby on Rails. Тогда мы называли его Mothership. Команда App отвечала за все в Rails-приложении, включая старый пользовательский интерфейс. Next представлял из себя одностраничное JavaScript веб-приложение, и мы пошли по принятому тогда пути, который заключался в разработке приложения, в качестве клиента к публичному API, которое было частью «монолита» на Rails.
Команды App и Web были изолированы друг от друга и даже работали в разных зданиях в Берлине, пересекаясь только на общих встречах и общаясь главным образом в IRC и в комментариях программ для управления разработкой. Если бы вы спросили любого члена двух команд о том, как у нас построен процесс разработки, то они бы описали его как-то так:
- У кого-то возникает идея для новой фичи, эти люди пишут описание на пару параграфов и рисуют несколько мокапов. Затем команда все это обсуждает.
- Дизайнеры создают макет интерфейса.
- Мы пишем код.
- После недолгого тестирования все выкатывается «в продакшен».
В итоге в воздуха постоянно витало разочарование. Инженеры и дизайнеры жаловались на перегрузку, при этом продакт-менеджеры и партнеры жаловались, что ничего никогда не делается вовремя.
Мы были небольшим потребительским бизнесом, так что нам надо было стать партнером для запуска как можно большего числа компаний (ну знаете, как Apple и Google показывают на слайдах скриншоты с разными приложениями в ходе презентаций продуктов) — это позволило бы нам получить много бесплатных публикаций в прессе и нарастить пользовательскую базу. Так нам нужно было запустить закрытое бета-тестирование Next до Рождества, иначе праздничная пора сдвинула бы все наши активности на второй квартал года — мы не хотели релизить новые функции до запуска нового сайта. Чтобы попасть на слайды всех крутых презентаций и не терять целый квартал, нам определенно нужно было уложиться во множество дедлайнов.
Как раз в это время мы решили попробовать точнее понять, куда нас завел наш процесс органического роста.
Взлом процесса
До работы в SoundCloud я потратил очень много времени на консалтинг, и одним из самых полезных инструментов, который я вынес из этого темного прошлого, стал концепт создания Карты потока ценности (Value Stream Map). Не будем углубляться в описание того, зачем оно нужно, и как точно работает, опишем все кратко.
Комбинация неформальных интервью с разными инженерами и собранных данных из разных автоматизированных систем, позволила нам нарисовать карту реального процесса разработки, а не процесса, который мы себе представляли. Сам документ показать не могу, но он не сильно отличался от картинки снизу:
Реальный поток состоял из таких этапов:
- У кого-то появилась идея для новой фичи. Они пишут коротенькую спецификацию с мокапами пары экранов, все это загружается в Google Drive.
- Спецификация висит в Drive, пока у кого-то не появится время взглянуть на нее подробнее.
- Очень маленькая команда дизайнеров получает документ и создает шаблоны интерфейса. Так появляются карточки на доске в Trello, «владельцами» которой являются члены команды Web.
- Карточки висит в Trello минимум две недели, пока инженер не освободится, чтобы поработать над ней.
- Инженер начинает работу. После конвертации дизайна в нечто, что можно с помощью тестовых данных попробовать в браузере, инженер пишет список того, что нужно поменять в Rails API для того, чтобы новая фича заработала. Все это попадает в Pivotal Tracker — инструмент, которым пользовалась команда App.
- Теперь карточка находится в Pivotal и ждет, пока освободится кто-то из команды App — на это уходит еще недели две.
- Член команды App пишет код, тестирует интеграцию и делает все, чтобы API заработало. Затем вносятся изменения в Trello, чтобы сказать команде Web о завершении своей части работы.
- Обновленная карточка находится в бэклоге еще немного, пока инженеры из команды Web завершат работу, которую они уже начали делать, пока свою бэкенд-часть делали парни из App.
- Разработчик команды Web соединяет свой фронтенд-код с тем, что на бэкенде сделали коллеги из App, и дает зеленый свет на развертывание.
- Поскольку развертывания у нас были рискованными, медленными и болезненными, команда App обычно ждала, пока накопится еще несколько изменений в бранче, и уже потом «накатывала» их в продакшен. Это значит, что новая фича находилась в виде кода еще несколько дней, и довольно часто ее приходилось вообще откатывать из-за возникновения проблемы в какой-то части системы, которая вообще с ней не связана.
- Когда-то в будущем фича все-таки оказывалась развернутой.
Шагов могло бывать и больше, поскольку часто члены разных команд хотели что-то уточнить или у них возникали идеи получше первоначальной, но для простоты опустим все это.
В общем, чтобы новая фича оказалась реализованной, требовалось не меньше двух месяцев. Хуже того: более половины этого времени уходило на ожидание, так как какая-то часть работы находилась в процессе, а без ее завершения другие коллеги не могли сделать свой участок.
Инструменты, вроде карты выше, помогают легко увидеть такие странные шаги. Одним их очевидных улучшений, которое пришло нам в голову после ее создания, оказалась идея о том, что нам надо адаптировать технику поезда релизов (release train). Вместо того, чтобы ждать, пока у нас накопится несколько новых фич, можно было просто развертывать их по мере готовности каждый день. Это еще не постоянное развертывание, но немного позволило нам сократить время разработки:
Такие идеи — отличная и мотивирующая вещь, но во всей схеме главной проблемой был пинг-понг между двумя командами фронтенд и бэкенд-разработки.
То, как мы разделили работу между командами Web и App, полностью изолировало разработчиков бэкенда от реального продукта. Это их расстраивало и создавало ощущение, что они на него вообще не влияют. «Нам только говорят, что делать эти любители подвигать пиксели» — так это воспринималось. В текущей рыночной ситуации, когда запрос на опытных разработчиков значительно превышает предложение, такой подход к команде по меньшей мере не назвать умным.
Но сегодня мы будет говорить о том, что проблема заключалась в 47 днях потраченных на «инженерию» и 11 днях реальной разработки. Остальное время вообще уходило впустую на бессмысленное ожидание.
Кстати говоря, если вы думаете, что дело в том, что у нас длинные итерации, и поэтому приходилось ждать так долго, то дело не в этом. Использование вариаций Kanban не сильно помогло сократить это время.
Затем мы решили сделать кое-то оригинальное: создать пары из бэкенд и фронтенд-разработчиков и назначить такой паре фичу до того момента, как она не будет полностью готова. У нас было 8 бэкенд-разработчиков и 11 фронтенд-инженеров, так что новая схема вызвала споры по поводу того, что фронтенд-разработчики будут работать по-максимуму, чтобы бэкендерам надо было тратить меньше времени. Это впечатление возникало интуитивно, однако создание карты процессов показало, что такой подход контрпродуктивен. Все равно сохранялось слишком большое количество пауз.
Мы решили попробовать создать тестовую пару, а затем распространить новую практику на других членов команды. В результате поток выглядел так:
В итоге каждый отдельный сотрудник стал тратить больше времени на работу по конкретной функции. При этом сохранялся процесс обязательного ревью кода (pull request), прежде чем код мог попасть в мастер-ветку нашего Rails-приложения.
Эксперимент был признан удачным, и мы решили распространить его на все шаги процесса. Дизайнер, менеджер по продукту и фронтенд-разработчики стали работать вместе, и цикл разработки удалось еще сократить:
Время разработки значительно сократилось, что позволило нам осуществить первый релиз Next сильно раньше дедлайна. Мы продолжили экспериментировать с созданием пар из сотрудников разных специальностей, что в итоге привело к созданию структуры «команд фич» (feuture team), которая используется в SoundCloud сегодня.
От mothership к legacy
Когда я присоединился к SoundCloud, меня впечатлила инженерная культура компании. Одним из важнейших аспектов была практика обязательного ревью кода.
Шел 2011 год и все стартапы хотели скопировать модель GitHub, которую лучше всего описал Зак Холман в своем выступлении «Как GitHub использует GitHub, чтобы построить GitHub». После многих лет использования так называемого trunk-based-development, мне не терпелось увидеть, как успешные компании вроде SoundCloud и GitHub используют другой подход.
В те времени все инженеры команды App сидели за одним столом, работали над одними и теми же задачами и были во всех смыслах близки. База кода нашего «монолита» была старой, доказавшей свою состоятельность и очень скучной. Все следовали одним принципам и паттернам, новые коммиты лишь расширяли существующий подход и не несли никаких сюрпризов. Все это сделало процесс обязательного ревью почти формальностью, на рассмотрение пулл-реквеста уходило меньше часа.
Чем больше людей покидали эту группу, чтобы сформировать пару с коллегами из команды Web для работы над фичами проекта Next, неформальные каналы коммуникации начали давать сбои. Участились проблемные развертывания, которые были вызваны непониманием того, что мы разворачиваем и как это сделали. Как это часто бывает, после множества таких сбоев мы решили, что решить проблему можно введением более строгого процесса осуществления влияний изменений. Теперь перед добавлением кода в мастер-ветку и его развертыванием, все изменения должны были быть формально одобрены вторым инженером.
Как видно из картинки выше, все это вылилось в долгое ожидание отправки пулл-реквестов в продакшен. Мы пытались это исправить и ввели правило, согласно которому члены команды должны были хотя бы час в день работать с пулл-реквестами, пришедшими от членов из другой команды — от людей, работавших над Next. Это не особенно сократило очередь, более того, мы поняли, что некоторые небольшие пулл-реквесты изучаются многими работниками, в то время, как более крупные (а от разработчиков Next только такие обычно и приходили) никого не интересовали, пока менеджер по продукту не укажет на это команде. Большие изменения нужно было дольше изучать, а природа нашего кода делала это все очень рискованным делом. Так что от таких пулл-реквестов все шарахались как черт от ладана.
Мы посовещались и решили, что инженеры, работающие над Next, будут разбивать свои пулл-реквесты на более мелкие. Это помогло снять боязнь работы с ними и увеличило скорость рассмотрения. Однако такое искусственное разбиение большой работы на мелкие куски привело к тому, что коллеги, которые проводили ревью, часто не видели за деревьями леса: иногда ряд на первый взгляд хороших коммитов скрывал серьезную инфраструктурную ошибку. Нужно было что-то придумать.
Было решено применить старый ход с парами. У нас было требование просмотра кода вторым инженером, а при парном программирование такое ревью осуществлялось в режиме реального времени. В основном, все идея понравилась, а тем, кто не захотел работать в паре, мы разрешили работать по-старинке в одиночку, но не над фичами проекта Next.
Мы начали эксперимент с несколькими парами, но возникли проблемы, которые заставили начинать все с начала. Наша база кода монолита была столь обширна и охватывала столь различные аспекты, что никто не мог во всем этом ориентироваться. Члены команды выработали свои зоны экспертизы, выстроенные вокруг подмодулей приложения. Когда очередная пара программистов должна была работать по очередной карточке, чаще всего получалось так, что связанные с этим части системы были незнакомы ни одному из ее членов. Так что им приходилось либо ждать, пока освободится эксперт именно по этой части, или брать другую, часто менее приоритетную, задачу. Оба вариант не годились.
В компании даже родился мем «все всегда хорошо, пока не понадобится лезть в монолит».
Неснижаемая сложность монолита
Нужно было сделать шаг назад и спросить себя, почему нам вообще понадобились пулл-реквесты. Вот каким был ход мыслей:
- Зачем нужны пулл-реквесты? Потому что годы опыта говорят, что люди делают глупые ошибки, которые можно вынести в продакшен и уронить всю систему на много часов.
- Почему люди так часто ошибаются? Потому что кодовая база очень сложна. В голове всего не удержишь.
- Почему база кода так сложна? Потому что SoundCloud начинался, как очень простой сайт, но со временем вырос в большую платформу. У нас много фич, различные клиентские приложение, разные типы пользователей, синхронные и асинхронные потоки и огромный масштаб. Код реализует и отражает многие компоненты новой сложной платформы.
- А зачем нужна единая база кода для реализации разных компонент? Из-за экономики объёма. У модели mothership были свои плюсы — отлаженный процесс развертывания, большое количество инструментов, архитектура выдерживает серьезные нагрузки и DDoS-атаки, лего масштабируется горизонтально и т.п. Если мы станем строить новые системы, то все это придется выстраивать для каждой из них с нуля.
- Почему мы не можем использовать экономику масштаба для разных, более маленьких систем? Хм…
Ответить на пятый вопрос было сложнее. Коллективный опыт подсказывал два возможных ответа:
- (А) Почему мы не можем использовать экономику масштаба для разных, более маленьких систем? Не то, чтобы мы не могли, но это будет не так эффективно, как при использовании единой базы кода. Вместо этого следует отладить тестирование и использовать современные инструменты для разработки монолита. Так делают Facebook и Etsy.
- (Б) Почему мы не можем использовать экономику масштаба для разных, более маленьких систем? Мы можем. Нужно будет поэкспериментировать с инструментами и поддержкой. Также, в зависимости от того, как много отдельных систем мы будем делать, нужно будет подумать об их экономике масштаба. Так свои системы построили Netflix, Twitter и другие компании.
У каждого подхода были свои сторонники, ни один из них не звучал как однозначно бредовый или правильный. Главный вопрос заключался в том, как много усилий потребует каждый из них. Ресурсы и деньги не были большой проблемой, но у нас не было достаточно времени и людей, чтобы занять их мегапроектом. Нужна была стратегию, которую можно было бы реализовать шаг за шагом, но которая бы начала приносить пользу с самого начала.
Пришлось еще раз посмотреть на то, что у нас было. Мы всегда представляли наш бэкенд-систему как нечто простое по форме:
Если думать о ней так, то логично реализовать систему в виде единого монолитного куска. Однако, позадавав себе вопросы, мы поняли, что все было не так просто. Если открыть этот черный ящик, то становилось ясно, что система куда сложнее и похожа (упрощенно) на картинку ниже:
У нас не один сайт, а платформа с множеством компонентов. У каждого из них свои акционеры и владельцы, и свой независимый жизненный цикл.
Например, модуль подписок (subscriptions) был создан однажды, и его нужно было бы видоизменить тогда, когда платежный шлюз попросит нас что-то поменять. С другой стороны, модуль оповещений (notifications) и другие части, связанные с ростом и возвращаемостью пользователей, должны подвергаться ежедневным обновлениям, поскольку молодой стартап всегда пытается нащупать ходы, которые привлекут аудиторию.
Кроме того, для разных модулей ожидание уровня сервиса также различалось. Никто не умрет, если на час «отвалятся» оповещения, но пятиминутное падение модуля проигрывания аудио могло серьезно повлиять на проект финансово.
Во время изучения опции (А), мы пришли к выводу, что единственный способ заставить схему с монолитом работать заключается в том, чтобы сделать эти компоненты ярко выраженными, как в коде, так и в архитектуре развертывания.
На уровне кода нужно было убедиться в том, что внесение изменений в конкретную функцию можно проводить в относительной изоляции, без необходимости трогать код других компонентов. Нужно было получить разумную уверенность в том, что изменение не повлечет за собой баги и изменение поведения в несвязанных частях системы. Это старая проблема индустрии, и мы знали, что нам нужно использовать связанные контексты и думать о том, как модуль может быть связан с другими.
Мы обсудили использование Rails engines и других средств для реализации схемы. Получалось, что все будет выглядеть примерно так:
С точки зрения развертывания нам бы понадобилось убедиться в возможности изолированного развертывания фичи. Публикация изменения в модуле в продакшен не должна требовать повторного развертывания несвязанных модулей, и если в ходе такого развертывания что-то пойдет не так и продуктивная часть сломается, то единственной функцией, на которой скажется сбой, должна быть только та, что его вызвала.
Чтобы добиться этого, мы подумали о том, чтобы о развертывании однной фичи на всех серверах с одновременных использованием балансировщиков нагрузки, чтобы сделать группу серверов ответственной только за одну фичу, изолируя любые проблемы, связанные с ней, только на этих машинах:
Реализовать это будет сложно. Даже если все пройдет гладко, мы знаем, что текущий код монолита все равно должен пройти рефакторинг. За последние годы этот код вынес многое. Помимо прочего, мы решили, что нужно обновиться с Rails 2.x до 3, что уже само по себе масштабный миграционный проект.
Все эти мысли заставили нас переосмыслить опцию (Б), подумав, что ее можно реализовать иначе:
Мы смогли бы получить выгоду от нового подхода с первого дня. Реализовывать новые функции стало бы проще, и не потребовались бы задержки на пулл-реквесты.
Решено было попробовать пойти этим путем и построить все, что нужно было для нашего проекта по монетизации, в качестве сервисов, изолированных от монолита. Проект включал масштабные обновления и полное переосмысление модели подписки, но нам удалось его закончить раньше сроков двумя командами по два инженера в каждой.
Опыт был признан удачным, и мы решили продолжить применять этот подход для всех новых разработок. Первые сервисы мы создали с помощью Closure и JRuby, в конце концов перейдя на Scala и Finagle.
Необходимая ссылка на закон Конвея
Почти все, что появилось нового в SoundCloud с 2013 года, использует сервисы. В какой-то момент мы стали использовать для их описания слово «микросервисы».
Новый архитектурный фреймворк позволил сократить время на создание новых функций до, пусть не идеальных, но уже куда более приемлемых для компании величин, позволявших не отставать от конкурентов на сложном музыкальном рынке:
Все просто отлично для новых функций. Но когда нужно было переработать часть монолита, мы использовали старый подход. При этом так много людей работало с микросервисами, что количество свободных для ревью инженеров постоянно уменьшалось, так что очередь из пулл-реквестов росла.
Мы пытались извлекать из монолита разные функции в процессе рефакторинга, но обычно это не удавалось. Людям проще работать со старым кодом или изобретать странные гибриды из монолита и микросервисов, где одно от другого не отличить.
На этом этапе команда App выступала в роли набора бэкенд-разработчиков, которых совмещали с людьми из команды Web для работы над определенными функциями. Работники прыгали с фичи на фичу все время, что не позволяло создать чувство владения конкретной функцией и автономности для части системы. Никто не хочет рисковать и трогать древний кусок кода, если они не чувствуют принадлежность к нему. За это отвечали все, и никто.
Мы думали о разбиение команд бэкенда на несколько более маленьких команд, сфокусированных на конкретных областях. Мы много думали на эту тему, но так ни к чему и не пришли. В итоге я разбил группу на команды по 3-4 человека и наполовину случайно выдал им модули для работы.
Также им было сказано, что за этот модуль отвечают они, и если с ним что-то случится, то исправлять сбой будут они. Также это дало работникам большую свободу творчества. Если команда решила оставить что-то в монолите, то это их ответственность.
Как нетрудно предположить, такой подход привел к настоящему исходу из монолитка. Сообщения, статистика и все важные фичи, которые были нам нужны для iOS-приложения, были извлечены из общей базы кода.
Все шло отлично, но подход с полуслучайным назначением команд имел побочный эффект: одна команда отвечала за большинство фундаментальных фич и объектов в экосистеме. Эта команда всегда работала в режиме тушения пожара, и у них не было мотивации мигрировать свои модули на микросервисы, так как это очень рискованно и повышает вероятность сбоев.
С этой проблемой мы справились только недавно. За главные объекты все еще отвечает одна команда, но теперь архитектура более стабильна, что снижает число «пожаров». В итоге даже эти сотрудники могут выделить время для перехода на микросервисы.
На сегодняшний день у SoundCloud все равно есть монолитный код, но его важность снижается с каждым днем. Он все еще критичен для многих функций, но теперь он даже не работает напрямую с интернетом (благодаря специальной системе). Возможно, полностью он не исчезнет никогда — многие функции столь малы и стабильны, что может быть дешевле оставить все как есть, но уже через год ничего реально важного там не останется.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий