Вопрос подготовки релизов и внесения новых изменений стоит перед любыми командами. Но чем масштабнее сфера применения каждого конкретного решения, тем важнее не допустить downtime, связанного с ошибками в новом функционале. В этом посте я расскажу о том, почему нам в Леруа Мерлен потребовалось развивать новый механизм работы с обновлениями, из-за чего не подошел ни один из популярных инструментов и что мы стали делать в итоге. Также мы обсудим различные подходы к организации функций Feature Toggle. Если вы тоже интересуетесь этой темой, приглашаю под кат!

Меня зовут Егор Дуняшин, и я — тимлид команды в Леруа Мерлен, мы занимаемся разработкой системы управления складскими операциями. Наша система работает по всей России — от Хабаровска до Калининграда. А значит, требования к ее доступности 24/7. 

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

Одновременная поддержка множества процессов и потоков
Одновременная поддержка множества процессов и потоков

Понятно, что WMS — это автоматизация склада. Но с развитием системы мы стали поддерживать новые бизнес-процессы, а количество объектов, с которыми мы работаем, становилось все больше. 

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

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

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

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

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

  • нужно дополнительно проконтролировать наличие брака или точность поставки при приеме товара;

  • в определенной точке на сегодня определена специфическая стратегия размещения товара

  • и так далее.

Как видите, у нас бывает масса развлечений. Поэтому еще в прошлом году наша команда пришла к выводу, что для WMS в Леруа Мерлен нужна возможность вариативной настройки, или, другими словами, функционал Feature Toggle.

Идем в сторону Feature Toggle

Предпосылка нового проекта была очень проста и понятна бизнесу на 100%. В нашем случае проверять работоспособность обновлений и новых функций на проде стало не просто дорого, а очень дорого — стоимость простоя по мере распространения решения на новые и новые объекты стала действительно высокой. 

Для начала мы приступили к внедрению практики A/B-тестирования. В рамках этой концепции новые версии и добавленные фичи проверяют на себе сначала определенные группы пользователей, и, если возникают какие-то нюансы или требуется доработка релизов, мы можем быстро вернуть WMS в прежнее состояние. 

Но когда система продолжает расти, а специфика применения становится все более и более разветвленной, одной только концепции A/B-тестирования здесь мало. Для решения задачи развертывания мы стали искать инструментарий динамического управления фичами и файлами конфигураций в runtime — без перезапусков сервиса и дополнительных действий по обновлению инфраструктуры. То есть нам нужен был Feature Toggle — мощный инструмент, позволяющий команде изменять поведение системы без изменения кода (по определению Мартина Фаулера).

Есть разные способы реализации Feature Toggle и dynamic configuration management. И у каждого из них имеются свои преимущества и недостатки, с которыми мы познакомились на практике. 

На следующей схеме упрощенно показано, как все это работает.

Общее представление о работе с Feature Togge
Общее представление о работе с Feature Toggle

Эта простая схема показывает, что Feature Toggle срабатывает на основании одного boolean-параметра (Feature Toggle flag) или на основании логики обработки некой конфигурации (configuration server config management). Остается вопрос, как это реализовать на практике так, чтобы могли легко включать или выключать каждую фичу. 

Хардкодинг 

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

Указание значения Feature flag в коде
Указание значения Feature flag в коде

Использование переменной окружения

Также можно получать значения Feature Toggle из переменной окружения. А ее уже можно подтянуть из аргументов запуска, конфигурации k8s, vault secrets и так далее. При таком подходе мы можем уже не вмешиваться в код, а просто перезагрузить приложение, поменяв значение переменной окружения, и отключить новшество, если оно «не выстрелило».

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

Указание значения Feature flag через переменные окружения
Указание значения Feature flag через переменные окружения

Используем СУБД

Следующий вариант решения задачи — хранить настройки в СУБД. Это тоже удобно, однако обязывает организовывать приложениям подключение к СУБД. И если изменение логики происходит в рамках только одного приложения, то такое решение может вам подойти, так как оно обеспечивает быстрое применение параметров, во-первых, без перезапуска и, во-вторых, для всех инстансов разом. Однако, если приложений несколько, им всем придется ходить в одну и ту же базу либо дублировать наши тоглы в нескольких БД, что значительно увеличивает риски и сложность сопровождения решения.

Указание значения Feature flag в БД
Указание значения Feature flag в БД

Но… нам ничего не подошло!

Но, учитывая сложность нашей экосистемы, все это, увы, не подошло. И мы решили использовать cloud-based-решения. Их основное преимущество — единая точка хранения настроек и возможность динамической конфигурации приложения. Подобные решения представляют собой standalone-сервис, позволяющий хранить, изменять и получать Feature Toggles. 

Указание значения Feature flag через приложение
Указание значения Feature flag через приложение

Собрав запросы разных команд, мы сформировали следующие требования к cloud-native feature-toggle решению:

  • обязательно наличие тенантов для работы с несколькими независимыми иерархиями продуктовых сред, например, для глобальных регионов или независимых инфраструктурных площадок;

  • поддержка нескольких сред для каждого продукта;

  • хранение и версионирование configuration в формате json (а желательно и в других форматах, например yml или даже xml =) );

  • хранение, кеширование и возможность обновления features без перезапуска.

На первый взгляд под этот внушительный набор требований подходило сразу несколько готовых решений. Но при детальном погружении…

Unleash поначалу казался вот прямо подходящим решением, т. к. наличие feature-toggles, поддержка нескольких продуктов, работа в разных продуктовых средах для каждого продукта и даже возможность организации частичного вывода релизов на среды по расписанию закрывают большую часть продуктовых потребностей команд. К сожалению, unleash не смог стать для нас тем, чем нужно, т. к. он не поддерживает версионирование файлов конфигурации в виде remote-configuration-server и его лицензия оказалась недоступной для покупки.

Spring cloud configuration server — прекрасное решение для динамического управления конфигурациями, построенное над git-server. Но нам опять же не подошло, причем по целому ряду причин: 

  • отсутствие коннекторов для динамического потребления конфигурации на всех наших стеках, а именно Java/JS (основных) и Python/Golang (частично используемых);

  • отсутствие выраженных feature-toggles;

  • отсутствие UI-интерфейса, который был бы дружелюбен потребителям.

Flag4s — действительно замечательная библиотека для работы с состояниями feature-flags… но доступная только в экосистеме JVM. Это нас тоже не устраивало. 

Togglz обладает встроенным UI и даже реализует функционал feature groups. Но он поддерживает только один стек компании, и был отвергнут по аналогичным предыдущему причинам. 

Growthbook не поддерживает версионирование json-конфигураций и недоступен с точки зрения покупки лицензий. Но, что интересно, при реализации нашего решения именно growthbook стал для нас эталоном с точки зрения реализации базового ключевого функционала.

Хочешь сделать что-то хорошо…

После всех наших поисков было принято решение разработать свой InnerSource-продукт и развивать его исходя из наших потребностей. Расскажу о нем подробнее.

В модель данных для конфигураций мы включили следующие сущности.

  1. Tenant — сущность, определяющая изолированную инфраструктурную среду, например разные дата-центры.

  2. Product — продукт, группа сервисов.

  3. Environment — окружения, которые есть у продукта, например test, preprod, perf.

  4. Context — ключевое слово, определяющее принадлежность Feature.

  5. Feature — определенный функционал продукта.

  6. Settings — описание составной настройки.

Схема модели данных
Схема модели данных

Связи тенанта, продукта и окружения были реализованы как простые иерархические «от одного к многим». Фичи относятся к продукту тоже как «один к многим». А для определения состояния фичи мы добавили таблицу «Feature state». Она показывает связь с окружением и указывает на состояние. 

Связь объектов для состояния Feature
Связь объектов для состояния Feature

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

Пример с фичами
Пример с фичами

Но и этого оказалось мало, так как не весь функционал можно определить через ключевые слова или разделить на включено/выключено. В связи с этим мы добавили параметр «Settings», а также поддержку версионности для разных возможностей отката. Также появилась возможность указывать настройки в разных форматах, например в xml, json или другом удобном формате.

Пример настройки
Пример настройки

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

Пример работы с настройкой
Пример работы с настройкой

Что мы в итоге сделали сами

Не обнаружив подходящих доступных инструментов, мы написали сервис для хранения настроек и конфигурирования приложений, добавили к нему UI, чтобы пользователи могли нормально работать с нашим новым сервисом. Чтобы с системой было удобно работать извне, мы сделали открытый REST API для получения настроек. А чтобы снизить количество запросов к сервису конфигов, мы добавили в решение кеш с периодическим обновлением состояния.

В итоге мы разработали систему, которая дает нам:

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

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

  • Аккуратное внедрение или возможность A/B-тестирования на разных объектах с точечным отслеживанием влияния наших изменений и сбором обратной связи на небольшой группе пользователей.

  • Централизованное управление — мы создали единую точку работы с конфигурациями системы и обеспечивали контролируемое (и изменяемое) отставание от реального времени в пределах допустимого для нас интервала. Работа с этим интервалом позволяет нам прогнозировать применение изменений в нашей системе.

  • Невероятно страшный SSR-UI, тем не менее покрывающий наши потребности в администрировании конфигураций и фичей.

Таким образом, мы получили отличные результаты, но останавливаться на достигнутом не собираемся. Наша команда планирует и дальше развивать культуру использования Feature Toggle, привлекая другие команды компании. 

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

Я считаю, что главный эффект заключается в том, что команда стала продумывать плавные релизы и освоила подход zero downtime update. Возможность сбора обратной связи позволила нам быть ближе к конечным пользователям и выкатывать исправления каких-то компонентов быстрее, не затрагивая при этом большую часть наших пользователей. Кроме этого:

  1. QA-команда теперь может изменить поведение системы без необходимости привлекать DevOps или разработчиков прямо через UI.

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

  3. Команда разработки теперь может выкатывать новый функционал по мере готовности с возможностью включить и отключить его в любой момент.

  4. Наши аналитики вовсю пользуются обратной связью от пользователей объекта, на котором функционал развернули в тестовом режиме.

В любом случае впереди нас ждет еще много работы: мы планируем распространять решение по всей компании, выходя уже далеко за пределы нашего домена, а также собираемся доработать UI, добавить унифицированную в компании систему авторизации и разграничение доступа. Одной из главных задач также будет проработка blue-green deployment с использованием разных площадок, нескольких ЦОД. Следующим этапом мы также планируем добавить поддержку DSL для описания состояния features на основании входящих переменных и текущих параметров среды. И все это ждет нас в ближайшем будущем!

Расскажите, а вы пользуетесь чем-то вроде Feature Toggle? Какие инструменты для этого используете?

Комментарии (7)


  1. SGordon123
    22.08.2023 13:16

    МОжно про инетмаг вопрос, почему нет управления получением постомат / пвз?


  1. dx-77
    22.08.2023 13:16

    А чем не подошёл Firebase Remote Config ?


    1. dunyashin Автор
      22.08.2023 13:16
      +1

      Мы рассматривали те продукты, которые можем установить в своем окружении, а Firebase таковым не является. Плюс нет возможности разделения продуктов по тенантам


  1. zvlb
    22.08.2023 13:16
    +1

    Сорян за вопрос не в тему, но как вы делали такие прекрасные картинки?


    1. dunyashin Автор
      22.08.2023 13:16

      Спасибо, нам действительно очень повезло с дизайнером!


      1. redfox0
        22.08.2023 13:16

        Вот только картинка с подписью "Связь объектов для Feature" очевидно неверная. Но красивая, да :)


        1. dunyashin Автор
          22.08.2023 13:16

          Да, действительно есть ошибка. Большое спасибо за замечание, исправляем)