Урок по треку Наблюдаемость и SRE 8 мин

PromQL за 8 минут

Селекторы, rate и increase, агрегации, histogram_quantile, сопоставление векторов, offset и предсказание.

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 часа» и «сервис пропал».

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.