Url
https://nsign.ru/blog/rezhem-monolit-po-zhivomu-istoriya-uskoreniya-odnogo-khoroshego-servisa
Name
Режем монолит по-живому или история ускорения одного хорошего сервиса
Blog

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

 

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

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

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

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

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

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

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

 

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

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

 

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

 

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

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

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

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

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

 

Предложение

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

 

Рис. 2. Внешний вид навигатора как отдельного сервиса на своем поддомене.

 

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

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

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

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

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

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

 

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

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

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

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

  • не потребовались дополнительные серверы СУБД (а с ними настройка бэкапов и репликации);
  • не появилось новых точек отказа;
  • плюс безопасность — нет локальной БД на публичном сервисе, значит, неоткуда случиться прямой утечке.

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

  1. администраторы портала, как и раньше, правят информацию в основной админке;
  2. ключевое архитектурное изменение — мы создали личные кабинеты для каждой из 300+ подведомственных организаций (рис. 3).

 

(А)

 

(Б)

Рис. 3. Внешний вид страницы подведомственной организации (А) и ее личный кабинет (Б)

 

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

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

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

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

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

 

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

 

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

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

 

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

 

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

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

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

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

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

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

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