Нагрузочное тестирование выполнять сложно, а инструменты далеки от совершенства. Почему?

Автор оригинала: Nicholas Tietz-Sokolsky
  • Перевод


Если вы создаёте приложение, которое должно масштабироваться — а все мы надеемся, что наши приложения будут расти — то в определённый момент нам приходится разбираться, может ли оно это делать на самом деле. Именно тогда на помощь приходит нагрузочное тестирование: если вы хотите узнать, справится ли ваше приложение с крупными масштабами, то мы просто генерируем эти масштабы и проверяем! Звучит достаточно просто.

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

Если ваше приложение становится чуть сложнее, то вы переходите к инструментам наподобие Gatling. Они позволяют симулировать виртуальных пользователей, выполняющих различные сценарии, что намного полезнее, чем простая осада одного или нескольких URL. Но даже этого недостаточно, если вы пишете приложение, использующее одновременно WebSockets и HTTP-вызовы в течение долговременной сессии, а также требующее повторения по таймеру определённых действий. Возможно, я серьёзно недоглядел чего-то в документации, но мне не удалось найти способа, допустим, настроить периодическое событие, запускаемое каждые 30 секунд и выполняющее определённые действия при ответе на сообщение WebSocket, а также производящее действия по HTTP, и всё это в рамках одной HTTP-сессии. Я не нашёл такой возможности ни в одном инструменте нагрузочного тестирования (и именно поэтому написал на работе свой собственный инструмент, который надеюсь выложить в open source, если найду время на подчистку кода и отделения его от проприетарных частей).

Но предположим, что у вас есть стандартный инструмент наподобие Gatling или Locust, который работает и удовлетворяет вашим нуждам. Отлично! Теперь давайте напишем тест. По моему опыту, сейчас это самая сложная задача, потому что нам сначала нужно разобраться, как выглядит реалистичная нагрузка — вас ждёт один-три дня кропотливого изучения логов и ведения заметок по показателям сетевых инструментов в браузере при работе веб-приложения. А после того, как вы узнаете реалистичную нагрузку, то вам придётся написать код, который сводится к следующему: подмножество вашего приложения будет притворяться пользователем, общаться с API и выполнять действия, которые совершает пользователь.

И на этом ещё ничего не закончилось! Здорово, мы написали нагрузочный тест, и он реалистичен. Но задача постоянно меняется, ведь выпускаются обновления. То есть теперь у нас появилась проблема поддержки: как обеспечивать актуальность нагрузочного теста при изменении приложения? Для этого нет качественных инструментов и почти ничто вам не поможет. Придётся сделать это частью процесса и надеяться, что вы ничего не упустите. Такой ответ не радует, и именно поэтому этот аспект является одним из самых сложных при тестировании приложения.

Можно даже опустить все заботы, связанные с «запуском», потому что, честно говоря, если вы добились таких успехов в нагрузочном тесте, то его запуск окажется не самой тяжёлой задачей.

Откуда берётся сложность


По сути, ситуация такова:

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

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

Симуляция пользователей. Действительно ли она нужна?


Здесь я могу ответить «да», хотя это может зависеть от конкретного приложения. В данном случае подразумевается пользователь сервиса: если у вас монолит, то это все ваши пользователи, но в случае микросервисов «пользователем» может быть один из сервисов! При создании приложений, над которыми я работал, мне удалось достичь незначительного успеха с таргетированными тестами отдельных конечных точек. Но в конечном итоге для этого требовалась такая сложная система, что это оказалось ненамного проще самого нагрузочного теста! И хотя мы получили некоторые результаты и усовершенствования, покрыть всё не удалось (в приложении могут быть взаимодействующие конечные точки) и мы не смогли получить реалистичную нагрузку.

Лучше задаться вопросом: «А когда не нужно симулировать пользователей?». Насколько я понимаю, такое бывает, когда вы знаете, что все конечные точки независимы по производительности, отсутствуют запросы с отслеживанием состояния и порядок запросов не влияет на производительность. Это довольно серьёзные допущения, и без проверки их независимости сложно быть уверенным в них, поэтому нам приходится возвращаться к написанию теста.

Вероятно, лучшее, что вы можете здесь сделать, происходит на этапе проектирования API и системы, а не на этапе тестирования. Если упростить API, то тестировать придётся гораздо меньшую площадь поверхности. Если спроектировать систему с более независимыми частями (например, когда у каждого сервиса есть своя база данных), то их проще будет тестировать по отдельности, чем монолит. К тому же, при этом вы сможете использовать более простой инструмент — двойная выгода!

Написание тестов — сложная задача. Как и их поддержка


Создавать тесты сложно, потому что требуется выполнить несколько задач: нужно разобраться, каков поток использования API и написать симуляцию этого использования. Для понимания потока необходимо понимание других систем, кроме тестируемой, и поскольку ваша система вряд ли подробно рассматривается в их документации, не будет чёткой диаграммы того, когда и что вызывается; часто приходится разбираться в логах, пока не поймёшь, какова же истинная схема использования. Далее идёт написание этой симуляции — тоже нетривиальная задача, поскольку необходимо обрабатывать состояние большого количества акторов, представляющих собой пользователей вашего API!

А, да, и теперь вам нужно писать для всего этого ещё и интеграционные тесты.

Существуют исследования о том, как упростить некоторые из этих задач. Например, можно разобраться в том, что вам нужно для первоначального теста, и распознавать регрессии (отсутствующие новые нагрузки) по автоматизированному анализу логов. Но насколько я понимаю, на GitHub, не говоря уже о коммерческой доступности, нет продукта, способного выполнить за меня эту задачу. Поэтому похоже, что в нашей отрасли подобные системы не получили широкой поддержки. Для самостоятельной реализации этот проект был бы слишком большим, и в этом может быть причина его увядания (или он реализуется в больших компаниях и о нём не говорят).

Может быть, не подвергать всё нагрузочному тестированию?


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

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

  • Старый добрый анализ. Усесться с ноутбуком, ручкой и пониманием системы в целом, выделить на это полдня, и вы получите примерные расчёты основных параметров и ограничений масштабирования системы. Когда вы наткнётесь на «бутылочное горлышко» или встретитесь с неизвестными переменными (сколько транзакций в секунду может поддерживать наша база данных? Сколько мы их генерируем?), то можно будет протестировать конкретно их!
  • Разворачивание фич. Если вы можете медленно разворачивать фичи на всех пользователей, то вам может и не понадобиться нагрузочное тестирование! Вы можете измерять производительность экспериментально и проверять, достаточно ли её. Достаточно? Разворачиваем дальше. Мало? Откатываемся назад.
  • Повторение трафика. Это совершенно не поможет с новыми фичами (для них воспользуйтесь предыдущим пунктом), но поспособствует пониманию критичных точек системы для уже имеющихся фич, при этом не требуя большого объёма разработки. Вы можете взять отслеженный ранее трафик и повторять его (многократно снова и снова, даже комбинируя трафик различных временных периодов), наблюдая за производительностью системы.



    На правах рекламы


    Серверы для разработки — это эпичные от Вдсины.
    Используем исключительно быстрые NVMe накопители от Intel и не экономим на железе — только брендовое оборудование и самые современные решения на рынке!

VDSina.ru — хостинг серверов
Серверы в Москве и Амстердаме

Комментарии 16

    0

    Слабо себе представляю сценарий, который можно реализовать с помощью Gatling и нельзя с помощью JMeter. И для JMeter есть два официальных плагина для работы с веб-сокетами.

      0
      У него с памятью туда просто. Большую нагрузку не сформировать. Есть коммерческие решения, но это уже не чистый jmeter
        0
        Да, большую нагрузку сделать сложно.
        Но ведь никто не ограничивает тестирование одним JMeter. Можно ведь несколько нод использовать
          0

          Уже давно нет проблем с памятью, даже на нагрузках в 1000 рпс, они остались на версиях ниже 5, по ощущениям

        0
        Интересный подход и интересное мнение.
        Думаю, что в определенных ситуациях его советы очень пригодятся, но в целом я не согласна с мнением, что если сложно, то надо писать меньше.
        Описанная сложность для меня тоже не убедительна, это то, с чем приходится каждый день иметь дело. При грамотно написанных сценариях их поддерживать — это не такая и сложная задача.

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

          А мне сложность кажется реальной. Потому что пользователи работают с приложением. И сценариев его использования много. На какой рассчитывать? Рассчитывать ли, что все пользователи могут единомоментно пользоваться всем функционалом приложения (и, соответственно, — всеми микроприложениями и микросервисами) или мы можем все-таки согласиться, что нагрузка будет меньше. Но нам все равно надо тогда задать пределы для каждого сервиса. В полей рост принцип «разделяй и властвуй». И чем изолированнее компоненты — тем понятнее, какие требования к ним предъявлять

            0

            «Потому что пользователи работают с приложением. И сценариев его использования много.»


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

          0
          Очень странная выборка средств тестирования. Но для рекламной статьи, видимо, допустимо. Возможно это подготовка к рекламе saas решения на основе упомянутых утилит.
          В целом по больнице ситуация не очень отражена, как мне кажется.
          Против общего посыла статьи не имею ничего. Недавно столкнулся с задачей краеугольным камнем которой была нагрузка! Но мне кажется это пока исключения. Может и есть специалисты выжать из битрикса, как ходовой пример, адекватный результат по нагрузке, нет я как-то не видел. Мне кажется внутренний рынок ещё не очень нуждается в нагрузочном показателе качества.
            +1
            Мне кажется внутренний рынок ещё не очень нуждается в нагрузочном показателе качества.

            Нуждается. Потому что без правильного нагрузочного тестирования и адекватных цифр нельзя управлять инфраструктурой. А экономия на мощностях может быть очень существенной. Что хуже — отсутствие понимания нагрузок будет приводить к факапам с недоступностью систем. Снова и снова. А это финансовые и репутационные потери…

            +1
            Возможно, я серьёзно недоглядел чего-то в документации, но мне не удалось найти способа, допустим, настроить периодическое событие, запускаемое каждые 30 секунд и выполняющее определённые действия при ответе на сообщение WebSocket, а также производящее действия по HTTP, и всё это в рамках одной HTTP-сессии. Я не нашёл такой возможности ни в одном инструменте нагрузочного тестирования

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


            А стремление создать в тесте производительности поведение реального пользователя — чаще всего недостижимо. И сделать профиль нагрузки на бумажке, лишь опираясь на логи — хороший подход. Тут согласен. Пример такой, по логам можно узнать интенсивность входов. А у аналитика узнать, что треть пользователей выполняет операцию Х. И вот теперь, на листочке, можно примерно оценить интенсивность операции Х, как функции от интенсивности входа, даже если по Х не было никакой статистики.

              +2

              И симуляция реального пользователя для большой нагрузки (100-1000 запросов в сек) вредна и не нужна. Проблемы автора, думаю, от этой идеи фикс. Но так можно делать для малой нагрузки.


              Иногда идея фикс принимает форму: нам нужна нагрузка от 100 000 пользователей, значит нам нужно 100 000 потоков в JMeter.


              Иногда такую: пользователь зайдя на сайт с вероятностью 80% ищет телефон, с вероятностью 70% красный, он может уточнить свой поиск, если наше телефон,… В результате люди пишут сложный функциональный тест на JMeter/Gatling, вместо того, чтобы заниматься нагрузкой.


              А на деле все проще. От нагрузки требуется создать лишь интенсивность. По возможности повторить время жизни сессии — но это можно делать отдельно, не в JMeter/Gatling/… уже, а в параллельном автотесте, который работает в 1 поток.


              Раз автор хочет все сразу, то у него нагрузка малая — до 10 запросов в сек.

                +1

                Очень поддерживаю, реалистичность — дорого и, чаще всего, не нужно

                  0
                  Иногда идея фикс принимает и такую форму: нам нужна нагрузка от 100 000 пользователей, значит нам нужно 100 000 запущенных браузеров (со всего света, разумеется, никак иначе).

                  В сущности же я склонен думать, что у каждого нагрузочного теста есть две стороны: 1) сделать нагрузку, 2) измерить производительность под нагрузкой.

                  Так вот, когда тестируемая система достаточно сложна с т.з. потенциальных сценариев её использования, не стоит даже и пытаться совместить эти две стороны теста в одном скрипте. Иначе да — скрипт получится уродливым по форме и содержанию, сложным в масштабировании нагрузки, дорогим в разработке и ещё более дорогим в сопровождении. Но это не проблема JMeter или любого другого инструмента.
                    0

                    Нам не нужно 100000 пользователей. Мы можем декомпозировать систему на компоненты.
                    Если мы знаем, что каждый компонент независим и может обслужить, скажем, 1000 пользователей, то нам нужно 100 экземпляров сервиса, но при этом мы можем его легко и дешево протестировать (единичный экземпляр) изолированно.
                    Разделяй и властвуй!

                  0
                  Несколько лет назад я помнится тестировал платформу в компании где работал на тот момент. Интуитивно было понятно, что тот же JMeter не будет масштабироваться в нужном объеме. Я просто на коленке написал вполне вменяемый спрингбутовский сервис и с симулировал миллион устройств. К сожалению на первый момент тестирования платформа довольно быстро «легла» пришлось пойти на поклон к людям с платформы кто на тот момент занимался нагрузочным тестированием.
                  В совместной работе через пару лет платформа спокойно держала нагрузку. В какой-то момент я опубликовал сам тул, не пропадать же добру
                  https://vyatkins.wordpress.com/2016/07/30/velociraptor/

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

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое