Финтех-приложения, как правило, одни из самых сложных и высоконагруженных — ежедневно они обрабатывают сотни тысяч операций. При этом критична их устойчивость, потому что любой сбой может мгновенно вызвать негативную реакцию пользователей, потерю их лояльности и ухудшение имиджа компании. Иван Акимов, Software Engineer в Яндекс, рассказал о том, какими принципами он руководствовался при построении высоконагруженной системы Яндекс Сплит, и к каким результатам привела его работа.

Иван Акимов

Иван, вы специализируетесь на построении высоконадежных распределенных систем. Как вы пришли к этой области, и какие ключевые навыки оказались наиболее важными для работы с финансовыми продуктами уровня Yandex Pay и BNPL-сервисов?

С написанием кода и алгоритмами я познакомился на олимпиадном программировании, во время учебы в физико-математическом лицее. Эта сфера заинтересовала меня, и после лицея я поступил в ИТМО на специальность «Компьютерные системы и технологии». Там я начал пробовать себя в разных областях программирования, и самым интересным для меня оказался backend.

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

После поступления в университет и обучения там я три года совершенствовал свои навыки, работая с различными компаниями, в том числе, и в банковской сфере. Среди моих клиентов были зарубежные корпорации из США и Европы, и у них я научился многим практикам, которые затем использовал в своей работе.

После этого меня пригласили в команду Yandex Pay, где я занялся построением микросервисной архитектуры для BNPL-продукта Сплит. Работая над проектом, я использовал экспертизу в многопоточном программировании, распределенных логах и базах данных. Также пригодилось мое внимание к деталям — ведь ошибка в финансовых операциях может стоить компании очень дорого.

Как выглядит архитектура отказоустойчивых микросервисов в Yandex Pay/BNPL? Какие принципы стали наиболее критичными для обеспечения аптайма и надежности?

Для обеспечения отказоустойчивости я использовал распределенную архитектуру. При этом, каждый stateless-сервис решал свою бизнес-задачу и масштабировался в зависимости от нагрузки, а в каждом stateful-сервисе (в базах данных, распределенных логах, очередях) я применил принципы идемпотентности и семантики exactly once.

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

Я знаю, что вы запускали систему параллельной отправки кредитных событий в БКИ — один из критически важных процессов для соблюдения банковской лицензии. С какими архитектурными ограничениями вы столкнулись и почему стандартные решения не подходили для обработки ~500k событий ежедневно?

Да, эта система стала одним из важнейших элементов продукта Сплит, так как без нее функционирование BNPL-сервиса было бы невозможным. Дело в том, что для разбивки платежа более чем на 4 части необходима банковская лицензия — она подразумевает выполнение определенных требований. Одно из них — ежедневная отправка кредитных событий в БКИ. За несоблюдение требований компанию ждет штраф до нескольких миллионов рублей в день.

В Яндексе уже была реализована механика отправки событий в БКИ, но она была очень медленной: на обработку 500 тыс ежедневных ивентов тратилось около полутора дней. Увидев задачу, я понял, что стандартные решения здесь не подойдут. Основной проблемой стала производительность — если система падала, то обработка событий останавливалась, и приходилось начинать процесс заново.

Поэтому я принял решение использовать многопоточное программирование и асинхронные вызовы функций или сервисов. Здесь возникли свои сложности: например, нужно было решить, что делать с упавшим сервисом или как убедиться, что все сервисы завершили работу корректно. Также необходимо было определить, сколько асинхронных сервисов придется запустить и какие ресурсы (CPU, RAM и т.д.) для этого потребуются.

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

Расскажите подробнее об ускорении отправки событий. Какие подходы к параллелизации и распределению батчей позволили достичь таких результатов?

Моей основной идеей стало извлечение событий определенными порциями (батчами) и их отправка на обработку в отдельные сервисы. Каждому сервису присвоен уникальный UUID, который по завершении работы оставляет запись с указанием статуса в специальной таблице в базе данных. Каждый переход сервиса в статус «готово» отражается в счетчике обработанных событий в этой же таблице. Для параллелизации я подобрал сервис наподобие AWS Lambda, где можно запускать Java-код на отдельных виртуальных машинах и оркестрировать их с помощью Java SDK.

В базе данных необходимо было, чтобы виртуальные машины не конфликтовали между собой. Для добавления таких записей, как адрес батча и количество выполненных задач, достаточно стандартного уровня изоляции PostgreSQL — READ COMMITTED. Однако для отражения данных в счетчике необходимо было использовать pessimistic lock.

Количество событий, отправляемых на обработку за один раз, и объем CPU-ресурсов я определял эмпирическим путем. В качестве основной метрики использовал время обработки, то есть оценивал, удовлетворит ли нас текущая скорость выполнения задачи. После формирования всех батчей запускается финальный сервис, который считывает из базы данных количество обработанных событий. Если оно совпадает с ожидаемым, то сервис завершает работу системы.

Если батч-сервисы упадут, они начнут работу заново. Это не занимает много времени и ресурсов, потому что отдельный сервис обрабатывает небольшое количество событий, а не все сразу.

Процесс параллелизации занял у меня около двух месяцев — это стало одной из критически важных задач проекта. Необходимо было уложиться в дедлайны, чтобы не попасть под штрафные санкции. Благодаря моей экспертизе, нам удалось добиться приемлемых сроков отправки и успешно доработать продукт.

Есть ли у вас принцип или правило в разработке распределенных систем, без которого вы не начинаете проект?

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

Обязательно необходимо определиться с семантикой системы — at most once, at least once или exactly once — и понять, как будут храниться данные. Исходя из этого, можно строить архитектуру. Конкретные инструменты зависят от задач, которые решает система.

Я советую уделять внимание деталям и продумывать все возможные corner cases. Это поможет предусмотреть все уязвимости и понять, что нужно делать для их предотвращения.

Вы также разрабатывали модуль курса и проводили лекции для программы Master’s Program SkillFactory. Какие ключевые идеи из своей практики вы стараетесь донести студентам?

Разработка модуля заняла у меня около двух месяцев, также в рамках обучения я проводил лекции по распределенным системам — в частности, по работе с большими данными. Я рассказывал не только о конкретных подходах, но и о фундаментальных концепциях — таких как CAP-теорема, ACID и другие. У всех распределенных систем есть общие детали, и если изучить базу, то станет проще понимать задачи и выбирать инструменты для работы.

Также я понимал, насколько студентам важно получить релевантные знания, которые можно применить в решении реальных бизнес-задач — особенно учитывая сегодняшнее обилие информации различного качества. Поэтому от теории мы переходили к практике, где я использовал подходы, близкие к production-ready уровням.

Какие подходы, на ваш взгляд, станут стандартом в построении финансовых высоконагруженных систем в ближайшие 3–5 лет? Будут ли это новые модели консистентности, ML-driven-routing, автоматизированные механизмы самовосстановления?

Я считаю, что всё больше компаний будут переходить к микросервисной архитектуре и event-driven системам с eventual consistency — где критичные операции изолируются, а остальная часть системы масштабируется с помощью CQRS, Kafka, event sourcing и оркестрируется через Kubernetes.

Также я наблюдаю устойчивый тренд в сторону cloud computing: всё больше компаний создают и развивают собственную облачную инфраструктуру. Например, свои облака есть у таких корпораций, как Яндекс, VK, Mail.Ru, МегаФон и других.

ML и AI будут чаще применяться в задачах финансовой безопасности, кредитного скоринга и других операциях, где требуется принимать решения на основе больших объемов данных. Но финальная проверка все равно будет лежать на плечах специалистов, потому что пока AI не может работать со 100%-ной точностью.