Как оценить ёмкость сервиса и не упасть под нагрузкой

в 7:18, , рубрики: capacity planning, load balancing, performance testing, sre, Блог компании Яндекс, команда яндекс.маркета, Разработка веб-сайтов, системное администрирование, Тестирование IT-систем, Тестирование веб-сервисов

Как оценить ёмкость сервиса и не упасть под нагрузкой - 1

Рано или поздно любому растущему сервису приходится оценивать свои технические возможности. Сколько посетителей мы в силах обслужить? Какова ёмкость (она же capacity) системы? Не добрались ли мы до предела и не упадём ли, если привлечём ещё несколько тысяч пользователей? Сколько дополнительных вычислительных ресурсов заложить в бюджет на следующий год, чтобы соответствовать планам роста?

Ответы можно получить аналитическим путём, адресовав вопросы опытному разработчику/DevOps/SRE/админу. Достоверность оценки зависит от огромного числа факторов: начиная с темпов наполнения системы функциональностью и графа взаимосвязей между компонентами и заканчивая временем, которое эксперт с утра провёл в пробке. Чем сложнее система — тем больше сомнений в адекватности аналитической оценки.

Меня зовут Максим Куприянов, вот уже пять лет я работаю в Яндекс.Маркете. Сегодня я расскажу читателям Хабра, как мы учились оценивать ёмкость наших сервисов и что из этого вышло.

Как оценить ёмкость сервиса и не упасть под нагрузкой - 2

Выходим на позицию

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

Теперь поговорим о том, без чего точно не построить объективные оценки: о метриках работы сервиса. Если число запросов на сервис зависит от DAU — значит, нам точно понадобится метрика «число запросов в секунду» (requests per second, RPS). Кроме того, чтобы оценить качество работы сервиса, нужно знать процент ошибок и времён ответа (таймингов запросов). Ошибкой будем считать ответ с HTTP-кодом от 500 и выше. Ошибки из диапазона 4xx клиентские и в нормально работающей системе обычно ничего не говорят о проблемах сервиса. Что касается таймингов, то тут у нас принято рассчитывать и хранить 80-й, 95-й, 99-й и 99,9-й перцентили времён ответа, но конкретный набор может немного отличаться от сервиса к сервису.

Итак, у нас есть метрики частоты запросов, процента ошибок и набор перцентилей времени ответа. А ещё мы знаем DAU сервиса на каждый день и на будущие периоды (в виде прогноза). Учитывая, что усреднённые паттерны поведения пользователей не слишком меняются день от дня, допустим следующее: зная RPS в наиболее активный период рабочего дня (пиковое RPS), мы можем спрогнозировать пиковое RPS на будущие периоды, при условии, что у нас есть прогноз DAU. И наоборот: если мы знаем, сколько запросов в секунду выдерживает система, не нарушая договорённости по времени ответа и проценту ошибок, то можем оценить, какой объём аудитории сможем обслужить, т. е. знаем ёмкость системы.

Отлично, мы определились с задачей: зафиксировать в виде договорённостей тайминги ответа и процент ошибок и найти максимальное RPS, которое система выдержит при этих условиях. Как будем решать?

Как оценить ёмкость сервиса и не упасть под нагрузкой - 3

Стреляем по мишени

Вот классический подход к решению задачи: собираем тестовый полигон, берём access-логи системы из production-окружения, делаем из них патроны и обстреливаем систему, повышая частоту запросов, пока полигон не покажет значимую деградацию по таймингам ответа и/или ошибкам. В этот момент останавливаемся и фиксируем частоту запросов (то самое RPS). Победа? Как бы не так. И вот почему:

• тестовый полигон, как правило, не идентичен платформе под сервисом в production-окружении;
• код сервиса меняется каждый день, а то и чаще;
• на нагрузку могут влиять эксперименты;
• запросы пользователей по тяжести зависят от времени суток и прочих условий;
• современные сервисы редко работают изолированно, чаще они делают подзапросы в другие сервисы, и это придётся как-то учитывать.

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

Как оценить ёмкость сервиса и не упасть под нагрузкой - 4

Имитируем реальность

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

• не решается проблема со взаимодействием с внешними компонентами, так как копию всего production-окружения делать очень дорого;
• журналы запросов из системы-зеркала могут случайно смешаться с журналами из production-окружения, а значит, необходимо строить систему с маркировкой зеркального трафика, чтобы потом его можно было найти и почистить;
• запросы обычно зеркалируются либо в полном объёме, либо в процентном отношении от общего числа, а такая точность нас не устраивает (но это можно решить, мы в эту сторону работаем).

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

Как оценить ёмкость сервиса и не упасть под нагрузкой - 5

Тестируем прямо в production

И вот мы наконец добрались до вкусного. Для каждого тестируемого компонента мы поднимаем в production отдельный экземпляр, частоту запросов на который регулируем с балансировщика с высокой точностью. В прошлый раз читатели нас спрашивали: «Хватает ли возможностей HAProxy? Не возникала ли необходимость писать что-то своё?». Так вот, это тот самый редкий случай, когда не хватило и пришлось писать.

При этом есть отдельный сервис, который пристально наблюдает за метриками нагружаемого экземпляра и, когда показатели приближаются к критическим величинам, прикрывает вентиль на балансировщике, уменьшая частоту запросов. Если же сервис работает в допустимых границах — вентиль, наоборот, открывается. Конечно, пороги таймингов и ошибок при нагрузке живого сервиса заметно более консервативны (обычно на 5–10%), чем на полигоне, ведь мы не хотим ухудшать взаимодействие с пользователями. Таким образом, нагружаемый экземпляр всегда работает на пределе возможностей. Эти показатели мы фиксируем. А дальше уже арифметика: мы знаем количество ядер сервиса под нагрузкой на каждый момент, знаем DAU за вчерашний день. Из этого считаем утилизацию, резервы по ёмкости и варианты поведения системы при отключении той или другой локации. Всё это ложится в базу, откуда строятся красивые графики. На основании этих данных при снижении ёмкости ниже заложенного порога срабатывают алерты.

Давайте посмотрим на графики

Как оценить ёмкость сервиса и не упасть под нагрузкой - 6

Вот так мы регулируем подачу трафика на тестируемый инстанс. Шаг может быть любым кратным 1 RPS. На графике для иллюстрации смоделирован подъём с трёхминутным интервалом: сначала от 650 до 1K RPS с шагом 50, а потом от 200 до 1K с шагом 100. Напомню, это настоящий пользовательский трафик, на который клиенты получали ответы.

Как оценить ёмкость сервиса и не упасть под нагрузкой - 7

Здесь показано RPS на три инстанса: один под нагрузкой и два контрольных. Для испытуемого искусственно задали верхний предел — 600 RPS. Сервис может и больше, но становится слишком неустойчивым и зависимым от внешних воздействий. Хорошо видно, что в первой половине дня запросы на сервис в среднем более тяжёлые и инстанс не может на приемлемых условиях достичь пиковой ёмкости, но ближе к вечеру всё приходит в норму. Всплески и пропуски на графике — это рестарты инстансов для выкладки релизов и прочих обновлений (все они под балансировкой, никто не пострадал). А ступенчатые корректировки RPS на испытуемом — как раз работа алгоритма, который ищет предел возможностей.

Как оценить ёмкость сервиса и не упасть под нагрузкой - 8

Хорошо видна частота запросов на сервис и нагрузка, которую может выдержать один инстанс.

Как оценить ёмкость сервиса и не упасть под нагрузкой - 9

А тут мы пересчитываем всё в проценты утилизации. На графике видно, что сервис был довольно сильно нагружен и при отключении одной из локаций возникли риски уйти за SLA. Но сейчас уже всё в порядке: в сервис добавили ресурсы, утилизация вернулась в приемлемые границы.

Таким образом, нагрузочное тестирование в production позволяет быстро оценить ёмкость системы и спрогнозировать потребление ресурсов на будущие периоды. При этом система фактически не добавляет заметных расходов и можно спокойно работать со stateful-сервисами, так как мы не порождаем новый трафик, а лишь аккуратно перераспределяем тот, что есть. Ну и напоследок: для работы, как правило, не требуется изменять код само́й подопытной системы, что позволяет тестировать даже legacy-приложения.

Как оценить ёмкость сервиса и не упасть под нагрузкой - 10

Рефлексируем

В Маркете эта методика работает уже не первый год, и мы можем поделиться наблюдениями и рекомендациями:

• Рядом с нагружаемым экземпляром обязательно должен быть обычный — контрольный, а лучше пара, так как деградация часто наступает не из-за того, что инстанс перегружен, а из-за общих проблем с сервисом в целом.
• Методика хорошо работает только с теми компонентами, чья нагрузка выше сотни запросов в секунду на локацию. Причина довольно проста: нам нужно нагрузить и тестируемый экземпляр, и один-два контрольных. Если трафика не хватит — мы не достигнем насыщения или не сможем честно сравнивать. А если предельное RPS на инстанс очень мало, то минимальный шаг изменения частоты запросов в 1 RPS может оказаться слишком грубым.
• Фронты и бэкенды лучше тестировать на разных локациях, чтобы артефакты нагрузочного тестирования бэкендов не влияли на оценку ёмкости фронтов.
• Когда мы анализируем тайминги ответов и ищем признаки деградаций, мы обычно берём пять минутных агрегатов и считаем медиану, чтобы не реагировать на случайные всплески.
• Основная причина, по которой падает нагружаемый экземпляр сервиса, — место на диске для файлов-журналов (логов). О нём всегда забывают.
• Запись логов на нагруженный по вводу-выводу диск web-серверов – очень частая причина ухудшения таймингов, даже на SSD. Всегда включайте буферизацию, асинхронную запись, да всё что угодно, лишь бы не висеть в ожидании, пока запись кончится.
• Ночная нагрузка не показательна, так как запросы в среднем более тяжёлые из-за большей доли роботов. Поэтому для оценки ёмкости лучше фиксировать диапазон из условно светлого времени суток, а ночью лишь уменьшать поток запросов, если появились признаки деградации.
• 99,9-й перцентиль таймингов ответа бесполезен для оценки ёмкости, так как гарантии доступности сети редко превышают 99%.
• Заведите таймлайн и фиксируйте релизы сервисов и прочие значимые события. Очень помогает находить то, что привело к снижению ёмкости.
• При детальном разборе причин деградации также полезна трассировка: в каждый запрос на сервис добавляется заголовок-маркер, который проходит от фронта до последнего бэкенда и попадает во все журналы. Так можно проследить весь путь запроса и понять, что приводит к задержкам.

Автор: Максим Куприянов

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js