Сравнение JMeter и k6 на практике
Привет! Меня зовут Максим Колесников. Я работаю в центре компетенций нагрузочного тестирования блока обеспечения и контроля качества выпуска изменений в «РСХБ-Интех» — IT-компании АО «Россельхозбанк». У нас молодое подразделение, которое активно развивается, так что вместо инерционного похода «так исторически сложилось», команда задается вопросами «что делаем», «почему делаем» и «как можно сделать лучше» (и надо ли).
Когда я в очередной раз прогонял себя по этому списку, возникла крамольная мысль: «А не выкинуть ли нам JMeter и переписать все на k6?». Вскоре уровень моей радикализации вернулся в норму, во многом под давлением аргументов, с которыми сложно спорить: «Нельзя внедрять технологии ради технологий», «Инструмент нужно выбирать под задачу, у всех есть свои плюсы и минусы», «А где будешь искать людей, владеющих инструментом, ты подумал?» и т. д. Но где-то в подсознании зародилась идея, от которой я мог избавиться только одним путем — написав эту статью. На этом закончим с лирической частью, всех заинтересовавшихся разбором инструментов прошу под кат.
Решения, проверенные временем
Наш текущий инструментарий достаточно распространен и отработан сотнями инженеров в течение многих человеко-лет: JMeter, единственный и неповторимый, для подачи нагрузки, Influx/Prometheus для мониторинга (сейчас мы в процессе выбора чего-то одного, но это тема для отдельной дискуссии), Jenkins для запуска тестов, ну и Grafana, GitLab, Confluence для прочих задач, которых мы здесь не будем касаться.
Гипотеза
Идея была такова — мы можем взять k6, переписать на нем наши скрипты и получить такие выгоды:
ускорение процесса разработки скриптов;
уменьшение времени на их поддержку;
экономию ресурсов генераторов;
инсайты по улучшению текущего инструментария/процессов.
Почему k6 вообще может нам подойти? Если откинуть субъективные симпатии, то по следующим причинам:
все скрипты на проекте — это HTTP-сценарии с нелинейной логикой (детальное описание скрипта будет ниже);
много переиспользуемых компонентов;
количество скриптов и заглушек растет быстрее возможности по добавлению железа.
О других плюсах решения можно больше узнать на сайте самого k6.
Формулировка критериев и выбор операции для теста
Используя научный подход, переработаем тезисы, изложенные выше, в более четкие критерии сравнения двух инструментов (подробнее о каждом ниже по тексту):
общие положения: лицензия, позиционирование инструмента на рынке, требования для входа;
функционал: возможности из коробки, плагины, интеграция в тестовую инфраструктуру (Jenkins, GitLab);
производительность и потребление ресурсов.
Сравнивать будем на примере одной операции, реализованной с помощью обоих инструментов. Сама операция представляет собой:
100+ HTTP-запросов, разделенных на 9 групп и реализующих выполнение BPM-процесса;
20+ динамически коррелируемых параметров, используемых в половине запросов, а также логические компоненты — циклы, условия и ветвления.
Поскольку, как известно, дьявол кроется в деталях, акцентировать внимание буду на тех моментах, которые лично для меня оказались неожиданными при работе с k6 и которые не описаны в документации или в других статьях.
Общие положения
Несмотря на разные классы лицензий, отличий (коммерческое использование, доработка, передача скриптов), которые повлияли бы на выбор в наших условиях, нет. Детальное сравнение лицензий Apache License 2.0 и GNU Affero General Public License v3.0 можно почитать тут или тут.
JMeter — мощный инструмент, поддерживающий кучу протоколов и заточенный под работу в собственном GUI.
K6 — инструмент заточенный под web и позволяющий придерживаться концепции everything as code. Есть cloud-версия, позволяющая избежать проблем с железом и мониторингом. Опять же, детально тут.
Порог входа в JMeter существенно ниже, чем в k6. С одной стороны — заполнение полей в интерфейсе, а с другой — написание кода на JS. И найти инженеров, владеющих k6, пока что существенно сложнее.
Настройки окружения
Думаю, большинство из вас знакомы с процессом установки JMeter, тем не менее, верхнеуровнево опишу, как он выглядит у нас:
Поставить Java, либо с помощью пакетного менеджера, либо с помощью старых добрых рук и архивов.
Поставить Maven, так как JMeter мы используем в связке с jmeter-maven-plugin.
Еще не помещает IDE для редактирования pom.xml и запуска GUI JMeter.
С k6 сложнее, но без драматической разницы (если вам не нужны плагины, то вместо шагов 1-4 будет только установка бинаря k6):
Установить go.
Установить xk6 (можно сразу скачать бинарь с GitHub).
Собрать бинарь k6. На этом этапе наткнулся на проблему с кросс-компиляцией: она отказалась работать, хотя в документации заявлена. Это породило следующий промежуточный шаг.
Поскольку тестовый контур и доступ в сеть для нас несовместимы, нужно выполнить одно из двух дополнительных действий для скачивания расширений: можно сделать go install на машине, где есть доступ в сеть, и скопировать $GOPATH/pkg на генератор, либо склонировать нужный репозиторий на генератор и сделать локальный go install.
Опять же, IDE, а для комфортной работы с k6 еще и IntelliSense. Принцип такой же — качаем на машине, с которой можно, и копируем на машину, с которой нельзя.
Если у вас уже настроено проксирование пакетов go (как обычно бывает через Nexus для Java), то разницы нет. Да и пройти этот этап нужно всего раз.
Настройки инструмента
Далее пункт, который вызывает у меня повышенный интерес и может в определенных случаях склонить выбор на ту или иную чашу весов — возможности настройки инструмента. Если в JMeter практически невозможно найти сущность, для которой нет каких-либо параметров, то k6 не вызовет у вас астенической фрустрации от ассортимента настроек. Да, из всех параметров JMeter большинство из нас слышали хорошо если о десятой части (я сам узнал много нового в процессе подготовки статьи), а реально применяющиеся настройки можно пересчитать по пальцам одной руки. Но все-таки наличие выбора лучше, чем его отсутствие. Также сказывается зрелость инструмента: если у JMeter отличная документация и все параметры детально описаны, то настройки k6 приходится изучать не только по документации на сайте, но зачастую и по тикетам на GitHubе, а то и по исходникам там же.
Дабы не быть голословным, приведу пару примеров.
Из того, что сразу потребовало вмешательства — k6 безапелляционно пытается использовать HTTP/2. Круто, современно, но у нас клиенты подключаются по HTTP/1.1, так что и грузить нужно им. И вот незадача: в документации ни слова про принудительную версию протокола. Да, через пару минут поиска я нашел, как это побороть (если кому интересно, то нужно установить переменную среды GODEBUG=http2client=0), но осадочек и подозрение, что в следующий раз это может не обойтись малой кровью, остались.
Из того, что не пригодилось, но взбрело в голову — настройка HTTP-клиента в JMeter httpclient4.time_to_live. Можете сказать, что притянуто, но все зависит от контекста — мне пару раз приходилось ее подкручивать.
Плагины и расширения
Поддерживаемых из коробки протоколов в JMeter в разы больше, чем в k6, но на необходимость использования плагинов это практически не влияет. Самих же плагинов для JMeter ощутимо больше, писать новые или дорабатывать готовые проще в связи с более широким распространением Java среди инженеров по нагрузке. Также за счет зрелости инструмента в плагинах JMeter больше уверенности — еще пол года назад плагин k6 для отправки метрик в Prometheus, который заявляется как одна из фич в официальной документации, выдавал не особо правдоподобные метрики. Вместо этого приходилось применять связку с statsd_exporter. Судя по всему, эта связка еще актуальна — в ходе эксперимента обнаружил, что использование этого плагина вызывает утечку памяти и CPU. Происходит это, вероятно, из-за подсчета средних значений и перцентилей за все время теста. В моем случае утечка составляет всего несколько процентов за сутки и на это можно закрыть глаза, но для кого-то это может быть критичным.
Помимо плагинов в JMeter можно использовать любые Java библиотеки. В k6 можно подключать библиотеки JS, но есть нюансы. Согласно документации не все модули будут работать с инструментом, я пробовал только JSLib, проблем не возникло.
Функционал
Про функционал k6 уже много сказано и написано, все это в разных видах пересказ документации. Уберем посредников — пользуйтесь https://k6.io/docs/using-k6/. Так что не буду еще раз повторять, что весь функционал по параметризации, корреляции и проверкам, которым вы привыкли пользоваться в JMeter, есть и в k6. Лучше чуть подробнее остановлюсь на отличиях, которые, как мне кажется, важно знать:
Assertions в JMeter vs Check в k6. Проверки в k6 не меняют статус запроса, вместо этого они порождают отдельную метрику. Статус запроса определяется только по коду ответа, включить в сам запрос дополнительные проверки пока нельзя https://k6.io/docs/javascript-api/k6-http/setresponsecallback. Поначалу это показалось мне неудобным, но немного поработав в таком стиле, я понял, что все это вопрос вкуса и наличие разных подходов — это даже хорошо. После работы с k6 у меня возникли идеи по улучшению проверок и мониторинга в скриптах JMeter.
Работа с переменными. Многие из вас знакомы с такими сущностями JMeter как vars и props. С vars в k6 все очевидно — это просто переменные, главное следить за зоной их видимости. С props сложнее — контексты виртуальных пользователей в k6 независимы друг от друга, и передать что-нибудь из потока в поток нельзя (а ведь под капотом go, там есть каналы...). Обойти это можно использованием Redis или чего-нибудь другого в том же духе.
Общая структура и читаемость. Запросы нагляднее представлены в дереве JMeter, хотя логические ветвления скрипта мне проще воспринимать в виде кода, чем в виде графических элементов. Возможно, это вкусовщина и дело привычки, а возможно действительно приходится делать меньше движений глазами и проще сохранять общий контекст. При этом не могу сказать, что в JMeter это причиняет невыносимую боль. Если придерживаться одного формата скриптов, то вопроса читаемости не возникнет. Один из вариантов упрощения восприятия скрипта — придерживаться политик наименования элементов. Например, если в If Controller проверяется переменная myAwesomeVar, то имеет смысл называть элемент в формате [IF] myAwesomeVar.
Метрики. А вот балл за метрики можно смело отдать k6. Да, JMeter не вызывает каких-то неудобств в части мониторинга — есть разные интеграции для мониторинга в реальном времени, набор самих метрик достаточен для анализа поведения системы, но когда возникает необходимость вывести какую-нибудь метрику, выходящую за рамки SampleResult, например, количество заявок в определенном статусе, придется придумывать ad-hoc-решения. Все они сводятся примерно к следующему — добавление JSR с каким-то кодом. В k6 же кастомные метрики — это часть основного функционала, и их использование лаконично встроено в инструмент. Коробочные метрики k6 кое-где не дотягивают до JMeter, например, нельзя получить описание ошибки — для этого нужно создавать отдельную метрику и обрабатывать ее вручную. На выходе в Grafana для k6 удалось создать ровно такой же дашборд, который мы используем для JMeter.
Переиспользование кода. Include/Module-контроллеры против функций. Хорошо, что в JMeter это хоть как-то реализовано, но это не идет ни в какое сравнение с разработкой скриптов в виде кода. Да, существует JMeter DSL, но это отдельная история, будем придерживаться ванильной версии.
Настройка. Благодаря jmeter-maven-plugin можно управлять зависимостями, параметрами, запускать тест. С k6 об этом нужно думать самому — скачивать нужные библиотеки на новый генератор, не забывать при запуске передавать переменные и т.д. Всего этого можно избежать, используя докер, но у нас такой потребности пока не возникло.
Большинство недостатков каждого инструмента можно компенсировать либо выработкой определенного подхода к написанию скриптов, либо использованием дополнительного инструментария, все сводится к контексту применения.
Про регулировку нагрузки. Если кратко — удобнее в JMeter, функциональнее в k6. Чуть подробнее: в JMeter наглядно видно какой будет подаваемая нагрузка, хотя при передаче параметров из командной строки тоже придется подключить воображение. В случае k6 придется потратить чуть больше времени, чтобы освоить и отладить регулировку нагрузки. Но в обоих случаях это разовая активность, которую легко скрыть под капотом автоматизации. Например, из всех режимов подачи нагрузки (executors) k6 я вряд ли буду использовать что-то кроме ramping-arrival-rate. И для упрощения запуска написал небольшую функцию, которая принимает параметры уровня нагрузки, количества ступеней и длительности (все как в Concurrency Thread Group) с последующим преобразованием их в параметры k6.
Что действительно заметно — k6 умеет более точно регулировать подаваемую нагрузку. Разберем на примере:
в скрипте JMeter я использую Concurrency Thread Group и Constant Throughput Timer в режиме all active threads in current thread group (shared), что в теории должно обеспечить компенсацию задержки одних тредов ускорением других;
в k6 — ramping-arrival-rate.
Возьмем этап разгона теста, в течение которого время отклика периодически возрастало. В случае JMeter каждая ступень это 50 потоков, пытающихся подать нагрузку в 1000 операций в час. У k6 preAllocatedVUs = 100 и maxVUs = 500, также по 1000 операций в час на ступень.
Как видно из графиков выше, k6 немного лучше поддерживает заданный уровень нагрузки, главное инициализировать достаточное количество потоков (параметр preAllocatedVUs).
Потребление ресурсов
Для сравнения потребления ресурсов каждым инструментом было проведено по два классических теста — максимум и надежность. Немного методики тестирования:
Поиск максимума — 10 ступеней по 1000 операций в час (~32 запроса в секунду). Длительность каждой ступени — 1 час.
Надежность — 24 часа на уровне 5000 операций в час (~160 запросов в секунду).
Тесты запускались на генераторе с 4 ядрами CPU и 32 Гб памяти.
Версии, на которых экспериментировал:
Apache JMeter — 5.5
k6 — v0.40.0
xk6 — v0.7.0
xk6-output-prometheus-remote — v0.0.4
При запуске k6 использовался аргумент --no-summary. Также для улучшения производительности в документации k6 рекомендуют применять параметр --no-thresholds и --compatibility-mode=base. Но первый я не использовал, так как в данном тесте не устанавливал никаких пороговых значений. А при добавлении второго скрипт начал падать на инициализации. Я посчитал нецелесообразным искать очередной костыль и провел оптимизацию процесса настройки k6 в части затрачиваемого времени. К тому же информация о пользе этого параметра весьма противоречива: в документации говорят, что это «the most impactful option to improve k6 performance», а в официальном блоге «unless you have a memory problem it's not worth using this mode when running k6». (Спойлер: исходя из результатов тестов, я склоняюсь ко второму варианту). С другими оптимизациями можно ознакомиться в этой статье.
Графики выполнения операций, времени отклика и утилизации ресурсов:
Максимум на JMeter
Максимум на k6
Надежность на JMeter
Надежность на k6
В ходе теста надежности я заметил, что k6 с плагином мониторинга протекает, и запустил тест без него, чтобы избавиться от погрешностей. Утилизация CPU стабилизировалась, но память все еще медленно, но верно куда-то исчезает:
Информация с графиков, сведенная в таблицу:
Инструмент | JMeter | k6 | ||
CPU | Memory | CPU | Memory | |
Максимум | 35% | 22% (-Xmx=4G) | 35% | 16% |
Надежность | 18% | 17% (-Xmx=2G) | 6% → 10% | 7% → 20% |
Надежность без плагина | - | - | 6% | 7% → 13% |
Результаты тестов k6 у меня вызывают два вопроса: «Чем обусловлен резкий рост утилизации CPU на последней ступени поиска максимума» и «Что происходит с памятью в тесте без плагина мониторинга». Для анализа такого поведения интересно было бы написать плагин для k6, выводящий метрики самого go и позволяющий запустить pprof, но тонкое изучение внутренностей k6 выходит за рамки текущей статьи.
Автоматизация
В плане автоматизации принципиальных различий нет. Оба инструмента предоставляют достаточный cli для автоматизации всех процессов через Jenkins. Один из первых вопросов, который у вас может возникнуть — а как же распределенный запуск? Да, в JMeter он есть, а в k6 как бы нет, но на практике JMeter мы управляем через Jenkins — просто запускаем несколько инстансов с нужными параметрами нагрузки. Поэтому никаких неудобств при попытке автоматизировать запуск k6 не возникло, скорее даже наоборот — тест показал, что весь наш профиль прекрасно умещается на один генератор и необходимости в распределенном запуске просто нет. Если же у вас все-таки возникнет необходимость запуска нескольких инстансов k6, то сделать это можно аналогично нашему примеру JMeter. Главное — настроить централизованный мониторинг и помнить, что трешхолды будут высчитываться независимо на каждом экземпляре k6 https://k6.io/docs/testing-guides/running-large-tests/#distributed-execution.
Открытый финал
Поскольку статья не писалась с целью сделать выбор за вас, то ответа какой инструмент использовать, тут не будет. Всегда нужно выбирать исходя из задач и контекста, а не слепо копировать чужой опыт и гнаться за трендами. Но надеюсь, что вы смогли чем-то вдохновиться и стать чуть более готовыми ко встрече с k6, если все же решитесь его попробовать.
Однако небольшое резюме я все же выведу. Если ваша нагрузка сводится к web-протоколам, то технически оба инструмента смогут закрыть ваши потребности, и выбор будет зависеть от окружения — процесс НТ, квалификация инженеров и т.д. Если же требуются работа с MQ или БД, то каждый случай стоит рассматривать индивидуально. В JMeter есть плагины, они проверены, но часто приходится писать код. В k6 тоже есть плагины, но они не проверены. Наилучший выход — сравнить. По поводу потребления ресурсов — опять же зависит от того, насколько это для вас важно: если грузите сайт визитку, то с k6 много не выиграете, но в JMeter будет проще войти, а если необходима нагрузка в сотни тысяч RPS, то выигрыш в ресурсах от k6 может быть существенным.
В любом случае попробовать k6 для собственного развития и поиска новых идей однозначно советую.