Coordinated omission: почему ваш генератор прячет самые долгие ответы
Нагрузочный генератор, который ждёт ответа перед следующим запросом, перестаёт измерять ровно тогда, когда система тормозит. Разбор с арифметикой и способами починки.
Ваш нагрузочный тест показал 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. Разница с первым расчётом выходит больше чем в сто раз, и при этом ни один замер не был сделан неверно. Просто в первом случае мерили не то.
Открытая модель вместо закрытой
Чинится это одним решением: запросы должны уходить по расписанию, а не тогда, когда пришёл предыдущий ответ.
В теории массового обслуживания такая схема называется открытой моделью — поток заявок задан снаружи и на то, справляется система или нет, не оглядывается. Закрытая модель, наоборот, привязывает темп к скорости обработки, и ровно эта привязка порождает coordinated omission.
У k6 за открытую модель отвечают исполнители constant-arrival-rate и ramping-arrival-rate. Документация описывает их как «фиксированное число итераций за заданное время», в отличие от constant-vus, где виртуальные пользователи выполняют «столько итераций, сколько успеют». Терминов «открытая» и «закрытая» в самой документации нет, они пришли из теории очередей.
У wrk есть форк от самого Тене — wrk2, и его подзаголовок описывает суть исправления прямо: постоянная пропускная способность и корректная запись латентности.
Побочный эффект, к которому надо быть готовым
Открытая модель честнее, и от этого тесты начинают падать.
Раньше генератор тормозил заодно с системой, и картинка держалась красивой. Теперь очередь неотвеченных запросов растёт, время ожидания в ней записывается — и хвост распределения показывает то, что там на самом деле есть. Первая реакция команды обычно одна: «инструмент сломался, раньше было нормально».
Раньше не было нормально. Раньше не было видно.
Что с этим делать в отчёте
Одного перехода на открытую модель мало, если результаты потом усредняются.
Хвост надо хранить целиком, а не усреднять при агрегации. Для этого и существуют структуры вроде HdrHistogram: они держат распределение с высоким разрешением в фиксированной памяти и не теряют редкие большие значения.
Практическое правило простое. Если ваш пайплайн где-то усредняет времена ответа за интервал, а потом считает перцентиль от усреднённых значений, вы получаете перцентиль средних — а это совсем другая величина, и она систематически меньше настоящей.
Чего этот разбор не покрывает
Coordinated omission не единственная причина, по которой цифры теста расходятся с продом. Профиль нагрузки может быть выбран неверно, стенд может отличаться от боевого окружения, тестовые данные могут ложиться в кеш там, где реальные не ложатся.
Но это единственная из причин, которая делает результат тем лучше, чем хуже ведёт себя система. Остальные ошибки шумят в обе стороны, а эта врёт строго в одну и всегда в приятную.
Источники
Доклады по теме
- Нагрузочное тестирование для котиков. Scenario- vs Hit-Based-подходы — Иван Приходько
- Подводные камни в нагрузке — Владимир Ситников
- Учимся анализировать результаты нагрузочного тестирования — Алексей Лавренюк