Режем монолит по-живому или история ускорения одного хорошего сервиса

от автора

Привет, Хабр. Меня зовут Алексей Постригайло. Двадцать с лишним лет я занимаюсь системной интеграцией и управлением проектами, сейчас — старший партнер одного крупного ИТ-интегратора. Здесь я рассказываю о технических и организационных подробностях наших проектов. 

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

Предыстория: непростая ситуация. 

Наш заказчик —  Департамент труда и социальной защиты населения Москвы (ДТСЗН), масштабная и живая структура. Его портал обслуживал две совершенно разные вселенные: 

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

  • с другой — сотрудники более 300 подведомственных учреждений, для которых портал служил ежедневным рабочим инструментом. 

Проблема назревала постепенно. Одним из ключевых сервисов для горожан был «Социальный навигатор» — интерактивная карта, встроенная в основной портал. К 2021 году на карте было отмечено свыше 935 адресов по 225 различных услуг, а общее число обращений к нему перевалило за 1,3 миллиона. Мы понимали, что рост нагрузки — дело времени, но настоящий вызов пришел с другой стороны. 

На портале кипела жизнь: релизы требовались в среднем раз в три дня, а срочные правки с дедлайном «еще вчера» — и того чаще. И вот руководство заказчика ставит задачу: срочно доработать «Социальный навигатор», добавив к нему крупную фичу А для презентации к несдвигаемой дате в Департаменте. Мы успевали, но был нюанс. 

Параллельно в том же монолитном проекте мы уже вели разработку другой, не менее масштабной фичи — подсистемы отчетности для тех самых 300+ организаций (фича Б). Код естественным образом смешался в общей dev-среде. 

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

Боль управления зависимостями

Чтобы было понятнее, в чем суть затыка (см. рис. 1), поясню чуть глубже архитектуру. Точками соприкосновения «Навигатора» (фича А) и системы отчетов (фича Б) были общие сущности в базе данных: «подведомственные организации», «НКО», «бизнес», «услуги», а также «округА» и «районы». Сущность «подведомственная организация» была особенно объемной и тесно связанной с «услугами» — ключевой для «Навигатора». Эти «лёгкие» правки мы и не могли перенести в прод без незавершенных структурных изменений по отчетам. 

Рис. 1. Схема противоречий. 

Рис. 1. Схема противоречий. 

Ситуацию усугубляли два фактора. 

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

Во-вторых, изменения были не косметическими, а архитектурными, затрагивающими таблицы БД. Изолировать их простым feature toggle было невозможно. 

Оставался скользкий путь, знакомый каждому, кто работал с legacy-монолитами: ручные cherry-pick’и — то есть выборочные переносы коммитов из одной ветки в другую. Но чем больше таких манипуляций, тем выше энтропия и риск уронить прод. На практике попытки таких переноса регулярно вызывали конфликты в общих CSS и JS-файлах, «уезжала» верстка, появлялись трудноуловимые баги на фронтенде. Учитывая масштаб подсистемы отчетности — с объемом рисков был явный перебор. 

Техническая проблема стремительно и ожидаемо переросла в коммуникационную: «Почему не выкатываете обновления? Мы заждались!». 

Предложение

И вот в один прекрасный летний день мы пришли к заказчику с радикальным, но,  на наш взгляд, неизбежным предложением: резать монолит «по-живому», а именно выделить «Социальный навигатор» в полностью независимый сервис на отдельном поддомене: https://nav.dszn.ru (см. рис. 2). 

Рис. 2. Внешний вид навигатора как отдельного сервиса на своем поддомене (А. услуги и Б. организации). 

Рис. 2. Внешний вид навигатора как отдельного сервиса на своем поддомене (А. услуги и Б. организации). 

Аргументы были просты и логичны: 

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

  • Это развяжет руки всем: 

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

    • срочные доработки перестанут «томиться» в ожидании «окна». 

  • Кроме того, анализ метрик, в том числе из  Яндекс.Вебмастера, показывал, что интерактивная карта сильно нагружала и без того тяжелый «монолит». Её вынос ее должен был стабилизировать работу основного портала. 

Уговаривать не пришлось — нам дали «добро». 

На реализацию ушло около 3 месяцев.

Технические компромиссы и бонусные решения

Выделить сервис — полдела. Надо было не создать на месте одной проблемы две новых. И мы пошли на некоторые технические компромиссы. 

Первый и самый принципиальный выглядел еретически: мы не стали заводить отдельную БД. Зачем плодить сущности? Навигатору не нужно состояние — ему достаточно получать свежие данные по запросу. База осталась на основном портале. 

Это решение одним махом сняло с нас пласт проблем:  

  • не потребовались дополнительные серверы СУБД (а с ними настройка бэкапов и репликации); 

  • не появилось новых точек отказа; 

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

Данные в «Навигатор» поступают через API в формате JSON, по запросу (вручную, по кнопке в админке) или по расписанию (cron). Но источников данных стало два:

  1. администраторы портала, как и раньше, правят информацию в основной админке; 

  2. ключевое архитектурное изменение — мы создали личные кабинеты для каждой из 300+ подведомственных организаций.  

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

Иными словами, вся система теперь завязалась на трех ключевых приложениях (см. рис. 3):  

  • панели администрирования на основном портале;

  • личные кабинеты для подведомственных организаций;

  • сам «Навигатора». 

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

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

Рис. 3. Три получившихся ключевых приложения системы.

Рис. 3. Три получившихся ключевых приложения системы.

Поскольку JSON-файлы объемны, их «наивная» обработка съедала бы много ресурсов. Поэтому мы задали легкий PHP-пакет, реализующий паттерн «Коллекция» (аналог Illuminate/Collections из Laravel). Вместо  «прожорливых» циклов foreach разработчики получили возможность использовать декларативный код с цепочками методов map(), filter(), groupBy(), плюс «ленивые» вычисления дополнительно экономили память и процессорное время. 

«Вишенка на торте» — помимо основного сервиса, мы сделали его облегченную версию в виде виджета (рис. 4). Это интерактивная карта в iframe, которую можно встроить куда угодно — мы встроили на главную страницу основного портала. Весь интерактив работает внутри фрейма, без всяких авторизаций и сложностей с CORS. 

Рис. 4. Виджет навигатора на главной странице портала 

Рис. 4. Виджет навигатора на главной странице портала 

Результаты и выводы

Что мы получили в сухом остатке?

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

Во-вторых, разработка стала безопаснее. Мы ушли от практики рисков с cherry-pick’ами, которая изрядно нервировала команду. 

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

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

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

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

ссылка на оригинал статьи https://habr.com/ru/articles/1026414/