К основному содержимому
performance⁠-⁠testing.ru

Coordinated omission: почему ваш генератор прячет самые долгие ответы

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

metric-latencyanalysisanti-patternmethodologyk6

Ваш нагрузочный тест показал p99 в четыре миллисекунды. Система при этом на минуту вставала колом, и вы об этом не узнали.

Это не преувеличение и не редкий случай. Это устройство по умолчанию у большинства генераторов нагрузки, и у явления есть имя. Гил Тене, технический директор Azul Systems, назвал его coordinated omission в докладе «How NOT to Measure Latency».

Как генератор перестаёт мерить

Обычный виртуальный пользователь работает так: отправил запрос, дождался ответа, отправил следующий. Живой человек ведёт себя ровно так же, спорить тут не с чем.

Проблема начинается, когда система замирает.

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

Отсюда и название: генератор теряет данные не где попало, а ровно в те секунды, ради которых тест и запускали.

Арифметика

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

На пятидесятой секунде система встаёт ровно на одну секунду.

Что запишет генератор с закрытой моделью:

  • 99 999 запросов по 1 мс
  • 1 запрос по 1000 мс

Считаем перцентили: p99 — одна миллисекунда, p99.9 — одна миллисекунда, p99.99 — тоже одна. Секундная остановка видна только в максимуме, а на максимум давно никто не смотрит.

Теперь посчитаем, что произошло бы на самом деле, если бы запросы продолжали приходить по расписанию. За секунду простоя должна была прийти тысяча запросов. Первый ждал бы 1000 мс, второй 999 мс, и так далее до последнего. Средняя задержка среди них около 500 мс.

  • 99 000 запросов по 1 мс
  • 1000 запросов со временем от 1 мс до 1000 мс

Теперь p99 около 500 мс, а p99.9 — около 900. Разница с первым расчётом выходит больше чем в сто раз, и при этом ни один замер не был сделан неверно. Просто в первом случае мерили не то.

Что записывает генератор, когда система замирает на секунду Две дорожки времени. Каждая точка — один запрос, светлая полоса посередине — секунда, пока система не отвечает. Верхняя дорожка, закрытая модель: внутри полосы точек нет вовсе, потому что виртуальный пользователь заблокирован и ждёт единственный ответ 1000 мс. В записи остаются 99 999 запросов по 1 мс и один на 1000 мс, поэтому p99 равен 1 мс. Нижняя дорожка, открытая модель: точки идут по расписанию и внутри полосы, десять из них показаны, от каждой вниз свисает полоска ожидания до конца паузы — 1000 мс у первого запроса, 1 мс у последнего. В записи 99 000 запросов по 1 мс и 1000 запросов с ожиданием от 1 до 1000 мс, поэтому p99 около 500 мс. система не отвечает 1 с Закрытая модель: после ответа ни один не ушёл точка — запрос он ждёт 1000 мс в записи: 99 999 × 1 мс + 1 × 1000 мс p99 = 1 мс Открытая модель: по расписанию полоска — ожидание: 1000 мс у первого, 1 мс у последнего время → в записи: 99 000 × 1 мс + 1000 × (1…1000 мс) p99 ≈ 500 мс
Одна и та же секундная пауза, записанная двумя генераторами. Закрытая модель за это время не отправила ни одного запроса: виртуальный пользователь висел на единственном ответе, и в записи от паузы остался один запрос на 1000 мс. Открытая слала по расписанию, накопила тысячу ожиданий и показала ту же паузу как p99 около 500 мс. Секунда нарисована шире своей доли — на сто секунд теста она приходится одна.

Открытая модель вместо закрытой

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

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

У k6 за открытую модель отвечают исполнители constant-arrival-rate и ramping-arrival-rate. Документация описывает их как «фиксированное число итераций за заданное время», в отличие от constant-vus, где виртуальные пользователи выполняют «столько итераций, сколько успеют». Терминов «открытая» и «закрытая» в самой документации нет, они пришли из теории очередей.

У wrk есть форк от самого Тене — wrk2, и его подзаголовок описывает суть исправления прямо: постоянная пропускная способность и корректная запись латентности.

Побочный эффект, к которому надо быть готовым

Открытая модель честнее, и от этого тесты начинают падать.

Раньше генератор тормозил заодно с системой, и картинка держалась красивой. Теперь очередь неотвеченных запросов растёт, время ожидания в ней записывается — и хвост распределения показывает то, что там на самом деле есть. Первая реакция команды обычно одна: «инструмент сломался, раньше было нормально».

Раньше не было нормально. Раньше не было видно.

Что с этим делать в отчёте

Одного перехода на открытую модель мало, если результаты потом усредняются.

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

Практическое правило простое. Если ваш пайплайн где-то усредняет времена ответа за интервал, а потом считает перцентиль от усреднённых значений, вы получаете перцентиль средних — а это совсем другая величина, и она систематически меньше настоящей.

Чего этот разбор не покрывает

Coordinated omission не единственная причина, по которой цифры теста расходятся с продом. Профиль нагрузки может быть выбран неверно, стенд может отличаться от боевого окружения, тестовые данные могут ложиться в кеш там, где реальные не ложатся.

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

Источники

  1. Gil Tene, «How NOT to Measure Latency» — доклад, где введено понятие(Ссылка)
  2. Тот же доклад на InfoQ(Сайт)
  3. giltene/wrk2 — форк wrk с исправленной записью латентности(Репозиторий)
  4. HdrHistogram — хранение перцентилей без потери хвоста(Репозиторий)
  5. Executors в k6 — точные имена исполнителей(Сайт)

Доклады по теме