В один прекрасный день наш заказчик приложения решает, что пришло время для тестирования производительности. Проект большой, коммуникации в бэкенде много, так что запрос вполне ожидаемый. И все же — когда мы получили заявку на тестирование производительности, то плохо понимали реальный объем нагрузки на наше веб-приложение в продакшене.
В чем проблема?
Наше приложение работает в сфере интернета вещей (IoT), и мы явно недооценивали его целевую нагрузку. На деле оказалось, что количество клиентов, которых надо обслуживать единовременно, исчисляется даже не десятками, а сотнями тысяч (в будущем, может, даже и миллионами).
Сначала взялись тестировать коммуникацию устройств с приложением, которая происходит по протоколу MQTT. Как раз в этом месте ожидалась максимальная загрузка. Быстро «набросали» тестовый сценарий, разработали скрипт в JMeter и настроили инфраструктуру для создания распределенной нагрузки на EC2-инстансах в AWS.
Первые результаты для нагрузки в «детские» 20 000 подключенных онлайн-устройств получили буквально за первую неделю. Дальше были итеративные тесты на все большее количество виртуальных девайсов, которые сейчас уже дошли до проверки системы в условиях одновременной работы с 300 000 устройств.
Написать абзац выше было настолько же просто, насколько сложно в реальности сделать это и протестировать. «Грабли» были раскиданы просто на каждом шагу. В этой статье я бы хотел подробнее рассказать о наших челленджах. Возможно, вы тоже на них наткнетесь, планируя тестирование больших нагрузок. Пусть наш горький опыт поможет вам избежать ошибок.
Запуск JMeter через Maven
Первое, что мы поняли: JMeter из «коробки» очень сложно заставить запускать тесты в более или менее гибкой манере. Хотелось гибкости, которую обычно предоставляют тестовые проекты, собранные с помощью Gradle или Maven. Тогда зависимости скачивались бы автоматически, а параметры можно было передавать и обрабатывать в удобном формате.
Довольно быстро нагуглили jmeter-maven-plugin, который позволяет относительно беспроблемно запускать JMeter через Maven, менять параметры запуска тестов и т.д. Чтобы настроить эту связку, понадобилось примерно два полных рабочих дня. Процесс занял намного больше времени, чем ожидалось.
Если вы собираетесь использовать jmeter-maven-plugin для более или менее серьезного тестирования, подумайте пару раз. Примеров в документации мало, а те, что есть, сделаны для базовых use-кейсов. Большинство родных параметров JMeter называются по-другому, структура pom.xml-файла и доступных блоков не очевидна и тоже плохо описана.
В целом, после двух дней все начало работать, но в режиме «лучше не будем на этот файл дышать, а то вся магия выветрится, работать перестанет».
Возможно, проблема и не в самом плагине, а в документации. Но если инженер с пятилетним опытом должен потратить два полных дня на настройку такой банальной вещи — значит, что-то там точно нечисто.
Запуск JMeter-тестов в распределенном режиме
Когда вся система с Maven и JMeter уже была настроена, все это целиком мы залили на виртуальную машину в AWS. Сразу же быстро прогнали тест на пару тысяч потоков, получили какой-то результат и пошли праздновать.
На следующий день пришли, поняли, что радоваться рано, и стали думать, как все это дело теперь скейлить.
JMeter в нашем случае съедал примерно 1 Мб оперативной памяти на один поток. На нашей «виртуалке» было 32 Гб. Путем нехитрых вычислений мы выяснили, что текущего объема памяти едва хватит даже на симуляцию 30 000 виртуальных устройств. А на тот момент целью стояло 50 000 или даже 100 000 одновременно подключенных гаджетов.
Мы решили распределить нагрузку на несколько машин. В документации к JMeter черным по белому написано, что для этого достаточно запустить на слейв-машинах один исполняемый файл и тем самым запустить jmeter-server. На мастер-машине необходимо начать тест с правильно прописанными айпишниками слейвов. Но и тут грабли!
На машинах, участвующих в тесте, должны быть открыты определенные порты. Это прописано в документации, но вот факт того, что для слейвов порты надо открывать не по одному, а по три — это написано неприметным текстом и только в одном месте в документации. Если же открывать только по одному порту, тест-то будет идти, но на мастер не дойдут никакие результаты тестов со слейвов.
На практике для корректного запуска теста на слейв-машинах нужно прописать в pom.xml-файле следующие параметры:
<client.rmi.localport>${port}</client.rmi.localport>
В этом примере значение port нужно передавать из командной строки при запуске, и если передать, к примеру, 50000, то фактически процесс будет занимать порты 50000, 50001 и 50002.
Параметры для слейв-машин нужно передавать специфичным образом. Когда мы пытались передавать дополнительные параметры в тест, они попадали на мастер-машину и не попадали на слейвы. Оказалось, параметры для слейв-машин нужно было передавать в другом формате. Это тоже было написано в документации далеко не на первой странице.
Чтобы передать на мастер какой-то параметр, он должен быть указан в теге propertiesUser:
<propertiesUser> <file_name>${fileName}</file_name> </propertiesUser>
Параметр file_name будет доступен только на мастере, но не будет доступен на слейвах. Чтобы параметр был доступен на слейвах, нужен блок propertiesGlobal:
<propertiesGlobal> <threadCount>${threadCount}</threadCount> </propertiesGlobal>
Все описанное выше еще сложнее сделать при использовании maven-плагина. Как я уже написал, в maven-плагине бóльшая часть действий и фич переименована и не очевидна из документации. Это сделало «инвестигейт» и реализацию всего распределенного запуска еще сложнее.
Время на выполнение задачи практически удвоилось: пришлось выяснять, как делать какое-либо действие в JMeter, и вдобавок учиться делать то же самое через плагин.
Если очень коротко, то в вашем файле pom.xml будут такие блоки:
- propertiesUser — для передачи параметров мастера;
- propertiesGlobal — для передачи параметров слейвов;
- testFilesIncluded — для фильтрации запускаемых тестов;
- jmeterExtensions — для подключения дополнительных библиотек;
- jMeterProcessJVMSettings — для твиков JMeter-процесса (увеличение Java-хипа, к примеру)
- generateReports — для генерации HTML-репорта сразу после теста;
- overrideRootLogLevel — для вывода логов нужного уровня;
- remoteConfig — для настройки запуска слейвов;
- propertiesSystem — для передачи информации про используемые слейвы и порты.
В следующей части расскажу о настройке виртуальных машин и анализе результатов.
Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: