PromQL: rate, агрегация, квантили
На своём стенде посчитал одно и то же двумя способами. Сначала скорость роста по каждому ряду, потом сумма: 360,7 запроса в секунду. Сначала сумма сырых счётчиков, потом скорость роста от неё: 334,6. Числа разошлись, и это ещё мягкий случай - на перезапуске приложения второй способ даёт полную чушь.
Стержень: сначала считают скорость по каждому ряду, потом складывают; квантили берут из гистограммы и понимают, что они приблизительные.
// Формулировки: «как посчитать долю ошибок?», «почему rate под sum, а не наоборот?», «откуда берётся p99?»
Порядок операций решает
Функция расчёта скорости работает с одним рядом данных - так называют уникальное сочетание имени метрики и всех её меток. Она смотрит на значения этого ряда в окне и считает прирост в секунду, попутно замечая сбросы счётчика при перезапуске. Если сначала сложить сырые счётчики разных экземпляров, а потом взять скорость от суммы, то перезапуск ОДНОГО экземпляра обрушит общую сумму, и расчёт увидит это как провал.
Поэтому правильный порядок один: скорость по каждому ряду, сумма поверх неё. На моём замере эти два способа дали 360,7 и 334,6 - разница небольшая только потому, что стенд спокойный.
// Отсюда же берётся главная формула дежурного - доля ошибок. Сумма скорости роста счётчика ошибок делится на сумму скорости роста всех запросов. У меня получилось 0,0404, то есть 4% - и это ровно та доля ошибок, которую я заложил в приложение. Обрати внимание на форму: это ОТНОШЕНИЕ двух скоростей, а не «сколько ошибок за час». Отношение не зависит от того, выросла нагрузка или упала.
- скорость роста
- прирост счётчика в секунду по одному ряду; умеет замечать сброс
- окно
- отрезок времени, по которому считается скорость; обычно кратен интервалу опроса
- доля ошибок
- отношение скорости роста ошибок к скорости роста всех запросов
Квантили и почему среднее врёт
На тех же данных я посчитал среднее время ответа и три квантиля. Среднее 79,1 миллисекунды. Медиана, она же квантиль 0,5 - 15,7 миллисекунды. Квантиль 0,9 - 57,4. А квантиль 0,99 - 2318 миллисекунд.
Смотри, что это значит. Половина запросов быстрее 16 миллисекунд, а средним объявлено 79 - потому что среднее тянут вверх редкие тяжёлые запросы. И самое важное: каждый сотый запрос ждёт больше двух секунд, то есть в 29 раз дольше среднего. Пользователь, сделавший тридцать действий за сеанс, почти наверняка хотя бы раз попадёт в этот хвост.
// Как это считается. Гистограмма хранит счётчики попаданий в корзины: «до 5 миллисекунд», «до 10» и так далее. Квантиль вычисляется прикидкой внутри той корзины, куда он попал: где именно между её границами лежит значение, точно неизвестно. Отсюда два ограничения. Первое: точность не выше ширины корзины, и если между 1 и 2,5 секунды нет границы, значение в этом промежутке будет прикидочным. Второе: всё, что больше самой верхней границы, сваливается в бесконечную корзину, и квантиль в этой области считать нельзя вовсе. Поэтому границы корзин выбирают под ожидаемые времена ответа, а не берут случайные.
- квантиль
- значение, ниже которого лежит заданная доля запросов
- корзина
- интервал длительности; гистограмма считает попадания в него
Что складывать можно, а что нельзя
Отдельная ловушка, на которой ловят почти всех: квантили не складываются и не усредняются. Если у трёх экземпляров сервиса квантиль 0,99 равен 100, 120 и 900 миллисекунд, то общий квантиль по сервису НЕ равен ни их среднему (373), ни максимуму. Он вообще не вычисляется из этих трёх чисел, потому что зависит от того, сколько запросов обслужил каждый экземпляр.
Правильно так: складываются корзины гистограммы (они счётчики, их складывать можно), и уже из суммарных корзин берётся квантиль. Именно поэтому квантиль в запросе всегда стоит СНАРУЖИ, а суммирование по экземплярам - внутри.
// Тот же принцип работает и для агрегации по времени: посчитать средний квантиль за сутки из почасовых значений нельзя. Нужно брать корзины за сутки и считать квантиль по ним. Это, кстати, вторая причина хранить именно гистограммы, а не готовые квантили: готовые числа потом невозможно сложить.
- суммирование корзин
- корзины - счётчики, их складывать можно; квантили нельзя
Как отвечать: «Как посчитать долю ошибок сервиса и почему rate стоит под sum?»
Доля ошибок - это отношение двух скоростей: сумма скорости роста счётчика ошибок делится на сумму скорости роста всех запросов. Я проверял на стенде, где заложил ровно четыре процента ошибок, - формула вернула 0,0404. Форма важна: это отношение, а не число ошибок за час, поэтому оно не зависит от того, выросла нагрузка или упала. Про порядок операций. Расчёт скорости работает с одним рядом данных и умеет замечать сброс счётчика при перезапуске. Если сначала сложить сырые счётчики нескольких экземпляров, а потом взять скорость от суммы, то перезапуск одного экземпляра обрушит общую сумму, и расчёт увидит провал там, где ничего не случилось. Поэтому скорость считается по каждому ряду, а сумма идёт поверх. И то же правило наоборот для квантилей: их складывать нельзя, надо складывать корзины гистограммы и брать квантиль от суммы.
Ответ даёт формулу, объясняет порядок операций через режим отказа и добавляет зеркальное правило для квантилей. Два правила рядом показывают понимание, а не заученный шаблон.
На чём валятся
- −Ставят расчёт скорости поверх суммы счётчиков и получают провалы на каждом перезапуске.
- −Считают долю ошибок как число ошибок за период, а не как отношение скоростей.
- −Усредняют квантили по экземплярам или по времени. Так они не считаются.
- −Берут корзины гистограммы наугад и не могут посчитать квантиль в нужном диапазоне.
- −Смотрят на среднее время ответа и не видят хвост: у меня квантиль 0,99 был в 29 раз выше среднего.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.
- Почему частоту запросов считают через rate по счётчику, а не разностью соседних значений?A)Разность запрещена синтаксисом языка запросовB)Rate сглаживает данные и убирает выбросы измеренийC)Разность считает в штуках, а нужны процентыD)Rate даёт частоту и переживает сброс
показать ответ и разбор
+D)Rate даёт частоту и переживает сброс// разбор: Счётчик обнуляется при каждом рестарте процесса, и наивная разность даёт отрицательный скачок или чудовищный всплеск. Rate за окно видит сброс, чинит его и делит прирост на длительность — получается частота в секунду, сравнимая между инстансами и окнами. Для алертов чаще берут rate за 5 минут, для быстрых графиков — irate, который смотрит два последних отсчёта.
- Нужна суммарная частота запросов по сервису. Почему пишут sum(rate(...)), а не rate(sum(...))?A)Rate считают до агрегации, иначе сброс портит суммуB)Sum не работает с диапазонными векторами по синтаксисуC)Порядок не важен, sum(rate) просто короче записываетсяD)Rate после агрегации даст значения в других единицах измерения
показать ответ и разбор
+A)Rate считают до агрегации, иначе сброс портит сумму// разбор: Rate умеет чинить обнуление, только пока видит каждый ряд отдельно. Если сначала сложить счётчики, то рестарт одного пода уронит сумму, и функция примет это за сброс или пропустит скачок — цифры поедут. Потому канон такой: rate по каждому ряду, затем sum by нужным лейблам. Тот же принцип действует для гистограмм: сначала rate по бакетам, потом суммирование и квантиль.
- На дашборде p99 считают как среднее p99 всех инстансов. Что с этим не так?A)Ничего: при равной нагрузке среднее квантилей корректноB)Среднее занижает результат, потому его умножают на число инстансовC)Квантиль не аддитивен: его считают из суммы бакетов гистограммыD)Проблема лишь в единицах: сначала переводят в миллисекунды
показать ответ и разбор
+C)Квантиль не аддитивен: его считают из суммы бакетов гистограммы// разбор: Квантиль не среднее: усреднять его между источниками математически бессмысленно — один медленный инстанс размывается остальными, и хвост пропадает с графика. Правильный путь для гистограммы: histogram_quantile от суммы rate по бакетам, сгруппированных по le. Тогда учтены все наблюдения сразу. Точность при этом ограничена границами бакетов, и их подбирают под целевой SLO.
- В запросе rate(http_requests_total[5m]) — что означает [5m] и почему без него rate не работает?A)[5m] — это сдвиг запроса на пять минут назадB)[5m] — интервал скрейпа, влияющий на точностьC)[5m] — окно диапазонного вектора для rateD)[5m] — окно сглаживания уже посчитанного rate
показать ответ и разбор
+C)[5m] — окно диапазонного вектора для rate// разбор: [5m] — оператор диапазона: он превращает мгновенный вектор в range-вектор, то есть набор точек за последние 5 минут для каждой серии. rate работает только с range-вектором: он берёт значения в окне, учитывает сбросы счётчика и делит прирост на время, выдавая среднюю частоту в секунду. Без [окна] rate не с чем считать наклон. Окно выбирают в несколько scrape-интервалов.
- Есть частота ошибок по каждому инстансу. Нужно свернуть до частоты по сервису, сохранив лейбл service. Как?A)sum(rate(errors_total[5m])) без группировкиB)avg(rate(errors_total[5m])) by instanceC)rate(sum(errors_total)[5m]) по всем сериямD)sum by(service)(rate(errors_total[5m]))
показать ответ и разбор
+D)sum by(service)(rate(errors_total[5m]))// разбор: Агрегирующие операторы (sum/avg/max) по умолчанию схлопывают все лейблы. Чтобы сохранить разбивку, добавляют by(<лейблы>): sum by(service)(rate(...)) сложит частоты инстансов внутри каждого service и оставит лейбл service на выходе. without(<лейблы>) — обратная форма: свернуть всё, кроме перечисленного. Rate всегда считают до агрегации, чтобы сбросы счётчиков обработались корректно на каждой серии.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.