Когда мне пришла идея о написании этой статьи, я предполагала, что это будет хроника ошибок, от которых мы бы хотели предостеречь техническое сообщество: как мы наивно полагались на «синтетику», а «видовое многообразие» сервисов раскрыло нам глаза.
Но по мере подготовки материала пришло осознание, что проблема не в том, что мы ошиблись в методике, а в том, что для core-системы, пропускающей сотни тысяч разнородных запросов в секунду, достичь точного воспроизведения боевой нагрузки практически невозможно.
Под катом рассказ о том, как мы двигались от упрощенных моделей к глубокому изучению специфики поведения системы, а по пути осознали, что нужно не выбирать между крайностями, а комбинировать подходы и учитывать риски.
О Фабрике интеграций
В 2024 году перед нами была поставлена амбициозная задача создать новую интеграционную платформу — Фабрику интеграций, — призванную заместить прежние legacy-решения. В первую очередь основную шину, именуемую «кровеносной системой» Банка. Через неё большая часть front- и middle-систем обращаются к автоматизированной банковской системе (далее — АБС).
С этим связаны высокие требования к производительности: новая платформа должна обеспечивать запас для роста бизнеса и выдерживать нагрузку до 150 k RPS. Не менее важна и надежность: через шину проходит значимая часть финансовых операций, поэтому её отказ или деградация могут приводить к серьезным репутационным последствиям и прямым убыткам.
Помимо этого Фабрика должна соответствовать современному стеку технологий, включая уход от монолитных решений. Так появилась микросервисная платформа, включающая как самописные, так и коробочные компоненты.

Для платформы такого класса принципиальное значение имеет технологическая устойчивость выбранных решений в условиях промышленной эксплуатации. При этом важно учитывать, что Фабрика разворачивается на физическом commodity x86-оборудовании.
150 тысяч попугаев
Как я упомянула выше, возможность выдерживать большие нагрузки была одним из самых важных критериев.
Настолько важным, что на этапе предпроекта мы просили потенциальных подрядчиков развернуть MVP-платформы и провести пробный нагрузочный тест на 150 k RPS на инфраструктуре Банка на реальном высоконагруженном сервисе (назовём его «часто вызываемый REST»), что минимизировало вероятность, что что-то пойдет не так.
Со стартом проекта стенд нагрузочного тестирования стал самым эксплуатируемым: инфраструктурные доработки зарождались там прежде, чем оказывались на стенде разработки. Большое количество компонентов и возлагаемых на них требований нуждалось в индивидуальном подходе, также осуществлялся их регулярный тюнинг по результатам испытаний. Одним из таких примеров стала разработка под нужды проекта механизма leastconn-балансировки для выбранной Kubernetes-платформы, впоследствии ставшая вкладом в профессиональное сообщество.
В конце концов труды увенчались успехом: тесты дали нам возможность объективно оценить выбранный технологический стек и его жизнеспособность в условиях применённых сетевых решений. Как видно на графике, мы пробили ожидаемый потолок в 150 k RPS.

Результаты выглядели весьма обнадеживающе: мы можем не только достигать, но и превышать целевые показатели. Платформа стабильно работает на заявленных нагрузках без роста времени отклика, демонстрируя предсказуемое поведение даже в пиковых сценариях. При этом архитектура обладает достаточным запасом прочности и масштабируемостью, чтобы обеспечить вероятный рост нагрузки на ближайшие годы.
Тут бы и насладиться красивым результатом. Но дьявол кроется в деталях.
Коварные ограничения
На входе мы имели ряд ограничений со стороны смежных систем.
Во-первых, концепция проекта изначально предполагала, что миграция пройдет бесшовно для команд-потребителей: ни функциональное, ни нагрузочное тестирование не должны отвлекать их ресурсы.
Во-вторых, максимальная нагрузка, которую держит тестовая АБС — раза в 3 ниже нашей целевой, потому вышеупомянутые тесты с экстраординарными результатами мы проводили на заглушке. Признать их полностью информативными мы, разумеется, не могли, и следующим этапом перешли к тестам с АБС.
Для моделирования сценариев мы разработали специальный инструмент (далее — синтетический модуль). Он обеспечивает полный контроль над параметрами теста, от объёма полезной нагрузки до задержек ответа, минимизируя при этом воздействие на АБС и систему логирования запросов, что делает его универсальным для валидации производительности и масштабируемости.
Модуль был создан на основе гипотезы: на уровне шины не реализуется бизнес-логика, а значит, все вызовы должны быть равнозначны с точки зрения производительности. Эта гипотеза и легла в основу первых нагрузочных испытаний. Так, на предпромышленном этапе была разработана методика, включающая основные сценарии нагрузки: запросы на чтение и на запись (GET, PUT, POST), работа с REST и SOAP-запросами. Подход включал как тестирование отдельных компонентов системы, так и всей «цепочки». В том числе большое внимание уделялось тестированию масштабирования, планирования ресурсов, отказоустойчивости, хаос-тестированию.
Тем не менее очень скоро стало понятно, что тестирование производительности «в вакууме» — лишь первый шаг и для более объективной оценки не обойтись без реальных сервисов.
Дивный новый мир: старт эксплуатации
С началом эксплуатации мы поняли, что проведённые испытания, даже на столь универсальном инструменте, как синтетический модуль, слишком упрощают картину в сравнении с нагрузкой, которой подвергается такая сложная и распределённая система. Мы достигли успехов, измеряя в «попугаях» — запросах в секунду, задержках, типовых сценариях. Но в реальности поведение платформы определяется не столько объёмом трафика, сколько его составляющими: типами вызовов, размерами запросов и ответов, частотой обращений, особенностями клиентов и реакцией backend-систем.
Один из ярких примеров — просадка производительности, вызванная неожиданно высокой нагрузкой от сбора метрик при интенсивном трафике. Миллионы необязательных числовых рядов, собираемых с каждого узла, создавали высокую фоновую нагрузку на сеть, дисковую подсистему, память и CPU. Это потребовало оперативной оптимизации: мы внедрили фильтрацию и отключение избыточных метрик на уровне шлюза, что позволило снизить нагрузку на инфраструктуру без потери наблюдаемости.
Так начался новый виток эволюции наших подходов к тестированию. В первую очередь мы вернулись к уже знакомому нам часто вызываемому REST. Его выбор был обусловлен рядом факторов: это один из самых нагруженных сервисов в экосистеме Банка, охватывающий широкий спектр функциональности — от работы с абстрактными моделями данных до реализации конкретных бизнес-операций. Кроме того, сервис исторически считается стабильным и безопасным для интенсивного вызова в тестовых сценариях, что делает его удобным «представителем» типичной нагрузки.
Нашим вторым героем стал редко вызываемый SOAP-сервис с низкой частотой обращений и сложной бизнес-логикой, по совместительству — первый кандидат на миграцию в продакшн.
Тесты на столь разных сервисах позволили нам оценить поведение платформы в различных сценариях, выявить влияние типов взаимодействия на стабильность и ресурсоёмкость, а также проверить корректность настройки соединений, сериализации, обработки ошибок и интеграции с инфраструктурой в условиях, максимально приближённых к боевым. Но и этого оказалось недостаточно.
Новый этап расширения видового многообразия пришел, когда мы сравнительно устоялись на бою и подошли к миграции высоконагруженных сервисов. Стало очевидно, что прежние тестовые сценарии, даже на высокой нагрузке, не позволяют в полной мере предсказать поведение системы в реальных условиях. Кроме того открытым оставался вопрос: как правильно подбирать ресурсы для таких сервисов?
Мы провели серию комплексных нагрузочных тестов с привлечением представителей «тяжелой артиллерии». Ими стали 3 высоконагруженных сервиса с малыми, средними и крупными объемами ответов при различных задержках бэкенда.
Есть сервисы, которые в отдельных случаях возвращают ответы объёмом более 20 МБ, при этом для 95% запросов размер ответа не превышает 100 КБ (95-й перцентиль, p95). Как выяснилось, для обработки таких, даже разовых запросов, необходимо выделение более 4 ГБ heap на 1 запрос, что в общем совсем не соответствовало изначальным результатам тестирования, когда для стабильной работы 99% микросервисов достаточно было выделять по 512 Мбайт на инстанс.
На основе этих данных была выработана методика расчета требуемых ресурсов — количество экземпляров, объём памяти, потребление CPU — в зависимости от ожидаемого RPS и размера полезной нагрузки.
Ближе к реальности
Настоящим праздником разнообразия и своего рода экзаменом для нас стал плановый тест АБС через нашу шину с одной из нагруженных систем-потребителей. Он охватил около 80% высоконагруженных сервисов и имитировал сложный, близкий к реальному профиль нагрузки, включая смешанные типы вызовов, переменные размеры запросов и ответов, а также высокую частоту обращений к АБС.
Ключевым открытием стало неожиданно высокое потребление ресурсов на стороне АБС. В отличие от замещаемой шины, новая архитектура инициировала более интенсивные коннекты, что привело к избыточной утилизации бэкенд-ресурсов. Анализ показал, что проблема кроется в неоптимальной работе механизма пулинга соединений на нашей стороне.
Была проведена детальная диагностика и последующая тюнинговая настройка ключевого компонента, отвечающего за управление пулом. Это позволило не только устранить избыточную нагрузку на АБС, но и в перспективе снизить потребление ресурсов и соответствующих расходов (на девятизначные суммы).
До:

После:

Сложно сказать, насколько решающую роль здесь сыграло разнообразие трафика. Думаю, ключевым всё же стал прицельный анализ результатов на стороне АБС и их сопоставление с данными тестов через старую шину.
Досадные неожиданности на бою
Если эти работы можно отнести к запланированным, то был и ряд неприятностей, которые мы отлавливали уже на продуктиве.
Некоторые из них, как выяснилось позже, проявлялись и на нагрузочных тестах. Речь о малочисленных ошибках с долей менее 0,001%, что на фоне миллиардов запросов за итерацию выглядело статистически допустимым, однако впоследствии они оказались заметны потребителям.
Среди ключевых проблем, о которые мы споткнулись:
Рассогласованные таймауты, приводившие к ошибкам 502. На плотной нагрузке система работала стабильно, но на разреженном трафике, особенно в сценариях с низкой частотой вызовов, возникал фон ошибок порядка 1 %. Его зафиксировали потребители, использующие ручной повтор операций по старинке.

Некорректное закрытие соединений на уровне API-шлюза, что также вносило вклад в фон ошибок. Проблема не проявлялась при высокой нагрузке, но давала сбои в «тихих» режимах, когда соединения простаивали и сбрасывались с нарушением протокола.
Рост времени отклика из-за сборки мусора в JVM. Хотя мы и проводили тестирование с различными сборщиками мусора — G1GC, Shenandoah, ZGC, — но в тестовой среде особых различий не выявили. Мы остались на G1GC, что впоследствии привело к появлению задержек до 3 секунд на фоне интенсивной нагрузки.
Точечные сбои при обработке крупных ответов. Мы столкнулись с тем, что время отклика выросло с нескольких миллисекунд на НТ до нескольких секунд в проде. Причина — в продакшене запросы формировались с другими параметрами и возвращали ответы, кратно превосходящие по объему те, что использовались в нагрузочном профиле, при этом JVM пожирала память, как Ти-Рекс.
Добавим к этому кейсы с системами, не умеющими делать retry, HTTP/1.0 и даже ожидание 100 Continue. Они лишний раз напомнили, что часто роль играет разнообразие не столько сервисов, сколько потребителей и их поведения.
Недостижимый идеал
Безусловно, нам хотелось бы избежать всех перечисленных ошибок, но правда жизни состоит в том, что для подобных систем достичь картины, идентичной боевой, на нагрузочном стенде, мягко говоря, проблематично:
Во-первых, тесты на нагрузке свыше 100 k RPS сами по себе являются нетривиальной задачей. Таких кейсов крайне мало в открытом доступе в мировой практике.
Во-вторых, фактически это означает буквально протестировать весь Банк и проводить это упражнение регулярно на протяжении длительного времени, предварительно автоматизировав все существующие тест-кейсы всего Банка и поддерживая их в дальнейшем по мере развития всех систем и сервисов. Прилагаемые на это усилия и затраченное время будут несопоставимы потенциальным рискам, ибо количество интеграционных приложений в нашем случае составляет несколько тысяч.
И в-третьих, не каждая система-потребитель имеет не только скрипты и команду НТ, но и нагрузочный стенд в принципе: для ряда систем это просто может быть нецелесообразно.
От синтетики — к видовому многообразию?
Проведя в общей сложности более 500 тестов, мы можем сказать с уверенностью: правильной траекторией видится только взвешенный подход и комбинация методик.
Крайности вредны в равной степени. Полностью полагаться на экстраполяцию, разумеется, нельзя. Например, возвращаясь к выработанной методике расчета ресурсов под «тяжелые» сервисы, стоит сказать, что она не является аксиомой, а постепенно обогащается новыми наблюдениями и корректируется. Даже современные подходы на основе ИИ, применяемые для рекомендаций по масштабированию, дают противоречивые результаты, что подтверждает — универсального решения не существует. Принимая адекватные риски мы избавляемся от необходимости тратить избыточные трудозатраты на внедрение каждого единичного сервиса.
А вот один из последних кейсов, который точно не позволит считать синтетические тесты бесполезными. В процессе миграции мы столкнулись с исчерпанием соединений уже на 10 k RPS — не из‑за нагрузки, а из‑за специфичной модели переиспользования TCP‑коннектов.
Чтобы отладить поведение системы, мы выбрали инструмент, основанный на «клонировании» того самого синтетического модуля, разработанного на заре проекта. Именно такая методика позволила нам контролируемо воссоздать условия с высокой плотностью переиспользуемых TCP-соединений.
Каждый из шагов на нашем пути вносил свой вклад, и именно их сочетание позволило нам достичь валидных результатов. Возвращаясь к «экзаменационному» тесту с одной из нагруженных систем‑потребителей, немаловажным будет отметить, что мы проходили его на довольно высокой стадии зрелости, достигнутой преимущественно за счёт предыдущих синтетических подходов, и, несмотря на существенный момент для доработки, иных замечаний по результату теста не было.
Да, мы не можем воссоздать весь Банк на нагрузочном стенде. Но можем комбинировать подходы: синтетику — для оценки поведения системы в целом, реальные сервисы — для изучения частных паттернов, а критическое мышление — для экстраполяции.
Нагрузочное тестирование — это не разовое упражнение для подтверждения красивых цифр, а многократный и регулярный подход к снаряду, непрерывный процесс итераций, наблюдений и адаптации. Думаю, в этом и состоит наш главный урок.
В завершение хочу выразить благодарность всем соавторам данной статьи: Максиму Еганову, Евгению Новикову, Роману Грекову, Игорю Мурзичу, Дмитрию Кужелю, Семёну Устинову.

