Привет, Хабр!

Сегодня расскажем, как мы в MANGO OFFICE занимаемся нагрузочным тестированием и экономим время и ресурсы команд, применяя неочевидные решения и подходы.

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

Нагрузочное тестирование — это сложно и дорого. И чем сложнее система, тем дороже его разработка и поддержка. Если тесты ловят какую-то аномалию, начинается процесс ре-тестов, подтверждения производительности или ее деградации. Релизы откладываются или переносятся. Сонные, злые и небритые разработчики корпят над кодом, а в их глазах стоит немой вопрос: где тот косяк в новом функционале, из-за которого все сломалось?

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

Ликбез: проблемы с нагрузочным тестированием и как их решать

Немного предыстории. В нашей компании много систем асинхронной обработки событий. В основном они работают около телефонии, на интеграциях между виртуальной АТС и внешними системами типа AmoCRM или Bitrix.

В продакшне это выглядит следующим образом:

В нагрузочном тестировании мы используем Apache Jmeter, которым эмулируем нагрузку от внутренних сервисов и телефонии на системы интеграции.

В ходе тестирования мы смотрим на время отклика приложений интеграции, на то, как  система реагирует на события, и далее по списку: ЦПУ, RAM, HEAP. Отдельно замеряем время от поступления события в очередь на RabbitMQ до момента отправки этого события в виде HTTP-запроса согласно бизнес-логике.

Если вы строили стенды для нагрузочного тестирования, то наверняка сталкивались с вопросом: как грамотно связать два совсем не связанных друг с другом события? В данной модели нагрузки у нас отсутствует четкое понятие «запрос–ответ». Ответ может прийти асинхронно и совсем не в ту систему, с которой мы даем нагрузку, так как запрос для нас — это событие в RabbitMQ, а ответ — HTTP-запросы во внешнюю систему.

Чаще всего такие задачи решают так:

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

Минусы первого способа:

1) Необходимо дополнительно идентифицировать и связывать записи об отправленных событиях в RabbitMQ c записями в логах об исполненных HTTP-запросах, что не всегда тривиальная задача.

2) В случае наличия нескольких нод имеем несколько источников логов.

3) Логи — не всегда надежный источник.

4) Обязательно что-нибудь пойдет не так, особенно на высокоинтенсивных тестах.

В итоге результаты нагрузки будут врать. Команда продукта не сможет доверять нечетким результатам, которые сопровождаются невнятными доводами вроде: «Процент идентифицированных ивентов немного отличается от эталонного — возможно система теряет события».

Минусы второго варианта:

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

2. Заглушка может дополнительно влиять на результаты.

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

Как мы работали раньше

Несколько лет мы проводили релизы, обрабатывая логи с помощью Python после теста и подмешивая их к результатам. Но в какой-то момент владелец продукта пришел к нам с проблемой: возникло подозрение, что система начала терять данные. На нагрузочных тестах такое не воспроизводилось, но и четко сказать, что этого нет — мы не могли. Процент потерь логов был не выше обычного уровня в 10–15 %.

Подсчеты показали, что на обработке логов мы теряли до полутора миллионов событий за один тест. При том, что, во время тестов Jmeter генерировал около 10 миллионов событий. Плюс от теста к тесту результаты плавали: иногда это был миллион, иногда почти два.

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

Техническая реализация

Для начала нужно было придумать, как засекать обращения в CRM быстро, просто и без использования дополнительных ресурсов, новых сервисов типа KAFKA, сложных расчетов на БД или переписывания ядра Jmeter.

Так как в проекте мы уже получали часть событий по Websocket, решили и дальше развивать идею в этом направлении. В итоге разработали следующую схему:

1) Создали свой плагин для Apache Jmeter — для поддержки нашего тестового WebSocket-протокола.

2) Слегка докрутили приложение: добавили в некоторые запросы сервисные хедеры для более точной идентификации HTTP-запросов.

3) Доработали профили нагрузки:

a) Сделали дополнительную инициализацию подключения на старте.

b) Написали свой Jmeter Sampler, которому на вход передавался ключ для идентификации события в кеше, а также время отправки изначального запроса в RabbitMQ. Данные семплеры отрабатывали в конце сценария и идентифицировали прилетевшие ответы в кеше по ключу. На выходе они создавали стандартный для Apache Jmeter объект с результатом, который обычным способом уходил дальше на обработку в Jmeter.

Схема нагрузки получилась такой:

Сценарий нагрузки выглядит следующим образом:

  1. На старте мы инициируем дополнительное WebSocket-подключение к заглушке.

  2. JM по сценарию начинает генерировать ивенты в шину, предварительно сохраняя внутренние ID абонентов.

  3. Записи с параметрами звонковых событий складываются в специальную очередь. Таким образом фиксируются параметры и «образно» listener на асинхронное событие.

  4. Приложение начинает реагировать на это, совершая HTTPs-запросы в CRM, в нашем случае — в заглушку. В хедерах запросов дополнительно пробрасываются ID, если их нет в теле запросов.

  5. Заглушка при поступлении HTTP-запросов совершает асинхронную отправку ивентов в формате JSON во все подключенные к ней WebSocket-сессии. (Вообще хотели в protobuf, но нам хватило и JSON реализации.

  6. Jmeter асинхронно получает эти события и складывает их в кеш (по сути это обычная хеш-мапа, без всякой магии типа Chronicle Map).

  7.  При этом отдельная независимая тред-группа обрабатывает очередь ожидания событий и фиксирует наступление асинхронного события по мере появления событий в кеше. Чем достигается практически true асинхронность и независимость потоков асинхронных и обычных событий.

  8.  Далее Jmeter берет из события timestamp поступления HTTP-запроса, сверяет его со временем отправки соответствующего события в RabbitMQ и формирует стандартный SampleResult, который поступает в движок Jmeter совершенно стандартным способом.

  9. Jmeter обрабатывает этот результат как еще один семпл.

Что получилось в итоге

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

Чего мы добились:

  • Появилась уверенность в системе (confidence).

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

  • У продуктовой команды сильно выросло доверие к результатам нагрузочного тестирования.

  • Полное отсутствие ошибок во время тестирования стало нормой.

  • Ошибки на этапе нагрузки стали серьезным поводом инициировать расследование и откладывать выпуск функционала до их устранения.

В целом данная схема хорошо себя зарекомендовала в задачах НТ, но развитие ее далеко от завершения, поскольку надежная основа лишь стимулирует совершенствование и расширение функционала.


Подписывайтесь на наши соцсети:

Аккаунты Mango Office

Комментарии (0)