PromQL язык запросов Prometheus. Его же понимают VictoriaMetrics (MetricsQL, расширенный диалект), Thanos и Mimir. На собесе почти всегда просят написать RPS, долю ошибок и p95.
Селекторы
http_requests_total # все ряды метрики
http_requests_total{job="api", status="200"} # точное совпадение
http_requests_total{status=~"5.."} # регулярное выражение
http_requests_total{status!~"2..|3.."} # не совпадает
Регулярные выражения в матчерах закреплены целиком: status=~"5" найдёт только "5", а не "500".
Результат бывает двух видов:
- instant vector: по одному значению каждого ряда на момент запроса;
- range vector: набор значений за окно,
http_requests_total[5m]. Его нельзя нарисовать напрямую, его передают в функции вродеrate.
rate, irate, increase
Counter растёт, его сырое значение бесполезно. Нужна скорость:
rate(http_requests_total[5m]) # средняя скорость в секунду за 5 минут
irate(http_requests_total[5m]) # по двум последним точкам окна
increase(http_requests_total[1h]) # прирост за час = rate × 3600
rateсглаживает, подходит для алертов и большинства графиков.irateреагирует на резкие скачки, но дёргается и на крупном масштабе пропускает пики.increaseэтоrate, умноженный на длину окна. Из-за экстраполяции к краям окна значение может быть дробным даже для целых счётчиков.- Все три корректно обрабатывают сброс счётчика при рестарте процесса.
- Окно должно покрывать минимум несколько опросов. Распространённое правило: не меньше четырёх
scrape_interval. В Grafana для этого есть переменная$__rate_interval. rateтолько для counter. Для gauge изменения смотрят черезderivилиdelta.
Агрегации
sum(rate(http_requests_total[5m])) # RPS всего сервиса
sum by (route) (rate(http_requests_total[5m])) # по маршрутам
sum without (instance, pod) (rate(http_requests_total[5m]))
topk(5, sum by (route) (rate(http_requests_total[5m])))
count by (job) (up == 1) # сколько целей живо
Сначала rate, потом sum. rate(sum(...)) неверен: сумма счётчиков разных подов сбрасывается, когда перезапускается любой из них, и rate по такой сумме врёт.
Доля ошибок
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
Агрегируйте числитель и знаменатель до деления. Без sum деление идёт между рядами с одинаковыми метками: ряд status="500" в числителе найдёт в знаменателе самого себя, и получится 1, то есть «100% ошибок», а ряды 200 просто пропадут из результата.
Гистограммы и перцентили
histogram_quantile(0.95,
sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
rateпо бакетам, затемsum by (le): меткуleобязательно сохранить, иначе функции не из чего считать. Хотите по маршрутам:sum by (le, route).- Внутри бакета значение интерполируется линейно, поэтому точность зависит от границ бакетов. Если квантиль попал в бакет
+Inf, возвращается верхняя граница последнего конечного бакета. - Среднее:
rate(..._sum[5m]) / rate(..._count[5m]). - Доля запросов быстрее 300 мс (для SLO): бакет
le="0.3"делить на_count. Это работает, только если такая граница бакета существует.
Сопоставление векторов
Бинарная операция между векторами сопоставляет ряды с одинаковыми метками. Управлять этим можно:
# метки разные, сопоставить только по instance
node_filesystem_avail_bytes / on(instance, mountpoint) node_filesystem_size_bytes
# многие к одному: добавить к метрике метку версии из info-метрики
rate(http_requests_total[5m]) * on(pod) group_left(version) app_info
Сравнения и фильтры
up == 0 оставляет только ряды, где условие истинно (это фильтр, а не true/false). С модификатором bool результат 0 или 1: up == bool 0.
Время
rate(http_requests_total[5m] offset 1w) # неделю назад
max_over_time(node_load1[1h]) # максимум за час
avg_over_time(up[1d]) # доля времени, когда цель была жива
predict_linear(node_filesystem_avail_bytes[6h], 4 * 3600) < 0 # кончится ли диск за 4 часа
max_over_time(rate(http_requests_total[5m])[1h:1m]) # подзапрос
Отсутствие данных
Если сервис пропал целиком, у метрики нет рядов, и алерт rate(...) == 0 не сработает: сравнивать не с чем. Для этого есть absent(up{job="api"}) или absent_over_time(...).
Частые ошибки
rateпо gauge или сырое значение counter на графике.rate(sum(...))вместоsum(rate(...)).- Потеря
leв агрегации передhistogram_quantile. - Окно
rateменьше двух интервалов опроса: пустые графики. - Деление векторов с разными метками без
sumилиon.
Что спросят на собесе
- Напишите RPS, долю ошибок и p95 по сервису.
- Чем
rateотличается отirateиincrease. - Почему сначала
rate, потомsum. - Как работает
histogram_quantileи чего он не умеет. - Как написать алерт «диск кончится через 4 часа» и «сервис пропал».