Нагрузочное тестирование в разработке веб-приложений

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

Введение в нагрузочное тестирование

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

Основные методики нагрузочного тестирования

Пиковая нагрузка (Spike Testing): Данный метод фокусируется на оценке поведения системы при резком увеличении нагрузки. Он позволяет определить, насколько стабильно приложение работает при внезапных всплесках пользовательской активности.

Длительное тестирование (Soak Testing): Этот метод используется для проверки устойчивости системы при стабильной нагрузке на протяжении продолжительного времени. Он помогает выявить долгосрочные проблемы, такие как утечки памяти или сбои в работе базы данных.

Стресс-тестирование (Stress Testing): Этот тип тестирования оценивает производительность системы при нагрузке, превышающей ожидаемую. Цель состоит в том, чтобы определить точку, при которой приложение начинает сбоить, и как быстро оно восстанавливается после перегрузки.

Постепенное увеличение нагрузки (Ramp-Up Testing): В рамках этой методики нагрузка на систему увеличивается постепенно до достижения определенного уровня. Это позволяет выявить пороговые значения, при которых начинаются проблемы с производительностью.

Инструменты для нагрузочного тестирования Java веб-приложений

Для эффективного нагрузочного тестирования Java веб-приложений существует множество инструментов, каждый из которых обладает своими особенностями и преимуществами. Ниже представлены наиболее востребованные из них:

Apache JMeter: Один из наиболее популярных инструментов для проведения нагрузочного тестирования, поддерживающий различные протоколы, включая HTTP, HTTPS, SOAP и JDBC. JMeter предоставляет широкий функционал для создания сложных тестовых сценариев и последующего анализа результатов. Универсальность и мощные аналитические возможности делают его предпочтительным решением для нагрузочного тестирования веб-приложений.

# Пример запуска теста в JMeter
jmeter -n -t test_plan.jmx -l results.jtl -e -o /path/to/output/folder

Gatling: Этот инструмент, разработанный на языке Scala, известен своей высокой производительностью и возможностью масштабирования тестов. Gatling предлагает удобный DSL (Domain-Specific Language) для создания сценариев тестирования и обладает мощными средствами анализа, что делает его идеальным для проведения сложных и ресурсоемких нагрузочных тестов.

class BasicSimulation extends Simulation {
  val httpProtocol = http.baseUrl("http://localhost:8080")
  val scn = scenario("Basic Scenario")
    .exec(http("request_1").get("/"))
  setUp(scn.inject(atOnceUsers(1000)).protocols(httpProtocol))
}

Locust: Инструмент для нагрузочного тестирования, написанный на Python. Locust позволяет разрабатывать тестовые сценарии на языке Python, что делает его особенно привлекательным для разработчиков, уже знакомых с этим языком. Он легко интегрируется с другими системами и предлагает гибкие возможности управления нагрузкой, что делает его подходящим для различных типов проектов.

from locust import HttpUser, TaskSet, task

class UserBehavior(TaskSet):
    @task
    def index(self):
        self.client.get("/")

class WebsiteUser(HttpUser):
    tasks = [UserBehavior]
    min_wait = 5000
    max_wait = 9000

Apache Benchmark (ab): Это легковесный инструмент, предназначенный для быстрого выполнения базовых тестов производительности веб-серверов. Apache Benchmark отличается простотой использования и подходит для быстрой оценки производительности веб-приложений в условиях базовой нагрузки. Однако, его функционал ограничен, и он не предназначен для выполнения сложных сценариев тестирования.

# Пример запуска теста с 1000 запросами и 100 параллельными пользователями
ab -n 1000 -c 100 http://localhost:8080/

Пример нагрузочного тестирования с использованием Apache JMeter

Ниже приводится подробный пример выполнения нагрузочного тестирования Java веб-приложения с использованием Apache JMeter. Этот пример охватывает все ключевые этапы: от установки JMeter до анализа результатов тестирования.

Установка Apache JMeter

  1. Загрузка и установка: Загрузите последнюю версию Apache JMeter с официального сайта.

  2. Запуск JMeter: После установки запустите JMeter с помощью команды jmeter в командной строке.

Создание тестового плана

  1. Создание нового тестового плана: Откройте JMeter и создайте новый тестовый план.

  2. Добавление группы потоков (Thread Group): Группа потоков определяет количество пользователей, время разгона (Ramp-Up) и количество циклов выполнения теста.

ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("Example Thread Group");
threadGroup.setNumThreads(100);  // Указание числа пользователей (потоков)
threadGroup.setRampUp(10);       // Время разгона в секундах
threadGroup.setLoopCount(1);     // Количество циклов

Добавление элементов к тестовому плану

HTTP Request Sampler: Добавьте HTTP Request Sampler для задания HTTP-запросов к тестируемому веб-приложению. Укажите домен, порт, путь и метод запроса.

HTTPSampler httpSampler = new HTTPSampler();
httpSampler.setDomain("example.com");
httpSampler.setPort(80);
httpSampler.setPath("/api/test");
httpSampler.setMethod("GET");

Конфигурация тестового плана: Добавьте настроенную группу потоков и HTTP Request Sampler в тестовый план.

TestPlan testPlan = new TestPlan("Example Test Plan");
testPlan.addThreadGroup(threadGroup);
threadGroup.addSampler(httpSampler);

Добавление слушателей для анализа результатов

View Results Tree: Этот элемент позволяет визуализировать результаты выполнения каждого запроса в виде дерева.

ViewResultsTree resultsTree = new ViewResultsTree();
testPlan.add(resultsTree);

Summary Report: Предоставляет сводные статистические данные о результатах тестирования.

SummaryReport summaryReport = new SummaryReport();
testPlan.add(summaryReport);

Запуск теста и анализ результатов

Запуск теста: Настройте и запустите тестовый план с помощью движка JMeter.

JMeterEngine jmeterEngine = new StandardJMeterEngine();
jmeterEngine.configure(testPlan);
jmeterEngine.run();

Анализ результатов: После завершения теста проанализируйте результаты для выявления узких мест и потенциальных точек отказа.

for (SampleResult result : resultsTree.getResults()) {
    System.out.println("Response time: " + result.getTime());          // Время ответа
    System.out.println("Response code: " + result.getResponseCode());  // Код ответа сервера
}

Этот пример демонстрирует, как можно использовать Apache JMeter для создания и выполнения нагрузочного тестирования веб-приложений на Java, а также для анализа результатов тестирования с целью улучшения производительности и надежности системы.

Преобразование нагрузки в RPS (Запросы в секунду) в Apache JMeter

Когда нагрузка на систему должна выражаться в запросах в секунду (RPS) вместо количества пользователей, подход к настройке тестового плана в Apache JMeter требует некоторых изменений. Это особенно полезно, когда нагрузка на различные адреса может отличаться при параллельном выполнении запросов. Рассмотрим пошаговую инструкцию по настройке такого теста.

Шаг 1: Настройка переменной load_msg_sec

  1. Добавление переменной в тестовый план:

    • Кликните на корневой элемент тестового плана в JMeter.

    • Добавьте новую переменную с именем load_msg_sec. Название переменной можно выбрать любое, но для ясности будем использовать load_msg_sec.

  2. Установка значения переменной:

    • В значении переменной можно указать как константное значение, так и параметризованное. Для параметризованного значения в столбце Value введите следующее:

${__P(load, 50)}

Здесь load — это название параметра, а 50 — значение по умолчанию, если параметр не передан.

Шаг 2: Добавление Constant Throughput Timer

  1. Добавление таймера:

  2. Настройка таймера:

${__jexl3(${load_msg_sec} * 60 * #percent#)}

Здесь:

  • ${load_msg_sec} — количество запросов в секунду, определённое переменной load_msg_sec.

  • 60 — коэффициент для перевода запросов в секунду в запросы в минуту (так как JMeter ожидает значения в запросах в минуту).

  • #percent# — процент от общего количества запросов, который будет применяться при параллельном тестировании нескольких адресов. Этот параметр определяется согласно методике нагрузочного тестирования.

Шаг 3: Настройка Basic Authentication

При тестировании на стендах, защищённых базовой аутентификацией (Basic Auth), необходимо настроить корректную отправку заголовка авторизации, чтобы избежать ошибок 403.

  1. Добавление BeanShell PreProcessor:

    • Кликните на вашу группу потоков (Thread Group).

    • Добавьте BeanShell PreProcessor: RBMAddPre ProcessorsBeanShell PreProcessor.

    • Вставьте следующий код в BeanShell PreProcessor:

    import org.apache.commons.codec.binary.Base64;
    byte[] encodedUsernamePassword = Base64.encodeBase64("login:password".getBytes());
    vars.put("base64HeaderValue", new String(encodedUsernamePassword));
    
  2. Добавление HTTP Header Manager:

    • В той же группе потоков добавьте HTTP Header Manager.

    • Добавьте следующий заголовок:

    • Переменная ${base64HeaderValue}, используемая в значении заголовка, была установлена в BeanShell PreProcessor.

Лучшие практики нагрузочного тестирования

  1. Реалистичные сценарии: Тестовые сценарии должны максимально приближаться к реальным условиям эксплуатации приложения. Это позволяет точно моделировать нагрузки и предсказывать поведение системы, выявляя потенциальные проблемы до того, как они появятся в реальной эксплуатации.

  2. Мониторинг и анализ: Эффективное нагрузочное тестирование требует тщательного мониторинга системных ресурсов и анализа полученных данных. Использование специализированных инструментов позволяет оперативно выявлять узкие места и принимать меры для их устранения, тем самым оптимизируя производительность системы.

  3. Регулярность тестирования: Постоянное проведение нагрузочных тестов необходимо для своевременного выявления проблем, особенно после внесения изменений в код или инфраструктуру. Регулярное тестирование помогает поддерживать высокую стабильность и производительность приложения.

  4. Интеграция с CI/CD: Включение нагрузочного тестирования в процессы непрерывной интеграции и доставки (CI/CD) обеспечивает автоматический контроль производительности после каждого изменения кода. Это позволяет минимизировать риск появления проблем с производительностью в продакшене.

  5. Комбинирование инструментов: Использование различных инструментов для нагрузочного тестирования (например, Apache JMeter, Gatling, Locust) помогает получить всесторонний обзор производительности приложения. Это позволяет выявлять разные аспекты поведения системы под нагрузкой и разрабатывать более комплексные решения для их оптимизации.

  6. Многоуровневое тестирование: Проведение тестов при различных уровнях нагрузки — от минимальной до экстремальной — позволяет глубже понять поведение приложения и определить его пределы. Это помогает выявить, как система реагирует на разные сценарии нагрузки и где могут возникнуть проблемы.

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

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

  9. Сотрудничество с командой: Эффективное тестирование требует тесного взаимодействия с разработчиками и другими заинтересованными сторонами. Совместная работа позволяет быстро реагировать на обнаруженные проблемы и вносить улучшения в продукт.

Пример протокола нагрузочного тестирования веб-приложения

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

1. Объект испытаний

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

2. Цели испытаний

  • Проверка соответствия разработанной системы предъявляемым требованиям.

  • Определение максимальной и пиковой производительности системы.

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

3. Базовый профиль

Базовый профиль тестирования включает в себя следующие параметры, рассчитанные на основе требований к производительности и предполагаемого пользовательского поведения:

Код

Требование

Значение

1

Общее количество запросов к системе в месяц

30 000 000

2

Пиковое количество запросов в месяц

3 300 000

3

Пиковое количество запросов в неделю

900 000

4

Пиковое количество запросов в день

110 000

5

Количество пользователей в месяц

300 000

6

Количество пользователей в неделю

60 000

7

Количество пользователей в день

10 000

Расчетное количество запросов в час на основе максимального количества запросов:

f33789ecef5ffb14ab0e1cc2598b2c34.png

На основе Customer Journey Map (CJM) выделены объекты, создающие основную нагрузку:

Объект

URI

Процент запросов в час

Количество запросов в час

Объект 1

/

27%

11 340

Объект 2

/…/

15%

6 300

Объект 3

/…/

11%

4 620

Объект 4

/…/

5%

2 100

Объект 5

/…/

16%

6 720

Объект 6

/…/

5%

2 100

Объект 7

/…/

6%

2 520

Объект 8

/…/

15%

6 300

Нагрузку на объекты распределили пропорционально целевой аудитории. Запросы распределяются равномерно в течение часа:

959531bc7fa4ec5aa3bbe143d58d051f.png

4. План тестирования

  1. Разработка профиля нагрузочного тестирования.

  2. Настройка систем мониторинга для отслеживания производительности.

  3. Разработка скриптов генерации нагрузки.

  4. Подготовка тестовых данных (например, варианты значений для поисковых запросов) и создание скриптов для их загрузки в базу данных перед тестированием и очистки после.

  5. Проведение нагрузочных тестов.

  6. Анализ результатов нагрузочного тестирования.

5. Типы проводимых тестов

Поиск пиковой производительности

  • Нагрузка: ступенчатая.

  • Начальная нагрузка: 40% от базового профиля.

  • Шаг: увеличение на 30% от базового профиля.

  • Продолжительность ступени: 30 минут или до отказа системы.

  • Критерий завершения теста: отказ системы.

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

Проверка стабильности работы системы

  • Нагрузка: равномерная с плавным разгоном.

  • Максимальная нагрузка: базовый профиль.

  • Время разгона: 15 минут.

  • Длительность основного этапа: 4 часа.

  • Критерий завершения теста: истечение времени или отказ системы.

  • Критерий успешности: завершение теста по истечении времени без отказов системы.

ребования к производительности системы

1. Утилизация ресурсов во время тестов

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

Эти требования были установлены на основании экспертной оценки и служат ориентиром, поскольку специфические требования к системе не были предъявлены.

2. Требования к количеству неуспешных запросов

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

  • Тест поиска максимальной производительности (последняя ступень) — не более 0,1% от общего числа запросов.

  • Тест подтверждения максимальной производительности (в течение всего теста) — не более 0,1%.

  • Тест стабильности (в течение всего теста) — не более 0,1%.

Периоды агрегации результатов соответствуют периодам постоянной нагрузки в каждом из тестов.

3. Требования ко времени отклика и интенсивности типовых шагов бизнес-операций

Для обеспечения высокой производительности и быстрого отклика система должна удовлетворять следующим требованиям по времени отклика при выполнении типовых шагов бизнес-операций:

  • 90% запросов должны обрабатываться за время не более 50 мс.

  • 99% запросов должны обрабатываться за время не более 1000 мс.

  • 99,9% запросов должны обрабатываться за время не более 2500 мс.

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

Конфигурация стенда для Нагрузочного Тестирования (НТ)

Компонент

Значение

CPU Cores

XX

RAM

XXX GB

Поиск максимальной производительности

Максимальная производительность системы определяется как нагрузка, при которой утилизация ресурсов (CPU и RAM) начинает превышать допустимые границы, указанные в разделе «Требования по утилизации ресурсов». Поиск максимальной производительности проводится через серию тестов с пошаговым увеличением нагрузки относительно базового профиля.

Шаг увеличения нагрузки:
Шаг рассчитывается по формуле:
Шаг = Значение базового профиля * 0,4 = 4.8 ≈ 5 запросов в секунду

Продолжительность каждого шага составляет 30 минут.

Результаты тестов:

Шаг

RPS

CPU

RAM

Среднее время отклика (ms)

Минимальное время отклика (ms)

Максимальное время отклика (ms)

Ошибка %

Результат

1

12 q/s

31,5%

6%

853

342

2332

0,01%

Соответствует

2

17 q/s

44,8%

6%

823

339

3132

0,00%

Соответствует

3

22 q/s

58,6%

6%

851

353

2041

0,00%

Соответствует

4

27 q/s

73,6%

7%

841

337

2652

0,00%

Соответствует

5

32 q/s

88,0%

7%

764

342

2332

0,00%

CPU превышает допустимый порог, производительность максимальна

Графики:

Шаг 1

График результатов теста из jMeter

График результатов теста из jMeter

График использования ЦПУ (Grafana)

График использования ЦПУ (Grafana)

График использования ОЗУ

График использования ОЗУ

Шаг 2

График результатов теста из jMeter

График результатов теста из jMeter

График использования ЦПУ (Grafana).png

График использования ЦПУ (Grafana)

График использования ОЗУ.png

График использования ОЗУ

Шаг 3

График результатов теста из jMeter.png

График результатов теста из jMeter

График использования ЦПУ (Grafana).png

График использования ЦПУ (Grafana)

График использования ОЗУ.jpg

График использования ОЗУ

Шаг 4

График результатов теста из jMeter.png

График результатов теста из jMeter

График использования ЦПУ (Grafana).png

График использования ЦПУ (Grafana)

Шаг 5

График результатов теста из jMeter.png

График результатов теста из jMeter

График использования ЦПУ (Grafana).png

График использования ЦПУ (Grafana)

График использования ОЗУ.png

График использования ОЗУ

Вывод: Максимальная производительность системы составляет XX запросов в секунду, что превышает ожидаемую нагрузку на XX%. Система удовлетворяет требованиям производительности.

Тест на стабильность системы

Тест на стабильность проводится при нагрузке, соответствующей базовому профилю, на протяжении длительного времени (4 часа).

Результаты теста на стабильность:

RPS

CPU

RAM

Среднее время отклика (ms)

Минимальное время отклика (ms)

Максимальное время отклика (ms)

Ошибка %

Результат

12 q/s

33,6%

7%

503

343

4116

0,00%

Соответствует

Графики:

График результатов теста из jMeter

График результатов теста из jMeter

График использования ЦПУ (Grafana)

График использования ЦПУ (Grafana)

График использования ОЗУ

График использования ОЗУ

Вывод: На протяжении 4 часов тестирования стабильности системы процент отказов составил 0%, нагрузка равномерно распределена. Система удовлетворяет описанным показателям производительности.

© Habrahabr.ru