Прямой ответ
PromQL — язык выборок из временных рядов Prometheus (ряд = имя метрики + набор лейблов — инструментация их породила); четыре конструкции покрывают 90% практики. (1) Селектор: http_requests_total{job="api", status=~"5.."} — метрика + фильтр лейблов (=/!=/=~/!~); «голый» счётчик почти бесполезен — смысл в производных. (2) rate/irate — скорость роста счётчиков (counters растут монотонно — смотреть надо на производную): rate(http_requests_total[5m]) — средние rps за окно (окно ≥ 4× интервала скрейпа — правило; irate — мгновенная по двум последним точкам — для пиков/алертов, rate — для графиков и алертов средней чувствительности); «рисовать счётчик напрямую» — ошибка новичка №1 (растущая прямая вместо нагрузки). (3) Агрегации: sum(rate(...)) by (status) / by (instance) / without (cpu) — схлопывание рядов по лейблам (sum/avg/max/quantile/topk — sum by — главная связка: суммарные rps по кодам ответа); (4) histogram_quantile — процентили из гистограмм: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) — p95 латентности (сумма по всем рядам кроме le — границы бакетов — гистограммы отдельным разбором). Рабочие формулы-шаблоны: доля ошибок — sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])); p95 по эндпоинтам — histogram_quantile(0.95, sum(rate(..._bucket[5m])) by (le, route)); память пода — container_memory_working_set_bytes{pod=~"api.*"}; глубина очереди — просто celery_queue_length{queue="reports"} (gauge — без rate). И два подводных камня: лейблы высокой кардинальности (user_id в лейбле — взрыв рядов — инструментация-дисциплина) и offset/sum without для сравнений «сегодня против вчера» (... / ... offset 1d — недельные сравнения в Grafana — дашборды).
Основы: ряды, типы, функции времени
Модель: каждый scrape создаёт точку (timestamp, value) для каждого сочетания лейблов; типы метрик диктуют обработку: counter (монотонный — только rate/increase — «сколько всего» за период: increase(http_requests_total[1h]) — число запросов за час), gauge (произвольный — смотреть как есть, изменения — delta()/deriv(); память/глубина очереди/температура), histogram (бакеты _bucket/_sum/_count — процентили и средние: средняя латентность rate(_sum[5m]) / rate(_count[5m]) — дешевле гистограммных процентилей, но среднее лжёт на бимодальных распределениях), summary (квантили посчитаны клиентом — процентили готовы, но не агрегируются между инстансами — sum по summary-квантилям — математическая ошибка — знать отличие от histogram). Полезные функции: increase (прирост за окно — «ошибок за сутки»), absent() (ряд исчез — алерт «метрика пропала» — тихие отказы ловятся), changes() (сколько раз менялся — «флаппинг состояния»), time() - process_start_time_seconds (аптайм), clamp_min/max (косметика графиков), predict_linear(...[1h], 4*3600) (линейная экстраполяция — «диск кончится через N часов» — классический проактивный алерт); векторные совпадения — бинарные операции по лейблам (rate(errors[5m]) / rate(requests[5m]) — ряды совпадут по общим лейблам; разные наборы — on(...)/ignoring(...)/group_left — синтаксический порог PromQL — чаще всего решается предварительным sum by к одинаковым лейблам с обеих сторон).
Рабочие запросы: API, ресурсы, очереди
Набор, который закрывает типовой сервис (копируется в Grafana/алерты): RPS по эндпоинтам — sum(rate(http_requests_total[5m])) by (route); доля 5xx — sum(rate(http_requests_total{status=~"5.."}[5m])) by (route) / sum(rate(http_requests_total[5m])) by (route) (алерт: > 0.05 пять минут — пороги); p95/p99 латентности — histogram_quantile по le (+route для разбивки); средняя длительность задачи — rate(task_duration_seconds_sum[5m]) / rate(task_duration_seconds_count[5m]); CPU пода — sum(rate(container_cpu_usage_seconds_total{pod=~"api.*"}[5m])) by (pod) (ядра-эквивалент; против requests — utilization для HPA-диагностики); рабочая память — container_memory_working_set_bytes{...} / on(pod) kube_pod_container_resource_limits{resource="memory"} — доля лимита (лимиты); троттлинг CPU — rate(container_cpu_cfs_throttled_periods_total[5m]) / rate(container_cpu_cfs_periods_total[5m]) — доля троттленных периодов; глубина очереди/возраст старейшей задачи — gauge как есть + алерт на порог/тренд (deriv(queue_length[15m]) > 0 — устойчивый рост — Celery-мониторинг); БД — rate(pg_stat_database_xact_commit[5m]), соединения pg_stat_activity_count / pg_settings_max_connections (PgBouncer-метрики рядом); доступность — up{job="api"} (0/1 по инстансам) и доля — avg(up); диски на прогнозе — predict_linear(node_filesystem_avail_bytes[6h], 24*3600) < 0 — «кончится в сутки». Алерты пишутся тем же PromQL (expr + for — Alertmanager-практика) — «запрос для графика» и «запрос для алерта» — один язык, разные пороги (графики — чувствительные, алерты — с for-окном против шума).
Отладка запросов и производительность
«Запрос ничего не возвращает или возвращает странное» — чек-лист: имя метрики существует ({__name__=~"http.*"} — что вообще есть), лейблы написаны как в данных (route против path — значения из /api/v1/... против шаблона — инструментация определяет — смотреть фактические ряды в /targets и graph-автодополнении), окно rate слишком короткое (< 2 точек в окне — пусто — правило 4× scrape interval), временные сдвиги (метрика появилась недавно — range-запрос за вчера — пусто — не баг), stale-маркеры (ряд пропал 5+ мин — PromQL считает его исчезнувшим — absent-логика). Производительность запросов: высокая кардинальность лейблов — дорогая (sum по миллионам рядов — тормоза — кардинальность-дисциплина); тяжёлые подзапросы/subquery ([5m:1m]) — умеренно; записывающие правила (recording rules) — предрасчёт дорогих выражений (job:http_p95:rate5m — правило считает раз в 15 с — дашборды/алерты читают готовое — стандарт зрелой установки — Grafana-дашборды на recording rules летают); federation/remote-write для глобальных представлений — инфраструктурный уровень. И практика разработки: prometheus /graph — итеративно (селектор → rate → агрегация → порог), Grafana Explore — то же с визуалом; «запрос написан на глаз без проверки рядов» — главная причина «пустых дашбордов» — начинать с фактических лейблов, не из головы.
Практический сценарий: дашборд сервиса с нуля за час
Исходник: сервис на FastAPI инструментирован (метрики: http_requests_total{route,status}, http_request_duration_seconds-гистограмма, celery_queue_length, контейнерные метрики K8s), дашборда нет — разбор инцидентов через kubectl. Построение (Grafana — дашборд-практика): (1) первый ряд — «здоровье за 5 секунд»: RPS sum(rate(http_requests_total[5m])), доля 5xx (формула выше), p95 histogram_quantile(0.95, sum(rate(..._bucket[5m])) by (le)), saturation — CPU/память к лимитам; (2) второй ряд — разбивки: RPS и p95 by route (topk(5, ...) — топ-5 эндпоинтов — «кто горячий»), ошибки by status (502 против 503 — разные истории — LB/health); (3) третий — воркеры и очереди: queue_length + возраст старейшей + rate выполнений (rate(tasks_finished_total[5m]) — приход/расход — дисбаланс виден); (4) четвёртый — БД-зависимость: соединения, commits/s, p95 запросов к БД (если инструментирована — SQL-наблюдаемость); (5) сравнения — панели с offset 1d (нагрузка «как вчера» — аномалии видны без порогов); (6) recording rules на p95/ошибки (дашборд лёгкий, алерты переиспользуют); (7) алерты из тех же expr с for: 5xx>5% 5m — page, p95>1s 10m — ticket, queue растёт 15m — ticket (дисциплина); (8) проверка боем: нагрузка locust — дашборд показал рост p95 за 30 с до алерта (окна rate 5m — компромисс чувствительности/шума — для критичного — irate-панель рядом). Время — 55 минут; первый же инцидент через неделю (деградация внешнего API — p95 route /export ×8 при плоском RPS) — найден по дашборду за 2 минуты вместо «пользователи жалуются, логи чисты» — окупаемость мгновенная; запросы дашборда — в git рядом с кодом (JSON-экспорт — конфиг-как-код — дашборды версионируются и ревьюятся).
FAQ
rate против irate против increase? rate — средняя скорость за окно (гладкие графики, большинство алертов); irate — мгновенная скорость по последним двум точкам (пики видны, шумно — для быстрых алертов/детальных панелей); increase — прирост за окно (rate × окно — «сколько событий произошло» — человеко-читаемые числа в отчётах/аннотациях алертов); все три — только для counter (gauge — delta/deriv); смешивание «irate на 1h окне» — бессмыслица (irate всегда про 2 последние точки, окно лишь ограничивает поиск).
Почему histogram_quantile «врёт»?
Процентиль из гистограммы — оценка внутри бакетов (линейная интерполяция — точность ограничена границами бакетов — p99.9 на бакетах до 10 с — «9.9 с» может быть и 3 с — проектировать бакеты под важные диапазоны — гистограммы); вторая ошибка — суммирование без by (le) (le теряется — quantile не считается/мусор); третья — среднее вместо процентилей на бимодальном распределении (кеш-попадания 5 мс + промахи 800 мс — среднее 400 мс не описывает никого — только квантили); четвёртая — квантили summary разных инстансов усреднены (математически неверно — только histogram агрегируется корректно).
Как сравнивать метрики с разными лейблами (ошибки/запросы по разным наборам)?
Привести к общим лейблам агрегациями: sum(rate(errors[5m])) by (job) / sum(rate(requests[5m])) by (job) — оба вектора по job — деление сойдётся; альтернативы — on(job)/ignoring(instance) (явное указание совпадения) и group_left (many-to-one — «на каждый pod свой лимит» — container_memory_working_set_bytes / on(pod) group_left kube_pod_container_resource_limits...); «деление вернуло пусто» — почти всегда несовпадение наборов лейблов — смотреть оба вектора по отдельности в Explore.
Где учить PromQL эффективно? Маршрут: официальные basics+functions (в шапке) — час теории; свои же метрики в /graph — итерации «селектор→rate→агрегация» (быстрее всего учит наличие живых данных — инструментируйте pet-проект); разбор чужих дашбордов (grafana.com — тысячи публичных — node_exporter/micrometer — копирование с пониманием каждой функции); задачники (promlabs training — бесплатный уровень); практика на инцидентах — каждый разбор заканчивается новым запросом/панелью/алертом (runbook-культура) — через месяц рабочих инцидентов PromQL становится родным.
Источники: документация Prometheus (querying basics, functions, histograms practice) — ссылки в шапке.