Проведение нагрузочного тестирования и анализ метрик
Большинство современных приложений сталкивается с проблемой изменчивой нагрузки: снижением или резким ростом числа одновременных пользователей, количества запросов в секунду и т. д. С такой ситуацией могут столкнуться онлайн-магазины в «чёрную пятницу» или сайты новостных изданий в моменты глобальных катастроф.
Если приложение не будет готово, чрезмерная нагрузка может привести к деградации производительности, полному или частичному отказу системы. Это всё снижает уровень удовлетворённости пользователей, ведёт к потере доходов и репутационным издержкам.
Одним из эффективных решений этой проблемы является автоматическое горизонтальное масштабирование под нагрузку. А чтобы убедиться, что приложение выдержит нагрузку и архитектура была спроектирована правильно, используют нагрузочное тестирование.
В этом уроке вы узнаете, как оценить готовность системы к нагрузке и выявить потенциальные точки отказа. А также научитесь применять оркестратор контейнеров для динамического масштабирования.
Для наглядности раскрывать материал урока мы будем на примере онлайн-сервиса «ОнлиСхемы», который предлагает командам разработки и архитекторам возможность совместной онлайн-работы на виртуальных досках с уклоном на проектирование и планирование. Одна из основных особенностей сервиса — это возможность просмотра истории изменений объектов на схеме с явным отражением дельты.
Сервис уже успел положительно зарекомендовать себя на рынке, число пользователей постепенно росло, и до недавнего времени в сервисе было 4500 зарегистрированных пользователей. При этом рост был достаточно предсказуемым и линейным. Однако после объявления компании Miro об уходе из России сервис «ОнлиСхемы» столкнулся с быстрым ростом пользователей. Количество пользователей увеличивается почти каждый день, рост при этом непредсказуемый: то на 100 человек в день, то на 1000. Соответственным образом меняется и нагрузка на сервис. При этом темпы роста только увеличиваются.
По оценкам аналитиков компании, количество пользователей может превысить 500 тысяч уже через один-два месяца. Компания понимает, что это её шанс захватить существенную долю рынка. При этом важным критерием успеха будет то, как сервис себя проявит в условиях высокой и непредсказуемой нагрузки. Руководство компании поставило задачу команде разработки проанализировать, готово ли приложение к растущей нагрузке, выявить проблемы и решить их как можно скорее.
Текущая архитектура приложения выглядит очень просто:
- Сервер. Пока что приложение крутится на одной виртуальной машине, запущенной в облачном провайдере. Деплой осуществляется посредством docker-compose.
- Веб-приложение. Приложение даёт возможность пользователям работать на доске: создавать и редактировать разные объекты и связи между ними. Веб-приложение периодически опрашивает бэк-приложение по REST для обновления состояния на доске.
- Сервис досок. Предоставляет весь необходимый API для работы на доске:
- получить текущее состояние объектов и связей на доске;
- создать/редактировать/удалить объект;
- создать/редактировать/удалить связь между объектами;
- получить детальную информацию по объекту/связи, включая историю изменений;
- создать комментарий к объекту на доске, в котором также можно отметить другого пользователя. Для получения списка пользователей происходит вызов в сервис пользователей.
В целом команда стремится придерживаться микросервисного стиля, но при проектировании было принято несколько решений, которые ухудшают масштабируемость и отказоустойчивость системы в целом. По ходу изучения материала мы будем вместе с командой «ОнлиСхемы» развивать это приложение.
Тестирование нагрузки на систему
При разработке приложения важно заранее понимать, как оно себя поведёт на продуктовой среде, во время резких всплесков нагрузки. Проверять это на реальных пользователях не лучший вариант, основной способ это выяснить — проводить нагрузочное тестирование.
✏️ Нагрузочное тестирование — это процесс проверки способности приложения справляться с различными уровнями активности со стороны пользователей или систем-потребителей вашего API, обеспечивая при этом приемлемое время отклика без потери стабильности.
Методики тестирования
Хорошая стратегия нагрузочного тестирования позволяет посмотреть, как приложение ведёт себя под различными профилями нагрузки, а также выявить пиковые показатели производительности приложения и его узкие места. Для достижения таких результатов существует множество методик. Рассмотрим основные:
- Smoke-тестТакие тесты проверяют функционирование системы при минимальной нагрузке. Важным результатом является сбор базовых значений производительности, которые в дальнейшем могут использоваться как референсные значения при анализе результатов. Этот тип теста, как правило, предполагает имитацию работы всего нескольких пользователей и в течение короткого периода либо малого числа итераций, либо продолжительности от нескольких секунд до максимум нескольких минут.
- Тест средней нагрузкиИмитирует количество одновременных пользователей и запросов в секунду, отражающих среднее поведение в производственной среде. Этот тип теста обычно постепенно увеличивает количество запросов и поддерживает эту среднюю нагрузку некоторое время. Иногда сценарии могут включать в себя некоторое переменное снижение нагрузки с последующим возвратом на прежний уровень.
- Стресс-тестИспользуется для проверки стабильности и надёжности системы в условиях интенсивного использования. Системы могут получать более высокие, чем обычно, рабочие нагрузки в необычные моменты, такие как сроки обработки, дни выдачи зарплаты, часы пик, конец рабочей недели и многие другие ситуации, которые могут вызывать частый трафик выше среднего. Этот метод тестирования максимально схож с тестом средней нагрузки, за исключением того, что уровень нагрузки задаётся сильно выше. В этом случае период нарастания занимает больше времени для получения желаемой нагрузки, и в целом он может длиться немного дольше, чем при тесте со средней нагрузкой. Иногда такие тесты ещё называют тестированием в час пик.
- Тест всплеска нагрузкиТест всплеска проверяет поведение системы при внезапном и массовом увеличении нагрузки. Тесты всплеска полезны, когда система может столкнуться с событиями с исключительно высокими объёмами трафика (например, «чёрная пятница», глобальные события и так далее). Тестирование всплеска увеличивает нагрузку до чрезвычайно высоких значений за очень короткое или отсутствующее время нарастания. Таким образом, время снижения нагрузки очень быстрое или отсутствует, позволяя процессу повторяться только один раз.
- Тест точки отказаПозволяет определить уровень нагрузки, при которых система начинает терять стабильность или вовсе отказывает. Это может происходить по причинам утечки памяти, забитых коннектов, недостатка серверных мощностей и т. д. В идеале такие ошибки должны возникать при нереалистичных уровнях нагрузки, с которым приложение не столкнётся в ближайшее время при эксплуатации. Однако оно позволяет определить возможности системы, что важно для долгосрочного планирования, особенно в условиях развивающегося бизнеса.
- Длительное тестированиеЭтот метод также является вариацией теста средней нагрузки. Основное отличие — в продолжительности. Время тестирования может достигать нескольких часов и даже дней. Оно помогает выявить долгосрочные проблемы, такие как утечки памяти или сбои в работе базы данных.
💡 Стресс-тест и тест всплеска нагрузки наиболее полезны для тестирования готовности приложения к масштабированию под нагрузку. Эти тесты предполагают сопоставимый уровень нагрузки, но отличаются во времени её нарастания до пиковых значений: либо постепенный рост, либо за очень короткое время. Как правило, высоконагруженные приложения должны быть готовы к обоим сценариям.
Нужно отметить, что методы нагрузочного тестирования могут называться по-разному: по этому поводу нет единого мнения. В том числе может несколько отличаться категоризация, однако суть остаётся примерно такой же.
Как сформировать стратегию тестирования
Правильная стратегия нагрузочного тестирования сильно зависит от профиля нагрузки конкретной системы. При этом не стоит брать цифры с потолка: всегда лучше ориентироваться на реальные показатели мониторинга с продакшн-среды или планируемые показатели.
И надо понимать, что ни один тип теста не позволит выявить все недостатки и риски в системе. Только применение нескольких методик позволит найти потенциальные точки отказа при различных режимах работы системы. Профиль нагрузки системы позволяет определить оптимальные типы тестов:
- Длительное тестирование проводится для системы, которая подвержена риску длительного непрерывного использования.
- Стресс-тестирование проводится в случаях, когда система может периодически или регулярно сталкиваться с повышенным уровнем нагрузки.
- Тест всплеска проводится, если нагрузка на систему может зависеть от сложно предсказуемых событий, вызывающих резкий рост активности пользователей.
Сохраните себе таблицу-шпаргалку, в какой ситуации какую методику тестирования применять.
Архитектура каждого приложения уникальна так же, как модель и профиль её нагрузки. При подготовке стратегии тестирования важно учесть различные факторы и постараться воспроизвести поведение пользователей и систем-потребителей в продуктовой среде, чтобы получить максимально релевантные результаты.
💡В первую очередь рекомендуется проводить smoke-тест. После получения успешных результатов можно приступать к проведению теста средней нагрузки. Далее — к стресс-тестированию. И только потом рекомендуется переходить к другим методикам тестирования.
Каждый из этапов тестирования позволяет выявить проблемы при более простых условиях эксплуатации, прежде чем переходить к более высоким уровням нагрузки и продолжительности тестирования. Это позволяет не только сэкономить ресурсы, но и провести более комплексное тестирование.
Исходя из вышесказанного, можно определить минимально необходимый набор тестов, в зависимости от профиля нагрузки на приложение. При этом использование дополнительных методик тестирования может дать вам дополнительные знания о возможностях приложения, которые будут полезны при его развитии.
Таким образом, если мы посмотрим на приложение «ОнлиСхемы», то увидим, что в данный момент оно сталкивается с непредсказуемой для себя нагрузкой, которая вызвана неординарной ситуацией: органический рост аудитории приложения наложился на уход главного игрока на рынке. И на текущий момент команде рекомендуется провести тесты, соответствующие профилю «Динамический, нерегулярный и непредсказуемый».
Тем не менее приложения такого типа, достигнув определённой точки развития, скорее будут сталкиваться 99,9% времени с нагрузкой динамической, но регулярной и предсказуемой. Это объясняется тем, что такие ситуации, как уход главного игрока с рынка, случаются нечасто, при этом активность пользователей глобально не зависит от каких-то происшествий, акций, ЧС и новостей в мире. Активность пользователей, скорее всего, определяется днями недели, рабочими часами и датами праздников.
В то же время если вам требуется, чтобы система была в любой момент готова к непредсказуемым событиям, то рекомендуется на регулярной основе проводить полный набор тестов системы.
Инструменты для нагрузочного тестирования
Существует множество инструментов нагрузочного тестирования, каждый из которых обладает своими особенностями и преимуществами. Вот некоторые из них:
- Apache JMeter является одним из самых популярных инструментов с открытым кодом для нагрузочного тестирования. Он предназначен для анализа и измерения производительности веб-приложений. JMeter позволяет проводить тестирование с использованием различных протоколов, включая HTTP, HTTPS, SOAP/REST, FTP и многие другие.
- LoadRunner от Micro Focus — это комплексный инструмент для тестирования производительности, который поддерживает широкий спектр возможностей. Он позволяет создавать точные симуляции работы тысяч пользователей, взаимодействующих с веб-приложением.
- Locust — это один из самых простых и легковесных open-source инструментов на Python для проведения нагрузочного тестирования. С Locust вы поработаете при выполнении практических заданий.
Анализ метрик
Важнейшим этапом при проектировании приложения является анализ метрик, полученных либо при нагрузочном тестировании, либо во время эксплуатации системы в продакшн-среде. Это критически важно для обеспечения высокой производительности и стабильности высоконагруженных приложений.
При изучении темы мониторинга вы узнали о различных типах метрик. Однако важно явно обозначить, какие метрики чаще всего используются при анализе производительности и для выявления потенциальных точек отказа системы.
Метрики
- Время отклика (Response Time)
Время, необходимое для обработки запроса приложением. Это один из важнейших показателей работы системы, который напрямую влияет на пользовательский опыт. Например, ещё в 2006 году компания Amazon обнаружила, что каждые 100 мс дополнительного времени загрузки страницы стоят корпорации 1% продаж.
Пример:
При росте числа одновременных пользователей команда «ОнлиСхемы» наблюдает деградацию времени отклика запроса текущего состояния доски. После профилирования команда определила, что наибольшие задержки возникают при запросах к БД. Проблема кратно усугубляется при росте количества объектов и связей на доске. Вероятно, проблема связана с текущим подходом к хранению данных: при запросе текущего состояния доски требуется при помощи join собрать сотни объектов, при этом для каждого из них требуется определить версию записи с актуальным состоянием.
- Время загрузки страниц на стороне пользователя
Важно отличать время, за которое отвечает бэкенд-сервис, и время, которое требуется для загрузки страницы в браузере пользователя. Эта метрика особенно эффективна при совместном анализе с временем отклика и может свидетельствовать как о проблемах в сети, так и о неэффективной работе фронтенд-приложения.
- Пропускная способность (Throughput)
Определяет количество обработанных запросов за единицу времени. Она измеряется в запросах в секунду (RPS) или транзакциях в секунду (TPS). Высокая пропускная способность указывает на способность системы обрабатывать большое количество запросов одновременно. Может помочь определить «точку отказа», то есть уровень нагрузки, после которой происходят ошибки.
Пример:
В текущем подходе к деплою приложение «ОнлиСхемы» не адаптировано под нагрузку: специалистам приходится вручную управлять количеством экземпляров приложения. Один из специалистов предложил на постоянной основе развернуть 100 экземпляров приложения, но это оказалось слишком дорого.
Дополнительно команда определила, что простое повышение количества выделяемых приложению ресурсов работает хуже, чем деплой дополнительных экземпляров приложения, так как в первую очередь растёт именно количество пользователей и их одновременных запросов к приложению. При этом команде удалось выяснить максимальный RPS всех операций, которые может выдержать один экземпляр приложения.
- Использование процессора (CPU Usage)
Показывает, какая часть вычислительной мощности процессора используется приложением.
- Использование памяти (Memory Usage)
Показывает, сколько оперативной памяти используется приложением.
Пример:
В «ОнлиСхемы» с ростом количества версий объектов и связей значительно растёт потребление памяти и процессора при запросах истории изменений. Команда определила, что это связано с необходимостью вычислять дельту изменений, отталкиваясь от снимков состояний сущностей данных.
- Ошибки (Error Rate)
Процент запросов, завершившихся с ошибкой, относительно общего количества запросов.
- Количество одновременных пользователей (Concurrent Users)
Анализ количества одновременных пользователей помогает понять масштабируемость системы и способность к обработке пиковых нагрузок.
Пример:
Рост числа одновременных пользователей «ОнлиСхемы» кратно увеличивает само количество запросов из веб-приложения в бэкенд для получения текущего состояния на доске. Что приводит в том числе к росту объёмов сетевого трафика. Дополнительно удалось выявить, что при одновременном изменении объектов на доске несколькими пользователями увеличивается шанс рассинхронизации досок у пользователей.
Рост числа запросов на операцию создания комментариев с отметкой прямо пропорционально увеличивает количество сетевого взаимодействия между сервисами досок, пользователей и уведомлений. Это ведёт к нескольким последствиям:
- Рост нагрузки на сервис досок ведёт к пропорциональному росту нагрузки на сервис уведомлений и двукратному росту нагрузки на сервис пользователей, так как в рамках данного процесса сервис пользователей вызывается два раза.
- Синхронная зависимость между сервисами требует пропорционального масштабирования на всех них, что приводит к завышению затрат на инфраструктуру.
- Недоступность сервиса уведомлений или сервиса пользователей приводит к отказу всей функции работы с комментариями.
Поиск потенциальных точек отказа
Повышение нагрузки на приложение может влиять на каждую из вышеописанных метрик. При этом негативное изменение той или иной метрики может быть свидетельством огромного разнообразия проблем.
Вот, например, такой список проблем можно составить исходя из полученных метрик.
Длительное время отклика и низкая пропускная способность:
- проблемы с производительностью сервера,
- проблемы с сетью,
- неэффективность алгоритмов обработки,
- неоптимальные запросы к БД.
При детальном анализе стоит использовать инструменты трассировки, которые позволят локализовать источник проблемы. Также рекомендуется сопоставлять с показателями утилизации памяти и процессора.
Высокое использование процессора и памяти:
- неэффективный код,
- утечки памяти,
- неоптимальное распределение нагрузки между серверами.
Использование профилировщиков поможет определить проблемные участки кода. Для выявления неоптимального распределения нагрузки между серверами необходимо проверять и сравнивать метрики RPS между разными экземплярами компонентов приложения.
Большое количество ошибок:
- недоработки в логике обработки запросов,
- отказы внешних зависимостей.
Как правило, ошибки требуют детального изучения логов, так как причина может быть в чём угодно.
Повышенная нагрузка тоже может приводить к ошибкам утечки памяти, превышению времени обработки, ошибкам параллельного доступа к объектам и другим ошибкам, которые не проявляются при средней нагрузке.
Для борьбы от каскадных сбоев существует несколько паттернов: Rate limiting, BulkHead, известный вам Circuit breaker. Иногда при неспособности внешних зависимостей держать нужную нагрузку помогает использование реплик и кеширования. Тем не менее эта метрика может часто сбивать с толку, если проблемы связаны не напрямую с нагрузкой, а, например, с ошибками в новой версии приложения.
🔍 Важно анализировать метрики вместе: это поможет выявить зависимости и закономерности.
Для отслеживания таких зависимостей удобно использовать дашборды в Grafana.
Высокий уровень потребления памяти, например, может говорить об «утечках памяти» из-за неэффективного кода приложения, а может быть вызван ростом количества запросов и отсутствием автоматического масштабирования приложения. Поэтому показатели использования памяти и процессора всегда стоит рассматривать вместе с пропускной способностью. Чаще всего анализ одной метрики без учёта других не сможет служить индикатором какой-то конкретной проблемы.
Итоги
- Основным последствием роста количества одновременных пользователей является рост количества одновременных запросов (RPS). Скачки RPS могут привести к появлению проблем с производительностью. Это сигнал к масштабированию инфраструктуры.
- С помощью анализа метрик приложения команды могут своевременно выявлять и устранять потенциальные точки отказа и повышать устойчивость и производительность своих приложений. При отслеживании трендов часто можно заранее выявить проблемные места и устранить проблему до её критичного проявления в продакшн-среде. Стоит сопоставлять метрики RPS с показателями утилизации памяти, процессора и учитывать при этом количество экземпляров, которые обрабатывают эту нагрузку.
- Нагрузочное тестирование позволяет выявить оптимальные ресурсы для приложения. Можно проводить несколько итераций тестирования, каждый раз меняя количество ресурсов, выделяемых на один экземпляр приложения, и анализируя производительность приложения.