Эволюция архитектуры ДБО ФЛ
Привет, Хабр! Меня зовут Алексей Кутневич, я работаю ведущим архитектором в ЦК архитектуры БЦТ РСХБ.Цифра. В этой статье хочу поделиться реальными кейсами оптимизации микросервисной архитектуры из проекта «Свои финансы» — нашего нового банковского приложения. Покажу, как эти решения помогли существенно улучшить производительность системы.

Когда стартовал проект по созданию «Свои финансы», у нас была монолитная система в классической трехзвенной архитектуре в ее основе. Система работала, но мы понимали, что нужно переходить на микросервисы. Но не просто разбить монолит на части, а сделать это с фокусом на производительность для высоконагруженных сервисов. Каждый микросервис должен был работать быстрее, чем соответствующая часть монолита.
Помню, как в один из дней мы смотрели на метрики и понимали: что-то идёт не так. Время отклика росло, пользователи жаловались, а мы не знали, что делать. Монолит работал, но масштабироваться не мог. Еженедельные встречи по оптимизации монолита не приносили нужного эффекта в силу природы монолита.
Проблемы, которые мы предвидели:
Каскадные задержки от синхронных вызовов: каждый запрос пользователя мог превратиться в цепочку вызовов между микросервисами, где задержка каждого сервиса суммировалась.
Зависимость от внешних систем: наши микросервисы постоянно обращались к ПЦ, АБС, CDI и другим банковским системам.
Отсутствие кэширования: каждый запрос шел через интеграционную платформу, которая, кстати, также добавляла задержки во внешнюю систему.
Дублирование кода: разные микросервисы решали одни и те же задачи интеграции
Поэтому закладывали в архитектуру другие принципы.
Пять принципов оптимизации микросервисов
За время работы над проектом сформулировал для себя важные вещи, которые стали основой нашей оптимизации:
1. Данные должны быть рядом с пользователем.
Самое простое правило — если пользователь запрашивает баланс, он должен получить его мгновенно. Без вызовов к внешним системам, без ожидания. Мы начали хранить критичные данные прямо в микросервисах. Баланс, история операций, статус карты — всё это стало жить в памяти каждого сервиса.
2. Диск — это смерть для производительности.
Даже SSD добавляет миллисекунды. А нам нужны были десятки миллисекунд на весь запрос. Поэтому все критичное — только в памяти. Tarantool оказался самым подходящим решением, потому что подходил по параметрам импортозамещения, имел гибридную архитектуру и хорошо масштабировался. Но не диск.
Скептики скажут, что мы храним много избыточных данных, и к ним может не быть обращений. На что я скажу: это допустимый компромисс за те плюсы, которые мы получаем от данного решения.
3. События вместо запросов.
Вместо того чтобы постоянно спрашивать у внешних систем «Что нового?», мы научили системы самостоятельно рассказывать об изменениях. Это кардинально изменило архитектуру. Система стала устойчивой к сбоям и задержкам внешних систем. При этом и сами внешние системы сказали нам спасибо, так как избавились от постоянных ддосов в пиковую нагрузку.
4. Кэш — это источник истины.
Звучит парадоксально, но это работает. Мы кэшируем данные с опережением, а обновления приходят через события. В результате большинство запросов обслуживается из кэша, а не из основной системы.
5. Сначала события, потом все остальное
Любое изменение в системе публикуется как событие. Другие сервисы подписываются на то, что им нужно. Это обеспечивает слабую связанность и высокую масштабируемость
Стратегии оптимизации
Кейс 1. Инверсия потоков с внешними системами
В инверсии потоков с внешними системами проблемой стала зависимость и таймауты при синхронных вызовах во внешние системы. Чтобы решить задачу, мы стали использовать Push-модель вместо Pull.
Архитектурная схема решения:

Архитектура представляет собой переход от синхронной (Pull) модели к асинхронной (Push) модели взаимодействия с внешними системами. Система размещена на PaaS-платформе App.Farm и использует Apache Kafka как центральную шину событий и Tarantool в качестве высокопроизводительного кэша.
Основные компоненты:
● App.Farm (Платформа контейнеризации). Продуктовый микросервис «Свои Финансы» получает информацию из кэша Tarantool. Интеграционный компонент — управляет кэшем и обрабатывает события.
● Tarantool (Высокопроизводительный кэш). Хранит актуальную информацию по карточным транзакциям и продуктам клиента. Обеспечивает быстрый доступ к данным без обращения к внешним системам
● Apache Kafka (Брокер сообщений). Центральная шина событий для передачи изменений данных. Агрегирует события от всех внешних систем.
● Внешние системы:
ПЦ Alpha — платежный центр
АБС — автоматизированная банковская система
CDC DataFlot — отечественная система, реализующая механизм захвата изменений и передачи данных.
Как работают потоки данных? Внешние системы (ПЦ Alpha, АБС) записывают изменения в реляционную базу данных PostgreSQL. CDC DataFloat читает WAL (Write-Ahead Log) логи PostgreSQL и публикует события изменений в Apache Kafka. Интеграционный компонент подписывается на события в Kafka и обновляет кэш Tarantool. Продуктовый микросервис получает данные из кэша без внешних вызовов.
Как реализуется оптимизация? Apache Kafka используется как центральная шина событий для потоковой передачи данных. CDC (Change Data Capture) отслеживает изменения и синхронизирует данные через DataFlot.
Кейс 2. Пилот PostgreSQL TCS — Сравнение производительности с Tarantool Column Store
Проблемой этого кейса стала необходимость подтверждения правильности выбора Tarantool для системы истории операций и сравнение его производительности с альтернативными решениями. Чтобы оптимизировать задачу, мы проводили пилот по сравнению производительности PostgreSQL (PG) и Tarantool Column Store (TCS) на реальных данных операций РСХБ для валидации архитектурного решения. Пилот был реализован совместно с командой VK Cloud.
Условия проведения пилота:
Инфраструктура: виртуальная машина в облаке VK (16 CPU, 64 GB RAM)
Объем данных: ~33 млн записей операций, распределенных по 100 дням д��я 110 тыс. пользователей
Сценарии тестирования: только чтение, запись + чтение (500 RPS на запись)
Тестируемые продукты: PostgreSQL (OSS) и Tarantool Column Store.
Ключевые запросы для тестирования:
Запрос 1: Анализ расходов и доходов по категориям
-- Вариант "а": только client_id, без фильтра по дате select category, direction, sum(amount) as total_by_category from operations where client_id = '{0}' group by category, direction; |
-- Варианты "б" и "в": с фильтром по дате (15 и 30 дней) select category, direction, sum(amount) as total_by_category from operations where operation_date_time > ('{1}'::timestamp - '15 days'::interval) and client_id = '{0}' group by category, direction; |
Результаты тестирования:
1. Запрос 1 (анализ по категориям):
● TCS без нагрузки: 2529 RPS, средняя задержка 11.71ms
● TCS с нагрузкой на запись: 2281 RPS, средняя задержка 12.99ms
● PG без кеша: 43 RPS, средняя задержка 693ms
● PG с кешем 20Гб: 1240 RPS без нагрузки, 75 RPS с нагрузкой на запись
2. Запрос 1 с BRIN индексом по дате:
● TCS без BRIN: 1963 RPS под нагрузкой
● TCS с BRIN: 2649 RPS под нагрузкой (ускорение в 1.35 раза)
3. Запрос 2 (последние операции):
● TCS: 3873 RPS под нагрузкой на запись
● PG: от 41 до 2814 RPS в зависимости от настроек кеша и индексов
Результаты пилота подтвердили правильность выбора TCS для системы истории операций:. TCS показывает стабильную производительность независимо от нагрузки на запись. PG требует тщательной настройки кеша и индексов для достижения производительности TCS: производительность PG сильно зависит от настроек кеша и наличия индексов. При нагрузке на запись PG теряет эффективность кеша, так как последние данные постоянно изменяются. TCS обеспечивает предсказуемую производительность в различных сценариях нагрузки, так как имеет настройки для изоляции нагрузок на запись и чтение по ресурсам. BRIN индексы значительно ускоряют TCS для запросов с фильтрацией по дате.
Кейс 3: Система истории операций на платформе APP.FARM с Tarantool
В этом кейсе нам было нужно централизованно агрегировать операции и получать быстрый доступ к истории операций с минимальной задержкой. Мы решили создать централизованную систему истории операций на основе Event-Driven архитектуры с использованием Apache Kafka и Tarantool для высокопроизводительного кэширования.
Архитектурная схема системы:

1. Микрофронт «История операций» — пользовательский интерфейс. Загружает распределенную статику и изображения. Взаимодействует с бэкендом через REST API.
2. МС История операций — центральный агрегатор данных. Потребляет события от всех микросервисов-источников. Обрабатывает и агрегирует операционные данные. Публикует обработанную информацию обратно в Kafka
3. Платформенная Kafka — центральная шина событий. Обеспечивает асинхронное взаимодействие между сервисами. Гарантирует доставку событий и отказоустойчивость.
4. Микросервисы-источники генерируют события операций МС Переводы СБП, МС Классические переводы, МС Обмен валют, МС Цифровой рубль, МС Платежи, МС Инвестиционные продукты, МС Брокерское обслуживание, МС Управление кредитами.
5. Микросервисы-потребители получают обработанные данные МС Клиентский профиль, МС Счета, МС Карты, МС Системный журнал, МС Ролевая модель, МС Справочники, МС Фичи Тогл, Рекомендательная платформа.
В потоках данных микросервисы-источники генерируют события операций, МС История операций потребляет и обрабатывает все события, Микросервисы-потребители получают нужные им данные, Микрофронт взаимодействует с МС История операций синхронно.
Почему выбрали Tarantool и как провели пилот? Изначально в системе Истории операций мы выбрали Tarantool как основное решение для высокопроизводительного кэширования. Для подтверждения правильности этого выбора и сравнения с альтернативами провели пилот PostgreSQL TCS (кейс 2), который показал, что TCS (Tarantool Column Store) обеспечивает стабильную производительность 3873 RPS под нагрузкой, PostgreSQL показывает производительность от 41 до 2814 RPS в зависимости от настроек. Результаты пилота подтвердили правильность первоначального выбора Tarantool для системы истории операций
Кейс 4. Единый канал доставки событий с бэкенда до UI через SSE + HTTP/2
В этом кейсе мы решали вопрос большого количества запросов на бэкенд с фронтенда, при этом большая их часть возвращала нулевой результат, то есть работала вхолостую. Чтобы решить задачу, мы использовали единый канал доставки сообщений с сервера на фронтенд для всех микросервисов.

Cерверная часть системы «Свои финансы» самостоятельно в режиме Server-Push отправляет события на фронт по мере их поступления через протокол SSE. Отправка предполагается только активным пользователям с контролем потери соединения на стороне бэка.
Кейс 5: Централизация интеграций через платформенные микросервисы
У нас дублировался код интеграций в каждом продуктовом микросервисе. Каждый сервис самостоятельно интегрировался с внешними системами, что приводило к дублированию логики, сложностям в поддержке и несогласованности данных. Мы решили разделить микросервисы на платформенные и продуктовые с централизованной интеграцией.
Архитектурная схема на примере платформенного сервиса клиентский профиль

Платформенные микросервисы отвечают за интеграцию с внешними системами (ПЦ, АБС, CDI и др), публикуют события в Kafka для продуктовых микросервисов, централизуют всю логику интеграций, обеспечивают единообразие обработки данных.
Продуктовые микросервисы получают готовые данные через события от платформенных сервисов, фокусируются на бизнес-логике, а не на интеграциях, не содержат кода интеграций с внешними системами.
Потоки данных распределили следующим образом: платформенный микросервис получает данные от внешней системы, обрабатывает и нормализует данные, публикует событие в Kafka. Продуктовые микросервисы подписываются на нужные события, получают готовые, обработанные данные.
Мы смогли устранить дублирование кода интеграций, централизованно обрабатывать ошибки и retry-логики, единообразно хранить данные во всех продуктовых сервисах. Также это помогло упростить поддержку и развитие интеграций. Все взаимодействия между микросервисами и внешними системами построены на событиях. Это обеспечивает слабую связанность, масштабируемость и отказоустойчивость.
Уроки оптимизации
Помню, как в начале проекта мы думали: «Ну что тут сложного, просто разобьем монолит на сервисы, и все будет работать быстрее». Оказалось, что это только начало истории.
Что мы поняли про оптимизацию микросервисов? Микросервисы — это не про технологии, а про организацию. Структура команды определяет архитектуру системы. Если команды работают по доменам, то и система будет разбита по доменам.
Инфраструктура — это must-have. Без надежной платформы микросервисы превращаются в распределенный монолит. App.Farm позволил нам сосредоточиться на бизнес-логике, а не на инфраструктуре.
Сначала измеряйте, потом оптимизируйте. Мы начинали каждую оптимизацию с понимания того, что нужно улучшить. Без понимания проблем невозможно найти правильные решения. В этом сильно помог опыт оптимизации монолита: мы уже знали, где искать узкие места.
Поэтапное внедрение снижает риски. Мы не переписывали все сразу. Начинали с нескольких критичных сервисов, измеряли результаты, затем масштабировали подход.
Нет серебряной пули. Успех зависит не от модных технологий, а от правильного применения принципов. Архитектура — это живой организм, который требует постоянного внимания. Она постоянно меняется, развивается, адаптируется под новые требования. И это нормально.
