Наша система хорошо покрыта unit-тестами, которые интегрированы в CI-процессы. Настроен запуск и контроль функциональных интеграционных тестов. После проделанной работы по обеспечению корректности выполнения бизнес-процессов возникли вопросы, связанные с производительностью, корректностью настройки компонентов системы, отказоустойчивостью, которые можно условно обрисовать, выделив основные из них:
Насколько корректно и оптимально настроены все модули системы?
Где порог отказоустойчивости наших сервисов и сторонних компонентов, используемых в решении?
Что именно мы можем гарантировать потребителю при различных условиях эксплуатации?
Сможем ли мы выдержать повышенные нагрузки - и если да, то какие именно?
Как внедрение нового подхода, инструмента, сервиса, а также изменения в конфигурации текущих сервисов повлияют на производительность продукта?
Также хотелось уйти от субъективных суждений вроде “система работает стабильно” и перейти к измеримым показателям: SLA, реальному времени обработки больших объёмов данных и подтверждённой доступности при пиковых нагрузках.
Выбор пал на такой инструмент нагрузочного тестирования, как K6. Коллеги уже имели опыт его использования и положительно отзывались о нем. Мы также предполагаем, что функционала K6 нам будет достаточно, чтобы решить основные моменты: найти и устранить узкие места, получить чёткие числовые показатели производительности системы.
Перед началом реализации нагрузочного тестирования продукта возникло множество вопросов, на которые необходимо было ответить ещё до старта.
В этом посте я хочу поделиться нашим опытом - рассказать, какие именно вопросы мы ставили и к каким выводам пришли в результате поиска ответов на поставленные вопросы.
Начнём со списка ключевых вопросов. Порядок не отражает приоритет - каждый из них тактически важен для построения корректного процесса нагрузочного тестирования:
Какие типы тестов мы будем реализовывать в рамках нагрузочного тестирования;
Как будут распределены тестовые сценарии по типам тестов и как сформулировать правило распределения для новых сценариев;
Как часто мы планируем запускать какие типы тестов;
На каких средах будет выполняться запуск;
Кто несёт ответственность за написание и поддержку тестов;
Какие сервисы мы исключим из тестирования, заменив их моками;
Как будут публиковаться и интерпретироваться отчёты.
Типы тестов
Для контроля качества продукта могут использоваться различные виды тестов - их список и описание представлены на изображении ниже.
Различия между ними можно провести в первую очередь по профилю запуска: количеству виртуальных пользователей, характеру роста нагрузки, длительности выполнения.
Однако в некоторых системах различия могут затрагивать не только профиль, но и набор тестовых сценариев, а также среду выполнения, которая может отличаться для разных типов тестов.

Профилирование тестов
Для формирования профиля запуска нагрузочного теста необходимо определить ключевые параметры системы:
количество CUs (Concurrent Users) - одновременно активные пользователи, этот параметр мы будем использовать в настройке VUs (Virtual users);
скорость роста нагрузки (Ramp-up);
скорость снижения нагрузки (Ramp-down);
длительность выполнения теста (Duration).
Concurrent users
В уже работающей (продакшн) системе этот показатель можно оценить с помощью аналитических инструментов - например, Google Analytics, или же используя внутренние хранилища данных, если они содержат информацию о длительности пользовательских сессий и количестве сессий в единицу времени.
Формула для расчёта выглядит следующим образом:
Hourly sessions * Average session duration(in seconds) / 3600
Например, если за час зафиксировано 176 сессий, а средняя продолжительность сессии составляет 1 минуту 13 секунд (73 секунды), то получаем:
Таким образом, в среднем около 3.6 пользователей одновременно конкурируют за ресурсы продукта.
Если система еще не находится в продакшн, здесь нужно ориентироваться на требования бизнеса с учетом перспектив на рост продукта.
Для формирования нагрузочных профилей мы выделили три характерных значения CU, рассчитанные по данным за последний месяц:
среднее значение (Average);
среднее верхнего квантиля (Above Average);
абсолютное максимальное значение (Peak).
Скорость роста и снижения нагрузки, длительность выполнения
Данные параметры выбираются исходя из типа теста, необходимости в получении результата в конкретный момент или через конкретный промежуток времени, состояния и наличия сред(ы) запуска, и итерационно после запуска нескольких тестов и оценки результатов.
Профиль тестов
Понятно, что этими параметрами профилирование не ограничивается, однако это базовые показатели, которые необходимо определить перед запуском нагрузочных тестов. Остальные параметры могут настраиваться в зависимости от специфики системы, целей теста и особенностей сценариев нагрузки. Для подбора базовых параметров профиля можно составить следующую таблицу, заполнить в соответсвии с вашими данными:

Если ваш продукт работает в закрытом контуре и количество пользователей остаётся стабильным во времени, то рассчитанных однажды значений будет достаточно.
Однако, если с течением времени наблюдается изменение количества пользовательских сессий, необходимо установить интервалы пересмотра профилей нагрузочного тестирования.
Разработка тестов
На данный момент для нашего продукта выделена отдельная среда для запуска функциональных тестов. Среда ограничена небольшим объёмом ресурсов; персистентные данные очищаются 1-2 раза в неделю скриптами либо при пересоздании стенда.
Тесты запускаются постоянно в течение дня и включают как компонентные, так и интеграционные тесты (без учёта «первой мили»). Такой подход гарантирует, что система корректно выполняет ключевые бизнес-процессы, а изменения в коде не нарушают существующий функционал.
Функциональные тесты пишутся командой разработки. Иногда они могут пересекаться с формальными тестовыми сценариями, но основная цель - сохранить взгляд разработчика на узкие места его решения.
Реализация автоматизированных тестов по тестовым сценариям остается QA-специалистам, чтобы сохранить независимую оценку качества. Сейчас такие тесты запускаются локально, параллельно с мануальным тестированием.
При распределении ответственности за написание и поддержку нагрузочных тестов хотелось сохранить аналогичный подход:
Компонентные нагрузочные тесты и End-to-End Protocol (интеграционные API) тесты - ответственность бэк-разработчика. Такой подход позволяет изолировать узкие места во время тестирования;
End-to-End Browser тесты - ответственность фронт-разработчика;
End-to-End тесты (Browser и Protocol) - ответственность QA, тесты реализуются по формальным тестовым сценариям.
Ниже схематически изображены точки приложения различных типов тестов:

Далее представлена таблица, которая отображает протоколы взаимодействия с компонентами системы и ответсвенность членов команды за реализацию тестов:

В целом, ожидается, что запуск нагрузочных тестов будет осуществляться преимущественно по сценарным, т.е. тем, которые будут реализовавытаться QA. Для изоляции узких мест при этом планируется периодический запуск веб, компонентных и интеграционных тестов с использованием тех же профилей нагрузки, написанных командой разработки.
Из-за удобства реализации тестов на уровне API, в отличие от тестов, которые проверяют взаимодействие клиента с веб-интерфейсом, а также ввиду наличия у нас узкого клиента с небольшой бизнес-логикой, мы сосредоточились именно на API-тестах.
Написание и поддержка интеграционных нагрузочных тестов или тестов через веб-интерфейс со стороны команды разработки носит спорадический характер - выполняется, если разработчик видит потенциальную проблему в логике или для изоляции проблемы, выявленной в результате нагрузочного тестирования.
Для того, чтобы ознакомиться с основными библиотеками K6 можно пройти по ссылкам ниже:
Запуск тестов
Можно сформулировать базовые правила для определения того, как часто какой тип тестов запускать, но любые формальные правила сталкиваются с практическими ограничениями:
Наличие адекватных сред(ы) для запуска тестов;
Возможность безопасного запуска тестов на продакшн;
Наличие человеческих и/или автоматизированных ресурсов для управления средами, для запуска, поддержки и расширения тестов;
Наличие процессов и контроля, а также чёткой регламентации триггеров для запуска каждого типа тестов.
В идеальном сценарии запуск тестов следует выполнять на выделенной для этого отдельной среде, но на практике такая возможность есть не всегда.
Среды окружения
На текущий момент в нашем распоряжении есть несколько сред:
Dev - среда разработки, куда попадают изменения из мастер-ветки (master у нас считается основной веткой разработки). Среда ограничена по ресурсам;
Test - среда для прогонки функциональных тестов, также ограничена по ресурсам, в нее попадеют изменения из мастер ветки;
Staging - среда используется для тестирования и подготовки релизов, по ресурсам соответсвует продакшн среде;
Pre-production - среда, в которой одновременно с продакшном размещается актуальный релиз, нужна, чтобы уточнять и исправлять проблемы, найденные на Production среде, ограничена по ресурсам;
Production - основная среда.
Ограничения запуска
Полный цикл тестов на каждой из этих сред нецелесообразен. Это связано как с техническими ограничениями сред, так и с особенностями самих тестов.
Основные причины:
Нагрузочные тесты создают большое количество данных, а в системе не реализован hard delete по всем сущностям, поэтому накопленные данные приходится периодически очищать полностью, а не выборочно.
Не на всех средах возможен запуск тестов в режиме записи (например, в продакш среде возможен запуск только в read-only режиме).
Запуск тестов с нагрузкой выше средней на средах, ресурсы которых не соответствуют продакшну, не даёт объективных результатов.
Проблемы с производительностью важно выявлять до выхода новой версии в продакшн.
Тесты типа soak (длительные нагрузочные проверки) занимают много времени и требуют устойчивой инфраструктуры, что доступно не на всех средах.
Исходя из описанных условий, можно сформировать таблицу с расписанием запуска тестов для разных сред. Также важно учитывать, что данные, необходимые команде QA для ручного тестирования, должны создаваться автоматически - с помощью специальных скриптов, запускаемых перед началом тестирования QA. Это необходимо, поскольку тестовые окружения будут периодически очищаться, соответсвенно, сохранение данных между циклами тестов не гарантируется.
При наличии отдельной среды таких проблем можно было бы полностью избежать, как и необходимости запускать высоконагруженные тесты только в часы, когда средой не пользуются. Но в нашем распоряжении есть ограниченное количество и качество сред, исходя из этого и строится панирование.
Smoke-тесты запускаются при каждом изменении в соответствующих ветках. Тесты средней нагрузки выполняются на среде, конфигурация и настройки которой соответствуют production-среде. Высоконагруженные тесты также проводятся на такой среде, но только в нерабочее время. На production-среде запускаются тесты с нагрузкой немного ниже средней и исключительно в режиме read-only.
Для проведения длительного soak-теста используется стабильная pre-production среда, так как этот тест не дает быстрых результатов и потому не подходит для этапа подготовки релиза. Однако он крайне полезен для выявления возможных проблем, которые могут возникнуть в production-среде уже сейчас.
Важно помнить, что триггером для запуска цикла нагрузочных тестов должны быть не только изменения в коде, но и:
внедрение новых инфраструктурных сервисов;
обновление версии языка программирования;
апгрейд существующих инфраструктурных сервисов;
изменение конфигурации инфраструктуры;
корректировка настроек собственных сервисов;
изменения на серверах, где развернута система.
Ниже представлена таблица, описывающая запуск различных тестов на средах:

Таймлайн запуска тестов на Staging может выглядеть следующим образом:

Сторонние интеграции и сервисы
В современных системах нередко используются интеграции со сторонними сервисами - например, с платёжными шлюзами, а также SaaS-решениями, такими, как Elastic Cloud. Более того, сама инфраструктура может быть полностью развернута в облаке, например, в AWS, с использованием готовых управляемых сервисов.
При запуске нагрузочных тестов стоит учитывать подобные зависимости: итоговая стоимость испытаний может оказаться неожиданно высокой или привести к сбоям в работе системы - например, если на облачном аккаунте закончились средства.
Что касается инфраструктурных сервисов, для проведения тестов идеальной считается среда, максимально повторяющая production по ресурсам и по конфигурации. Однако, на практике это не всегда возможно - многое зависит от бюджета и доступных мощностей.
В некоторых случаях разумным компромиссом может стать развёртывание среды в более дешёвом ЦОД, но при этом важно помнить: запуск тестов в среде, отличающейся от production, может привести к увеличению ложно-положительных или ложно-отрицательных результатов. Особенно, если поведение системы зависит от конкретной версии или особенностей инфраструктурного сервиса, адаптированного под облако.
Что касается сторонних интеграций, то здесь, как правило, приходится идти на определённые допущения - например, замокать внешние сервисы, ориентируясь на их характеристики, такие как SLA и среднее время отклика.
Некоторые провайдеры предоставляют тестовые среды или sandbox-доступ, но в каждом конкретном случае рекомендуется уточнить у поддержки, возможно и репрезентативно ли использование такого доступа для задачей нагрузочного тестирования на вашей стороне, а вам решить - целесообразно ли включать такую интеграцию в ваш тестовый цикл.
Чтобы минимизировать риск ошибок и нестабильности в случае работы с внешними сервисами, стоит отдавать предпочтение интеграциям с высоким SLA, доказанной надёжностью и хорошей репутацией, при этом использовать моки данных сервисов для задачи нагрузочного тестирования.
Распределение тестовых сценариев
Как уже упоминалось ранее, разработка тестов на основе тестовых сценариев - зона ответственности команды QA. В большинстве случаев можно использовать единый набор сценариев для всех типов нагрузочных тестов, варьируя лишь уровень нагрузки и длительность их выполнения. Такой подход позволяет выявлять проблемы на всех этапах выполнения бизнес-операций — от лёгкой до экстремальной нагрузки.
Если же есть ограничение по времени или по ресурсам, имеет смысл разделить тестовые сценарии на категории:
Основные — сценарии, охватывающие основные пользовательские операции и используемые в каждом прогоне.
Периодические — сценарии, выполняемые редко, например, если система раз в месяц проводит перерасчёт баланса, выполняет статистический анализ или партиционирование данных. Такие тесты можно исключать из smoke-прогонов.
Регрессионные — сценарии, написанные разработчиками для контроля за производительностью ситемы после устранения ранее выявленных проблем. Эти тесты также могут быть исключены из регулярных smoke-тестов, но полезны для stress и breakpoint запусков.
Результаты тестирования
Нет смысла писать и выполнять нагрузочные тесты, расходуя ресурсы системы, деньги компании и время команды, если отсутствует эффективный инструмент анализа и процесс реагирования на результаты тестирования.
Практика показывает, что наибольшую ценность нагрузочное тестирование приносит тогда, когда:
результаты наглядно визуализированы с помощью удобных графиков и дашбордов;
система алертинга настроена так, чтобы уведомлять только о действительно важных отклонениях;
уведомления получают только те участники команды, которые несут ответственность за тесты и могут оперативно реагировать на проблемы, выявленные на этапе тестирования.
Хорошо настроенный процесс мониторинга и анализа - это не вспомогательная часть нагрузочного тестирования, а его неотъемлемая составляющая, без которой невозможно сделать корректные выводы о стабильности и производительности системы.
В K6 анализ агрегированных данных можно настроить с помощью thresholds - пороговых значений, которые определяют критерии успешности теста. Вот пример из официальной документации:
export const options = {
thresholds: {
// http errors should be less than 1%
'http_req_failed': ['rate<0.01'],
// 90% of requests should be below 600ms
'http_req_duration': ['p(90)<600'],
// 95% of requests tagged as static content should be below 200ms
'http_req_duration{type:staticContent}': ['p(99)<250'],
// the error rate of my custom metric should be below 5%
'my_custom_metric': ['rate<0.05'],
},
};
Кроме thresholds, для контроля корректности результатов используются checks и assertions, ошибки, выявленные в результате их прменения не останавливают выполнение тестов, но влияют на финальный результат тестирования.
Полезные ссылки на официальную документацию:
Поскольку запуск нагрузочных тестов может привести к ложно-положительным и ложно-отрицательным результатам (например, из-за временных проблем на сетевом уровне, накопления данных после предыдущих прогонов, несоответствия инфраструктуры и т.д.), это необходимо учитывать при планировании тестирования и в анализе его результатов. Вот несколько советов, которые помогут улучшить анализ:
Запуск нескольких прогонов нагрузочных тестов в течение дня - не стоит ориентироваться на результат запуска только одного прогона нагрузочных тестов. Чтобы уменьшить влияние случайных факторов, следует запускать различные виды тестов более одного раза.
Агрегация результатов - вместо того, чтобы отправлять уведомления о проблемах, связанных с запуском теста, стоит агрегировать результаты нескольких запусков, и только на основании более длительного наблюдения принимать решение об уведомлении о проблемах.
Настройка алертов - можно настроить пороговые значения, чтобы информировать команду об отклонениях, на которые стоит обратить внимание и о явных отклонениях, которые требуют быстрой реакции.
Использование метрик из разных источников - помимо метрик из тестирования, полезно агрегировать и инфраструктурные метрики (CPU, память, использование дисков и сети), чтобы выявить, если проблемы с производительностью связаны с недостаточными ресурсами или ограничениями инфраструктуры.
Визуализация - эффективный инструмент для анализа и наблюдения за результатами тестирования, в билиотеке Grafana Labs можно найти готовые дашборды для K6 для певого этапа запуска.
Важно воспринимать результаты тестов не как абсолютные данные, а как индикаторы трендов, которые могут дать полезные сведения, но только в контексте общей картины системы. Например, даже если нагрузочные тесты показывают нестабильность в одном запуске, это не обязательно указывает на серьезные проблемы, особенно если система была стабильной в предыдущих прогонах.
Вывод
Нагрузочное тестирование - ключевой инструмент для понимания реальной стабильности и возможностей вашего продукта. Чтобы он действительно работал, недостаточно просто запустить сценарии: нужно правильно выбрать типы тестов, составить расписание запусков, настроить среду, продумать набор тестовых сценариев, а также организовать наглядное отображение результатов и эффективный алертинг.
Только при комплексном подходе можно не просто выявлять проблемы, но и предсказывать поведение системы под нагрузкой, а также общаться с клиентами конкретными цифрами, опираясь на реальные метрики.
Внедрение нагрузочного тестирования, помимо прочего, дает большой задел на тестирование разнообразных продуктов и подходов для улучшения производительности вашего решения. Кроме того, качественный продукт всегда мотивирует команду, которая его создаёт. Даже если сейчас у вас нет задачи эксплуатировать продукт под большой нагрузкой, но есть ресурсы для реализации минимального набора нагрузочного тестирования, то это станет отличным заделом на будущее - и для развития продукта, и для поддержания мотивации команды.