График сырого счётчика http_requests_total выглядит как линия, которая только растёт: монотонно вверх, после рестарта обрыв к нулю и снова вверх. На такой картинке невозможно увидеть ни всплеск трафика, ни провал: рост счётчика означает «запросы когда-то были», а не «запросы идут сейчас». PromQL решает это двумя механизмами. Первый — селекторы: имя метрики выбирает все её серии, а фигурные скобки с условиями сужают выборку: http_requests_total{status="500"} оставляет только пятисотые, регулярка status=~"5.." — все пятисотые коды разом, а сочетание route="/api",status="200" — конкретную серию из урока про лейблы. Второй механизм — окна: суффикс [5m] превращает мгновенный выбор в диапазон за пять минут, range vector, с которым уже работают функции. Любой запрос дашборда складывается из этих деталей: сначала выбрать серии, затем применить функцию, при необходимости агрегировать. Начнём с самой ходовой связки — имя, окно и rate.
Селекторы: четыре вида matchers#
Условий в фигурных скобках всего четыре вида, и их стоит выучить наизусть, потому что встречаются они в каждом запросе. Точное равенство status="500" оставляет серию с конкретным значением. Неравенство status!="200" выбрасывает успешные ответы и оставляет всё остальное — включая редкие коды, которые вы забыли перечислить. Регулярка со знаком равенства status=~"5.." выбирает по шаблону, а с восклицательной тильдой status!~"2.." — всё, что шаблону не подходит: так собирают трафик неуспеха без перечисления кодов. Важная деталь, о которую спотыкаются пришедшие из других языков: регулярки PromQL полностью заякорены от начала и до конца, то есть 5.. означает ровно три символа, начинающихся с пятёрки, и ничего лишнего. Комбинировать условия можно сколько нужно, перечисляя через запятую: route="/api",status=~"5.." — пятисотые одного маршрута.
PromQL: rate prometheus-счётчика и окно#
rate(counter[5m]) считает среднюю скорость роста счётчика в секунду на пятиминутном окне: берёт значения внутри окна, учитывает сбросы после рестартов и экстраполирует результат на края. Для счётчиков это главный инструмент — rate prometheus рекомендует практически всегда, когда хочется увидеть «сколько в секунду», от запросов до заказов. У окна два жёстких требования. Первое: внутри должно лежать хотя бы две точки, иначе результат пустой — при скрейпе раз в 30 секунд окно короче минуты бессмысленно, а сразу после старта сервиса графики будут молчать пару минут. Второе: длина окна выбирается под скорость изменений, и практический минимум — четыре скрейп-интервала: при интервале 15 секунд берите не меньше минуты, при интервале 30 — не меньше двух. К gauge функция rate неприменима по смыслу: датчик не монотонный, и «скорость роста» температуры ничего не значит. Для gauge есть своё семейство: avg_over_time, max_over_time и min_over_time усредняют или выбирают экстремум по окну, не претендуя на счётную природу значения.
Три запроса, с которых начинается дашборд
GO# Трафик: все запросы в секунду
sum(rate(http_requests_total[5m]))
# Трафик по маршрутам
sum by (route) (rate(http_requests_total[5m]))
# Пятисотые в секунду
sum(rate(http_requests_total{status=~"5.."}[5m]))Первый запрос складывает все серии в одно число — линию общего трафика. Второй группирует по лейблу route: вместо одной линии получается по линии на маршрут, и график читается как таблица. Третий срезает серии регуляркой по статусу: matcher со знаком =~ пропускает все значения, подходящие под шаблон 5.. — пятьсотые коды целиком.
Доля ошибок: деление мгновенных векторов#
Доля ошибок — это пятисотые, поделённые на весь трафик:
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
Хитрость в том, что PromQL делит векторы, а не числа: каждой серии левого запроса ищется пара справа по совпадающим лейблам. Если слева серии с лейблами route и status и справа — тоже route и status, пары найдутся не для всех: статуса 500 нет среди серий «всего трафика» с тем же лейблом status, и часть делений развалится. Поэтому сначала суммируют: sum(...) схлопывает векторы до одного элемента без лейблов, и деление становится простым. Хотите долю ошибок по маршрутам — суммируйте с группировкой с обеих сторон: sum by (route) в числителе и в знаменателе, и группировка обязана совпадать, иначе векторы снова не найдут пары. С нулями поведение такое: если знаменатель пуст — серий нет — результат пустой; если знаменатель ноль при ненулевом числителе — плюс бесконечность, и Grafana рисует разрыв. Ночной сервис без трафика с честно разорванным графиком — норма, а не баг.
Агрегации: sum, avg, min, max, count#
sum — не единственная агрегация, и остальные полезно держать в голове, потому что отвечают они на другие вопросы. count(http_requests_total) вернёт число серий: моментально видно, сколько комбинаций лейблов породил сервис — быстрый тест на кардинальность. avg усредняет значения серий и годится для датчиков вроде числа горутин по инстансам, но не для счётчиков: средний счётчик — бессмысленная величина, когда инстансы живут разное время. min и max находят крайности: max по инстансам покажет самого нагруженного узла. Отдельно стоит topk(3, rate(...)) — топ три серии по значению: быстрый способ найти самый горячий маршрут без разглядывания линий. Без группировки все серии складываются в одну. Все агрегации принимают необязательную группировку by или without: без (instance) складывает всё, кроме инстансов, — удобный способ увидеть кластер целиком, не перечисляя остальные лейблы руками.
Латентность: p99 из гистограммы
GOhistogram_quantile(
0.99,
sum by (le, route) (rate(http_request_duration_seconds_bucket[5m]))
)Гистограмма хранит кумулятивные бакеты, поэтому при агрегации лейбл le обязан сохраняться: sum by (le, route) группирует по маршруту, не теряя границы бакетов. histogram_quantile интерполирует квантиль внутри бакета; rate обязателен, как и для любых счётчиков. Подробно про устройство бакетов и сетки — в уроке про гистограммы.
increase, irate и длина окна#
increase(counter[1h]) отвечает на вопрос «сколько за час»: та же скорость, умноженная на длину окна; его место — графики «заказов за день» и «ошибок за неделю». irate(counter[5m]) — мгновенная скорость по двум последним точкам: она реагирует на всплески раньше rate, но и дёргается от каждого скрейпа, поэтому для дашбордов и алертов берут rate, а irate оставляют для разбора инцидента в реальном времени. Отдельного внимания заслуживает поведение после рестарта: счётчик обнуляется, и rate трактует уменьшение как сброс, прибавляя остаток прошлого значения — трафик между последним скрейпом до смерти и первым после подъёма учтён. На коротких окнах это выглядит как единичный выброс, и это честная цена данных: процесс не мог сообщить ничего между падением и подъёмом. С длиной окна связана и самая частая жалоба на свежем дашборде — дрожащая линия. При скрейпе раз в 30 секунд окно в минуту содержит всего две-три точки: каждая новая перестраивает расчёт радикально, и линия скачет. Правило четырёх скрейп-интервалов спасает: окно от двух минут при 30-секундном скрейпе даёт четыре-пять точек, и rate становится устойчивым. Обратная крайность — слишком длинное окно: час сглаживает всё, включая инцидент, и вы увидите аварию через полчаса после пользователей. Рабочая связка: дашборды смотрят на окна пять-пятнадцать минут ради читаемости, алерты считают на окнах одна-две минуты ради скорости обнаружения, а дрожь внутри них гасит for. Если сомневаетесь — поднимите окно вдвое и сравните: изменение формы графика при том же трафике означает, что дело в окне, а не в сервисе.
rate формально применится к любой метрике: ошибки запроса не будет. Но если серия — датчик, который растёт и убывает, отрицательные приращения rate трактует как сброс счётчика и учитывает по своим правилам, и результат оказывается бессмысленным числом. Симптом на дашборде: «скорость» числа пользователей в очереди скачет около нуля без связи с реальностью. Для gauge используйте avg_over_time, max_over_time и min_over_time — они агрегируют сами значения по окну.
У Prometheus есть собственный веб-интерфейс на порту 9090: вкладка Graph выполняет выражение и рисует за период, а табличный режим показывает мгновенный результат по сериям — для отладки это быстрее, чем пересоздание панели. API запросов — /api/v1/query и /api/v1/query_range — позволяет вызвать выражение curl-ом, что удобно в smoke-тестах дашборда. И помните о свежести: последние один-два скрейп-интервала ещё не доехали, поэтому справа на графике бывает обрыв — такова природа pull-модели из первого урока.
Функции первых запросов#
| Выражение | На чём работает | Что отвечает |
|---|---|---|
| rate(counter[5m]) | счётчики | средняя скорость роста в секунду с учётом сбросов |
| increase(counter[1h]) | счётчики | приращение за окно целиком |
| irate(counter[5m]) | счётчики | мгновенная скорость по двум последним точкам |
| avg_over_time(gauge[10m]) | датчики | среднее значение по окну |
| sum by (route) (...) | векторы | складывание серий с сохранением лейбла |
| histogram_quantile(0.99, ...) | бакеты гистограмм | квантиль латентности из распределения |
Проверка: что возвращает rate
Что возвращает rate(http_requests_total[5m])?
Проверка: срез по маршрутам
Какой запрос покажет трафик отдельными линиями на каждый маршрут?
Что вернёт sum?
Прочитайте код и выберите, что он выведет:
http_requests_total{route="/api",status="200"} 500
http_requests_total{route="/api",status="500"} 12
http_requests_total{route="/health",status="200"} 800
sum(http_requests_total)Допишите группировку
Допишите группировку в числителе и знаменателе доли ошибок по маршрутам.
От селектора к срезу
Расставьте запросы по порядку усложнения: от сырого селектора до среза пятисотых по маршрутам.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: первые запросы
Запросы из этого урока — готовый материал для панелей: в уроке про Grafana соберём из них дашборд, а в уроке про алерты превратим запрос доли ошибок в правило с порогом. Практика требует запущенного стека: учебный сервис и Prometheus поднимаются локально, песочница сайта PromQL не исполняет — язык считается сервером, а не приложением. Начните с трёх запросов из урока на своих метриках и посчитайте в уме, сколько серий вернёт каждый: сумма по всем маршрутам — одна линия, по маршрутам — столько, сколько значений отдаёт лейбл route, который вы завели в уроке про лейблы. Затем спросите у count(http_requests_total), сколько серий у вас на самом деле, — и сравните с собственной прикидкой. Полная программа курса — на странице Prometheus.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Селектор выбирает серии, окно превращает его в range vector, функции делают расчёт — так собирается любой запрос.
- rate(counter[5m]) — средняя скорость в секунду; главный инструмент для счётчиков, для gauge не годится.
- Окно — минимум четыре скрейп-интервала: две точки обязательны, четыре-пять дают устойчивую линию.
- sum by (route) сохраняет лейбл в группировке; sum без by складывает всё в одно число.
- Доля ошибок — деление rate пятисотых на rate всего; группировки по обеим сторонам обязаны совпадать.
- p99 считается histogram_quantile по бакетам с сохранением лейбла le.
- Дашборды смотрят на длинные окна, алерты — на короткие; дрожь гасит for из урока про правила.
Домашнее задание
Поднимите учебный сервис и Prometheus локально и воспроизведите три запроса урока: трафик целиком, по маршрутам и пятисотые. Затем посчитайте долю ошибок делением и проверьте, что группировки sum by (route) по обеим сторонам совпадают. Сознательно примените rate к gauge из своего сервиса и найдите бессмысленный результат на графике — теперь вы знаете, почему он такой. Песочница PromQL не исполняет: язык считается сервером Prometheus.
Личные заметкиПусто
Вопросы и ответы по уроку
Почему rate на свежезапущенном сервисе пустой?
Окну нужны минимум две точки: при скрейпе раз в 15 секунд первые результаты появятся через полминуты-минуту. Пустой результат сразу после старта — норма, а не ошибка выражения.
Чем sum отличается от avg и когда что брать?
sum складывает серии, avg усредняет. Для трафика и ошибок берут sum: события складываются. Среднее по квантилям гистограмм считать нельзя — сначала rate по бакетам, затем histogram_quantile.
Можно ли писать PromQL в приложении на Go?
Да, через клиент api/prom/v1 — но обычно запросы живут в Grafana и правилах алертов. Приложение только экспортирует метрики; запросы — зона мониторинга, и смешивать их с бизнес-кодом не стоит.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
WebSocket в Go: real-time чаты и уведомления
Курс WebSocket на Go: gorilla/websocket, эхо-сервер, hub-паттерн, комнаты, авторизация соединений, ping/pong, reconnect и масштабирование. 25 уроков.
- Поднимать WS-сервер и апгрейдить HTTP-соединения
- Писать readPump/writePump без гонок на каждое соединение
- Строить hub: регистрацию, рассылку и комнаты
- Авторизовывать соединения и ограничивать сообщения