Раньше наш способ собрать данные из всех микросервисов в одно место (в витрину) напоминал копролит (только не древний, а наш собственный ИТ-артефакт). Он был сложный, медленный, постоянно ломался и требовал много ручной работы. Данные размазывались по куче баз данных. Чтобы сделать отчёт или отправить данные во внешнюю систему, надо было собирать их вместе. 

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

Мы тратили время на латание дыр, а не на разработку. В какой-то момент решили, что пора выкинуть этот велосипед и внедрить нормальный, промышленный подход к работе с данными — Data Lakehouse с медальонной архитектурой.

В чём были наши ошибки:

  • Спроектировали сложную доменную модель для витрины, которая не соответствовала ни моделям в исходных сервисах, ни требованиям пользователей. Одна таблица из микросервиса могла раскладываться на 5 таблиц в витрине.

  • Превращали данные из сервисов в эту модель — это был ад.

  • Схема данных на фронте, в сервисе, в витрине и у потребителя была везде разная. Это постоянные баги из-за того, что кто-то ждёт ID, а ему приходит business_number.

  • Чтобы отдать данные потребителю, приходилось делать кучу JOIN-ов. Это ломало SLA по производительности.

  • Любое изменение в схеме БД микросервиса (добавил колонку) требовало сложной доработки витрины. Всё тесно связано и хрупко.

  • Сервисы писали изменения в свои локальные outbox-таблицы, а отдельный обработчик забирал их и складывал в витрину. Данные из разных сервисов приходили в разное время. Запрос к витрине мог пытаться сджойнить данные о клиенте и его заказе, но данные по заказу ещё не приехали. В итоге потребителю уходило неполное или вообще никакое сообщение.

  • Разработчики сервисов вместо одного события «Заказ сохранён» генерировали 20 событий на каждое изменение поля в UI. Это забивало очередь и создавало дикую нагрузку.

  • Тестирование — тоже ад.

Для таких ситуаций есть понятный шаблон — трёхслойная архитектура с понятным дата-пайплайном.

Почему прошлая схема зашла в тупик

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

Эта модель не совпадала ни с базами данных исходных микросервисов, ни с тем, что хотят видеть конечные пользователи в отчётах. В итоге одна простая таблица из сервиса-источника при переезде в витрину разлеталась на пять разных таблиц. Инженерам приходилось городить сложную логику, чтобы просто разложить данные по местам.

Постоянная головная боль — путаница с названиями полей. Фронтенд называет поле одним именем, микросервис другим, витрина третьим, а потребитель ждёт четвёртое. В итоге вылезали ошибки, когда потребитель ожидает технический ID, а получает business_number. Каждое такое расхождение приходилось вылавливать и править руками.

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

Технически это работало так: микросервис записывает изменения в свою локальную outbox-таблицу. Специальный обработчик постоянно опрашивает эти таблицы и перекладывает данные в витрину. Из-за того что сервисы независимы, данные доезжают в разное время. Когда витрина пытается собрать сообщение для внешней системы через джойн (например, склеить «Клиента» и его «Заказ»), часто оказывается, что клиент уже приехал, а заказ ещё в пути. В итоге потребитель не получал нужную ему информацию. 

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

Переход к Data Lakehouse и медальонной архитектуре

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

 В первом слое (бронзовый) данные сохраняются ровно в том виде, в котором они лежат в микросервисах. Никто ничего не переименовывает и не трансформирует. Вместо того чтобы постоянно дёргать базы данных через Polling, данные залетают через Kafka. Это намного быстрее и надёжнее. 

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

Второй слой — серебряный. Здесь данные чистят и приводят к порядку. Тут как раз решается проблема с именованием: все ID и business_numbers приводятся к единому стандарту. Данные всё еще детальные, но уже понятные всей системе. Серебряный слой используют дата-аналитики для глубоких исследований и дата-сайентисты для построения моделей. 

Ну и третий золотой слой — это финальные витрины под конкретные задачи. Главная фишка в том, что структура каждой витрины теперь полностью совпадает с тем, что хочет видеть конкретный потребитель. Данные здесь лежат в денормализованном виде (плоские таблицы). Больше не нужно делать тяжёлые джойны, когда кто-то запрашивает отчёт, поэтому всё работает быстро и укладывается в SLA.

Как это решает проблему консистентности

Главное изменение — логика отправки данных. Теперь витрина не пытается собрать сообщение на лету из того, что успело доехать. Сначала всё накапливается в Data Lake. Только когда система видит, что приехал полный и согласованный набор данных от всех нужных микросервисов, она формирует итоговое сообщение.

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

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

Что получили на выходе и почему решение нам так нравится

Когда перестали латать старый велосипед и внедрили новое решение, числа сразу пошли вверх. Сейчас система обслуживает больше 3000 табельных номеров и работает стабильно.

Ниже новая схема архитектуры. Изменения на ней, по сравнению с прошлой версией, выделены зелёным.

Что нового и почему это круто:

  1. Внедрили Kafka для загрузки данных на основании outbox-таблиц из сервисов в витрину и далее для отправки из витрины на шину (маршрутизатор) к конечным потребителям.

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

  3. Ушли от доменной модели в витрине и сделали её под себя, без нормализации и лишних таблиц.

  4. Добавили компонент настройки потоков, который позволяет:

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

    o Делать настраиваемую параллельную обработку потоков для потребителей и задавать очерёдность отправки сообщений, где это требуется потребителю.

    o Контролировать отправку сообщений и ставить отправку на паузу, если потребитель временно недоступен и не принимает сообщения.

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

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

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