Как расширить функциональность ПО на продакшене, ничего не сломав

Партнёрский материал. Что это?

Рассказывает Павел Оборин, ведущий разработчик в «Иксолла»

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

Типы развития ПО

Мне нравится рассматривать время как неотъемлемую часть некоего универсального дифференциального уравнения эволюции, описывающего развитие систем. Со временем все системы либо развиваются, либо исчезают — можно сказать, что так действует эволюция. Говоря здесь о развитии, я буду иметь в виду развитие бэкенда серверных приложений.

Известны разные варианты развития программного обеспечения (см. Стив Макконнелл «Совершенный код»), например, развитие через постепенное улучшение и приращение функциональности, создание «лучшей версии» системы через полное переписывание и гибридный подход, основанный на комбинации первых двух.

Главными минусами подхода с полным переписыванием системы являются большие временные затраты и отставание новой версии от мастер-версии, в которую постоянно добавляется новая функциональность. Процесс может затянуться в разы по сравнению с изначально заложенными сроками, и, как следствие, появляется риск провала.

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

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

Эффект «плато»

Мы привыкли думать о развитии как о плавном процессе, но на практике оно чаще всего происходит скачкообразно. Пауза между скачками в развитии называется эффектом плато. Такое развитие обычно выглядит как отсутствие роста или очень плавный рост — «плато», за которым следует скачок на качественно новый уровень, а затем снова «плато».

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

Скачки роста между периодами плато часто называют апгрейдами. В данной статье этот термин является собирательным названием для действий, приводящих к быстрому наращиванию функциональности или повышению производительности систем. Обычно на презентациях докладчиков IT-конференций апгрейды — это те ключевые моменты, которые докладчики упоминают при показе временной шкалы развития своих систем. Апгрейд системы — это практически всегда большая задача. Она часто скрывает под собой «завоз» новых технологий или переписывание большого куска системы. Какому бы типу развития ни отдавалось предпочтение, вам не удастся избежать апгрейда, если вы хотите, чтобы система оставалась конкурентоспособной на рынке.

Апгрейд системы на практике: как разработчику минимизировать риски, если что-то пойдет не так

С точки зрения разработчика практически любая задача выглядит так: продолжительный процесс разработки, тестирование и деплой. Но на деле все обстоит немного иначе — изначальные планы разработчиков часто рушатся. Основная проблема состоит в том, что в реальности всегда что-то может пойти не так, особенно если речь идет о таких больших задачах, как апгрейд.

Проблемы могут возникнуть не только в процессе разработки (например, отклонение от первоначального плана), но и после релиза. Что-то может сломаться как внутри самой системы, так и в каждом из связанных с ней внешних компонентов. Причем если поведение внешних систем от нас не зависит, то уменьшить возможные риски, связанные с отклонениями в подконтрольной системе, в наших силах.

Проектирование механизмов обратной связи

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

«Что я буду делать, если что-то пойдет не по плану?»

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

Мы привыкли, что если что-то идет не так после деплоя, это выявляется практически сразу. Сейчас уже все знают про важность метрик и логов. Мы знаем, что в хорошем продукте, над которым работают «нормальные ребята», есть метрики на все, поэтому любую аномалию тут же выявят. Однако системы бывают разные. Бывает, что комбинаций всевозможных параметров так много, что выявить, какая из этих комбинаций — симптом проблемы, а какая характеризует нормальную работу, — совсем нетривиальная задача.

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

Как вы ее обнаружите?

Что вы будете делать, если через два дня после релиза что-то пойдет не так?

Должны ли вы наблюдать за судьбой своей системы после того, как вы выкатились, или вам следует тут же с головой окунуться в новую задачу?

Если не вы, то кто будет следить за системой после выкатывания, сколько дней? Как минимум нужно найти человека, не загруженного работой. А реально ли это?

Что-то пошло не так, или вы только предполагаете, что что-то идет не так. Кто вам поможет в этой сложной ситуации? Будете ли вы сами писать запросы в базу и искать по логам подтверждение своей гипотезы об аномалиях?

Если вы сами когда-то попадали в подобную ситуацию, то понимаете, насколько это неприятное положение. Чтобы не переживать это снова и снова, продумайте заранее негативные сценарии, продумайте, как вы можете сами себе помочь на этапе проектирования, пока у вас еще есть время. Какие метрики вы будете использовать, какие логи смотреть, когда времени у вас уже не будет.

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

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

Минимизация потерь за счет плавного переключения через механизм тогглеров и постепенного замещения старой функциональности

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

Одной из самых распространенных ошибок при апгрейде системы является переключение типа «вжух» между старой и новой версией системы: вжух — и система после деплоя на продакшен перешла из состояния А в Б. Однако чаще всего «вжух» так не работает.
В какой-то мере помогает протестировать систему на регресс использование сплита (canary deployment) для обкатки новой фичи только на части трафика, но это работает далеко не всегда. Никто обычно не держит на сплите изменения больше одного дня. А цикл полной обкатки может быть завязан на какие-то внешние циклы, которые вполне могут иметь длительность в несколько дней или даже недель.

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

Одной из первых задач, в которой мы впервые применили тогглеры, была задача переработки системы списания подписок. Изначально продление подписок проходило с интервалом несколько раз в день, и это было приемлемо, пока подписок было относительно немного. Рост количества подписок потребовал переработать систему списания так, чтобы подписка продлевалась с точностью до секунды.

Еще на этапе тестирования обнаружилось, что выкатывать на всех партнеров данную фичу нельзя, и ее необходимо согласовать с каждым партнером индивидуально. Так мы использовали тогглер по проектам. Все боевые проекты мы разбили на группы: первая волна, вторая волна и третья волна. Когда фича попала на бой, она была включена только у тестового проекта. Сперва переключали небольшие проекты первой волны и, когда очередь дошла до основных проектов, у нас уже была обкатана вся инфраструктура, аналитика и сформировалось понимание всех нюансов нового поведения системы.

Каждое решение имеет свою цену — здесь наряду с гибкостью нас поджидают и несколько «утяжеляющих» факторов. Это гирьки, которые все время будут привязаны к вашим ногам, но тем не менее вы должны научиться взлетать с ними:

  1. Во время перехода между системами, который может затянуться, предстоит поддержка, по сути, двух систем (разработка при этом не останавливается), а это: своя логика, свои тесты, свои логи, своя конфигурация и т. п. Другими словами, все умножается на два. Поэтому перед этим хорошо бы пересмотреть сбор тех же логов и метрик по старой системе и вынести их в удобном виде на новые дашборды. Возможно, на этапе подготовки стоит заранее реализовать какие-то специальные задачи на этот счет.
  2. Решение конфликтов новой и старой логики. Скорее всего, не получится просто взять и выкатить параллельно новую функциональность, не задев текущую. Нужно проверить, не будет ли деградации, и внести соответствующие доработки и в новой, и в старой системах.

Если говорить образно, я предлагаю проводить такой апгрейд системы, при котором мы не просто «строим новый дом», не снося старого, но живем в обоих, постепенно съезжая из старого, день за днем перенося по одной чашке, вешалке, стулу. Это муторно и в обычной жизни может показаться нерациональным и неправильным, но все иначе, когда вам действительно есть что терять.
В каком-то смысле вы не перерабатываете старую систему, а создаете новую, постепенно замещая ей старую.

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

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

Построение правильных взаимоотношений

Другие разработчики, менеджеры, необходимость стыковки параллельно разрабатываемых подсистем, сложность тестирования, взаимодействие с админами, поддержка документации, служба саппорта и даже прогноз реакции конечного потребителя — все это внешние зависимости, которые придется учитывать разработчику, если он действительно хочет выкатить фичу в срок. При неправильном подходе все это может превратиться для разработчика, как говорится в одной игре, в «войну, которая никогда не меняется».

Во всех командах, где я работал, можно было услышать утверждение «на нас все держится». Но открою секрет: так считают в любой команде любого отдела. Зайдите к админам, антифроду, саппорту, менеджерам, тестировщикам — они скажут вам то же самое. Эта статья — взгляд на разработку с точки зрения разработчика, написанная разработчиком, и поэтому в ней разработчик занимает центральное место. Но это не значит, что одновременно не может быть в центре менеджер, админы, тестировщики или другие люди. Даже наоборот, в этом есть свой смысл: когда систему опутывает сеть из разных людей со своими точками роста и своим видением системы — это определенно хорошо. Однако в других отделах люди изначально работают на другой результат. Может показаться, что вы работаете над одной задачей, но на самом деле у них совсем другие цели. Обладая самыми глубокими знаниями о системе, именно разработчик должен стать локомотивом процесса апгрейда.

Я считаю, что именно разработчик и команда, реализующая задачу, должны довести ее до конца. Если вы во что-то вложились, это становится для вас важным. И вам решать, кто будет в команде. Может, следует сразу заманить в нее своего админа, аналитика, техписа, человека из саппорта. Лучше всего везде найти своих людей (если вы еще этого не сделали) и поддерживать с ними связь в течение всего цикла. Привлекайте консультацию аналитики и всех служб, которые могут способствовать успеху вашей задачи. В идеале разработчик должен наладить каналы связи со всеми отделами, чтобы информация о текущем состоянии задачи свободно распространялась в обоих направлениях (для крупных задач этой действительно важно).

Что в итоге?

Какая из всего написанного мораль? Если вы планируете сложный апгрейд системы, то:

  1. Переписывание системы с нуля с последующим переключением на нее всего трафика сразу — плохо.
  2. Непродуманная организация обратной связи еще на этапе архитектуры в виде дашбордов на основе логов, метрик, аналитики — плохо.
  3. Отсутствие прозрачности, открытой и доступной документации, свободной циркуляции актуальной информации, открытости всех дашбордов с метриками и аналитикой, отсутствие понятного даже непосвященному человеку описания того, что происходит и где, отсутствие сценариев действия в случае каких-то проблем — плохо.
  4. Не заручиться заранее поддержкой и пониманием задачи у нужных людей в лице админов, dba, аналитиков, техписов, саппорта — это тоже плохо.
  5. И последнее: не нужно тянуть одеяло на себя и пытаться все сделать в одиночку. Чаще всего это приводит к плачевным результатам.

Логотип «Иксолла»Эта статья подготовлена при поддержке компании «Иксолла». Редакция Tproger выражает благодарность представителям компании за хороший материал и работу при его подготовке.

Рекламные публикации для бизнеса:
sales@tproger.ru, +7 916 559-71-10

Tproger