Нагрузочное тестирование распределенных систем с высокой нагрузкой

Нагрузочное тестирование распределенных систем с высокой нагрузкой

21 апреля 2023 г.

У нас есть сервис, взаимодействующий с внешним миром и очень высоконагруженный (мы прикинули и подготовились к 100 RPS). Назовем эту службу ServiceX, своего рода службу прокси-оркестратора. Этот сервис взаимодействует с набором наших внутренних сервисов и смотрит во внешний мир.

Мы тестировали этот сервис и думали, что готовы к высоким нагрузкам, но это оказалось не так. Да, нам удалось спасти сервис под обстрелом, и на это у нас ушло меньше двух дней.

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


Краеугольный камень проблемы

Как оказалось, эмулируемая нами нагрузка по записи составляет всего 5 % от нагрузки на ServiceX, а остальная часть нагрузки по записи приходится на другие системы позже.

Для лучшего понимания проиллюстрируем схематически

Cornerstone of the problem

* Если заходит стрелка RO - ServiceX откуда-то читает - грузится на другой сервис * Если стрелка RO выходит - это нагрузка на ServiceX

* Зеленый - мы хорошо эмулировали * Желтый — «как-то эмулируется»: на стороне тестов других систем * Красный — не эмулируется вообще

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

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

Общий список причин и проблем

  1. Для загрузки RW нужно создавать какие-то "моки", уводящие трафик записи куда-то "в сторону", что сложно.
  2. Код зарастает операторами if, что усложняет его поддержку.
  3. Репрезентативность теряется.
  4. В качестве альтернативы трем описанным выше сценариям тестовые данные будут записываться рядом с реальными данными, распространяться по системам, нарушать аналитику, занимать место и т. д.

Как только в коде появляется функция is_test_user(), большинство ваших тестов в той или иной мере становятся недостаточно репрезентативными. Вот почему в период высокой нагрузки на ServiceX мы не были должным образом подготовлены.

Стратегии тестирования распределенных систем

Постановка

  1. Во-первых, постановка не показательна. Формула «если мы можем обрабатывать 100 RPS на одном сервере, мы можем обрабатывать 1000 RPS на 10 серверах» здесь не работает. Почему? Хотя бы из-за теоремы CAP. Вообще об этом можно было бы написать целую отдельную статью, а здесь мы просто примем это как факт — распределенные системы масштабируются нелинейно.
  2. Необходимо много аппаратного обеспечения, что экономически неэффективно, так как практически требует копирования производственной среды, которую также необходимо обслуживать таким же образом. Так что постановка — не панацея.
  3. Невозможно протестировать географическое распределение.

Изоляция

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

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

К сожалению, альтернативы этому нет, так как очень часто узким местом может стать база данных. В то же время это не должна быть отдельная база данных for_tests — она просто нерепрезентативна.

Common Mocks Layer

| Плюсы такого решения | Минусы такого решения | |----|----| | Можно проверить загрузку записи | Слабая репрезентативность | | Изолированное тестирование каждой системы | Сложно поддерживать в коде |

Однако, конечно, есть варианты развития этого пути

* Что такое слой макетов? Это библиотека/фреймворк (что-то вроде reflection или runkit), который не выпускает тестовые запросы вне службы, заменяя ответы? * Это какое-то решение для обнаружения служб и/или балансировки нагрузки, которое отправляет тестовые запросы в специальные «фиктивные» модули?

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

Эмуляция

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

Common Emulation Layer

| Плюсы такого решения | Минусы такого решения | |----|----| | Можно проверить загрузку записи | Трудно поддерживать (не как изоляция) | | Высокая представительность | Все сервисы должны его поддерживать |

Однако и здесь есть варианты развития этого пути

* Что такое слой эмуляции? Это решение для обнаружения служб и/или балансировки нагрузки и промежуточное ПО, которое помечает все тестовые запросы и отслеживает сохранение разметки, например трассировку?

Скорее всего, ответ на этот вопрос также зависит от конкретного бизнеса.


Технические детали

Наш ServiceX получает большинство событий через Kafka< /а>. Какие варианты доступны для тестирования асинхронных взаимодействий?

Самый простой способ

  1. Есть сроки, в течение которых мы можем обеспечить бизнес-процесс.
  2. Создайте показатель, отражающий количество входящих сообщений от Kafka.
  3. Узнайте у бизнес-клиентов, как долго мы можем прекратить доставку этих сообщений.
  4. Остановите Kafka, аккумулируйте задержку и измерьте скорость обработки этих сообщений.

Building up latency

| Плюсы | Минусы | |----|----| | Весьма актуально для структуры данных | Всегда проверяйте максимальную пропускную способность | | Всегда проверяйте максимальную пропускную способность | Может выполняться только вручную | | | Может повредить подключенные услуги | | | Не подходит для регулярного тестирования | | | SLA страдает для критически важных систем | | | Оценить скорость только по задержке |

Второй простой способ

  1. Использовать прокси-сервер Kafka-rest
  2. Создать обычный прокси или специально разработанный сервис с асинхронным взаимодействием
  3. Можно применять любую нагрузку

Kafka-rest proxy

| Плюсы | Минусы | |----|----| | Может применять любую нагрузку | Высокие накладные расходы (REST→Kafka) | | Можно протестировать на производстве | Дополнительный сервисный уровень может повредить результатам | | | Оцените скорость с помощью показателей задержки и USE | | | Просмотр нагрузки на метрики RED по генератору | | | Возрастает сложность анализа |

Единый генератор нагрузки Kafka

Оба этих варианта нас не удовлетворили, и мы решили реализовать свое решение, которое назвали унифицированным генератором нагрузки Kafka.

Что может эта служба?

  1. Может добавлять любые данные в любую тему с заданной интенсивностью.
  2. Автоматически устанавливать тестовые флаги в заголовке
  3. Может использовать статистику из специальной темы и смешивать ее с результатами.
  4. Разрешает загрузку топологии.
  5. Просмотрите результат загрузки в метриках генератора
  6. Не требует специальных решений для сбора статистики.


Заключение

В этой статье мы подробно рассмотрели различные методы и стратегии тестирования производительности в распределенных системах, сосредоточив внимание на асинхронных взаимодействиях и использовании Kafka. Мы описали плюсы и минусы стадирования, изоляции, эмуляции и других подходов, что позволяет специалистам выбрать наиболее подходящий метод для своих проектов.

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

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


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE