Как перевести банковский продукт в realtime
Опыт построения real-time системы предодобренных предложений в банковском enterprise. Apache Flink, Tarantool, Kafka — и уроки, которые не найдёшь в документации.
Среда, 2:47 ночи. Дашборд горит красным.
Весна 2024-го. Мы прошли функциональное и интеграционное тестирование, выходим на прод — и тут начинается настоящее нагрузочное, только уже на боевом трафике. Tarantool начинает терять запросы на двадцатой минуте под реальной нагрузкой. Не сразу — сначала один таймаут из тысячи, потом два, потом десять. Красивая экспонента на графике, которая через полчаса превратит систему в тыкву.
Я сижу перед монитором на ночном созвоне с двумя разработчиками, третья за ночь чашка кофе остывает на столе. В голове крутится одна мысль: через шесть недель — демо бизнес-заказчику. И мы только что обнаружили, что фундамент нашей архитектуры трещит.
Но это будет потом. Сначала — о том, зачем вообще ломать то, что работало.
Почему batch — это вчера
Банковская система предодобренных предложений работала в batch-режиме годами. Ночью прогоняются модели, утром формируются списки, днём клиенты получают SMS. Конверсия — доли процента. К моменту, когда человек видит предложение, он уже давно забыл, зачем заходил в приложение.
Задача звучала просто: клиент совершает действие — банк реагирует за секунду. Не завтра, не через час. Сейчас.
Вот как выглядит разница на бумаге:
| Параметр | Batch | Real-time |
|---|---|---|
| Латентность | 12-24 часа | < 1 секунды |
| Свежесть данных | Вчерашний снимок | Текущий момент |
| Стоимость инфраструктуры | Ниже (ночные окна) | Выше (24/7 кластеры) |
| Сложность архитектуры | Линейная (ETL-пайплайн) | Экспоненциальная (event mesh) |
| Обработка ошибок | Перезапуск батча | Dead Letter Queue, ретраи, алерты |
| Масштабирование | Вертикальное (мощнее сервер) | Горизонтальное (больше нод) |
| Отладка проблем | Логи одного прогона | Распределённый трейсинг |
На бумаге — таблица. На практике — полная перестройка архитектуры, данных, процессов и мышления команды. Люди, которые пять лет писали батчевые джобы, теперь должны думать в терминах событий, окон и watermark-ов. Это не обучение технологии — это перепрошивка мозга.
Архитектурные решения: почему именно этот стек
Flink, а не Spark Streaming
Выбор стримингового движка — решение, которое будет стоить тебе нервов следующие три года. На столе были Apache Spark Streaming, Kafka Streams и Apache Flink.
Spark Streaming отпал за минуту — micro-batch с задержкой в секунды не вписывался в SLA. Kafka Streams рассматривали серьёзно: он легче, встроен в экосистему, не требует отдельного кластера. Но нам нужен был stateful processing с оконными функциями и сложной логикой обогащения. У Kafka Streams на тот момент с этим было туго.
Flink выиграл по трём критериям. Первый — нативная поддержка event time. Когда события приходят с задержкой (а из банковских систем они приходят с задержкой всегда), это не просто nice-to-have — это разница между правильным и неправильным решением по клиенту. Второй — exactly-once семантика из коробки. В банковском контексте потерянное событие — не метрика в дашборде, а потенциальная жалоба клиента и внимание регулятора. Третий — зрелый checkpoint/savepoint для fault tolerance. Система должна пережить падение ноды без потери состояния.
Цена: на российском рынке специалистов по Flink можно буквально пересчитать по пальцам. Рекрутинг превращается не в воронку, а в охоту.
Tarantool: скорость и предательство
Каждое событие нужно обогатить: профиль клиента, история операций, текущие лимиты, параметры продуктов. Классический путь — реляционная база. Но когда тысячи событий в секунду и SLA на обработку — десятки миллисекунд, PostgreSQL становится бутылочным горлышком.
Tarantool решил эту проблему красиво: in-memory хранилище с latency менее миллисекунды. Данные из основных систем реплицируются с минимальной задержкой, Flink-джобы обращаются за обогащением.
А потом наступила та ночь.
Баг проявлялся только под нагрузкой, близкой к production. В тестовой среде — идеально. На половине боевого трафика — идеально. На семидесяти процентах — спорадические таймауты. На целевых объёмах — деградация, которая за полчаса превращала систему в овощ.
Первая неделя дебага — ложные следы. Мы грешили на сеть, на JVM, на конфигурацию Flink. Вторая неделя — изоляция проблемы до конкретного паттерна конкурентных запросов в Tarantool. Конкретный сценарий: когда параллельные запросы на чтение и запись попадали в одну и ту же область данных с определённой частотой, in-memory движок начинал деградировать.
Помню совещание, где мы решали, что делать. Два варианта на столе: ждать фикса от сообщества (неопределённо долго) или экстренный пивот части архитектуры. Времени не было. Мы задокументировали баг, сообщили сообществу и за выходные переписали критический путь на связку PostgreSQL + кэширование.
Latency выросла с микросекунд до миллисекунд. Архитектура стала грязнее. Но стабильной. В банке предсказуемость важнее элегантности.
Kafka как нервная система
Apache Kafka — транспортный слой всей системы. Каждый микросервис публикует и читает события через топики. Три вещи, которые решает Kafka: развязка сервисов (каждый знает только про свои топики), replay (можно перечитать события для дебага) и масштабирование через партиционирование.
Kafka, кстати, была единственной технологией в стеке, которая ни разу не преподнесла сюрприза. Работает как швейцарские часы, если не лезть в настройки retention слишком часто.
Четыре языка в одном проекте — осознанный хаос
Java, Scala, Lua, SQL. Звучит как плохая шутка. Но за каждым выбором — прагматизм, не эстетика.
Java — основа микросервисов. Spring Boot, огромный пул разработчиков, зрелые библиотеки для Kafka.
Scala — Flink-джобы. Flink API на Scala выразительнее на порядок. Писать оконные агрегации на Scala — удовольствие. На Java — наказание.
Lua — хранимые процедуры в Tarantool. LuaJIT как встроенный язык — для сложной логики обогащения единственный вариант.
SQL — аналитические запросы и конфигурация правил в движке решений. Бизнес-пользователи модифицируют правила без разработчиков.
Цена: найти разработчика, который свободно читает Java и Scala — уже квест. А Lua-разработчики для Tarantool — штучный товар, который на открытом рынке не водится. Мы вырастили своих. Других вариантов не было.
Event-driven: красиво в теории, больно на практике
Event Sourcing для критических бизнес-событий. Каждое изменение — событие. Полная история, возможность воспроизвести любое состояние на любой момент. Must have для банковского аудита, который приходит в самый неподходящий момент и спрашивает, что именно произошло с клиентом 17 февраля в 14:23.
CQRS для разделения потоков. Запись через event pipeline, чтение из проекций в Tarantool и PostgreSQL. Независимое масштабирование нагрузки.
Dead Letter Queue — наш лучший друг в первые недели после запуска. Событие, которое не удалось обработать, не теряется — попадает в отдельный топик для анализа. В первую неделю на проде DLQ разбухла до размеров, которые мы не закладывали. Оказалось, legacy-системы присылали события в формате, который не соответствовал спецификации. Не иногда — в пяти процентах случаев. Пять процентов от миллионов событий — это много мусора в DLQ.
Главный подводный камень — eventual consistency. В event-driven системе нет гарантии, что все сервисы видят одинаковое состояние одновременно. Для банковских продуктов это страшно: нельзя предложить кредит, если данные о текущей задолженности клиента ещё не добрались до scoring-сервиса. Решение — явное управление порядком через timestamp и watermark во Flink. Работает, но добавляет слой сложности, который нужно объяснять каждому новому разработчику.
Провалы, которые нас научили
Провал первый: синтетические данные врут. Мы три месяца тестировали на синтетике и были уверены в системе. Первый день на реальных данных — каскад ошибок. Legacy-системы присылали даты в четырёх разных форматах. Поля, которые по спецификации «обязательные», приходили пустыми. Идентификаторы, которые должны быть уникальными, дублировались. Тестовая среда — это карта, но территория выглядит иначе.
Провал второй: мониторинг запустили поздно. Первые два месяца мы строили бизнес-логику. Мониторинг «потом допилим». В итоге, когда начались проблемы, мы гадали на кофейной гуще. После этого я поставил правило: ни один сервис не уходит в ревью без метрик, трейсинга и алертов. Мониторинг — не вишенка на торте, а фундамент.
Провал третий: canary deployment мы открыли для себя слишком поздно. Первые релизы катили на весь трафик. Один из них уронил конверсию на тридцать процентов — баг в логике скоринга, который проявлялся только на определённом сегменте клиентов. Четыре часа на обнаружение, час на откат. После этого — только канарейка: сначала один процент трафика, потом десять, потом пятьдесят.
Провал четвёртый: мы недооценили backpressure. Утро понедельника, пиковая нагрузка. Upstream-система выгружает накопившиеся за выходные события пачкой. Kafka справляется — она всегда справляется. Flink начинает захлёбываться: checkpoint-ы растут, latency ползёт вверх, и через двадцать минут consumer lag разрастается до миллионов сообщений. Мы не заложили механизм backpressure, который корректно замедлял бы потребление при перегрузке. Пришлось вручную останавливать джобы, делать savepoint и рестартовать с ограничением скорости чтения. После этого инцидента backpressure-стратегия стала частью архитектурного ревью каждого нового Flink-джоба.
Что осталось после проекта
Архитектурные решения в enterprise — всегда компромисс между идеальным и возможным. Идеальная система существует в конференционных докладах. Реальная — это баланс между производительностью, надёжностью, стоимостью поддержки и скоростью вывода на рынок.
Технологии — десять процентов проблемы. Flink, Kafka, Tarantool работают. Девяносто процентов — координация между командами, согласование API-контрактов, интеграция с legacy и управление ожиданиями бизнеса, который хочет «как у Google, но за три месяца».
Технический долг в realtime-системе — не абстрактная метрика. Каждая задержка в обработке события влияет на бизнес-метрики прямо сейчас, в реальном времени. Мы выделяли двадцать процентов спринта на техдолг. Этого было мало.
Конкретный результат, ради которого всё затевалось: время от действия клиента до персонализированного предложения сократилось с 12-24 часов до 800 миллисекунд на 95-м перцентиле. Конверсия в целевое действие выросла кратно по сравнению с batch-режимом. Не буду называть точные цифры — NDA, — но разница была достаточной, чтобы бизнес-заказчик, который полгода скептически спрашивал «а зачем нам realtime?», после первого месяца в проде пришёл с запросом на расширение на ещё четыре продуктовых сценария. Лучшее доказательство, что архитектура работает, — когда бизнес просит ещё.
А ещё — production пахнет иначе, чем стенд. Аномалии, пики, битые данные из систем, которые последний раз обновлялись при Медведеве. Всё это проявляется только в бою. И если ты к этому не готов — бой проявится в тебе.
Оригинал статьи: Как перевести банковский продукт в realtime на Habr
По мотивам доклада на Saint HighLoad++ 2024, Санкт-Петербург.
Ваш ДПУПП
Подписка на обновления
Новые статьи, доклады и проекты — без спама, только по делу.
Без спама. Отписка в один клик.
Похожие статьи
Разработка в финтех: 7 кругов ада
Честный гайд по тому, что ждёт команду при выводе продукта на прод в крупном банке. От найма до поддержки — каждый этап как отдельный вызов.
Saint HighLoad++ 2024: Опыт перевода банковского продукта в реалтайм
Доклад на Saint HighLoad++ 2024 — как мы строили real-time триггерную систему для банковских клиентов на Apache Flink, Tarantool и Kafka, и что пошло не по плану.
Analyst Days 14 / Импульс 2022: Не крась траву — риски и ценность данных в BANI мире
Доклад на Analyst Days 14 и Импульс 2022 — почему данные без управления превращаются в бомбу замедленного действия, и как DAMA, ISO 27000 и здравый смысл помогают этого избежать.