Зачем разные типы
Метрика - это не просто число. Семантика определяет как её агрегировать.
http_requests_totalза минуту вырос на 1200 → rate = 20 req/s. Counter монотонен, разница между измерениями = темп.memory_usage_bytes = 2.3 GBсейчас. Прямое значение, gauge может скакать вверх-вниз. Среднее за час = average.request_duration_secondsраспределение → нужны percentiles (p50, p99). Считать среднее бесполезно: hides tail latency.
Каждый тип, это контракт между приложением и системой запросов
(PromQL/MetricsQL). Использование rate() на gauge, мусор. avg() на
histogram, теряешь смысл.
Counter, монотонно растущее
Семантика: «сколько раз случилось событие». Только вверх, reset на рестарт процесса.
http_requests_total{method="GET",status="200"} 12345В PromQL не используй raw counter. Всегда через rate() или
increase():
rate(http_requests_total[5m]) # req/s в среднем за 5 мин
increase(http_requests_total[1h]) # сколько прироста за час
sum by (status)(rate(http_requests_total[5m])) # по статусам
rate() автоматически handles counter reset (при рестарте).
Примеры counter в реальных системах:
process_cpu_seconds_total, суммарное CPUnode_network_receive_bytes_total, байты пришлиkafka_consumer_messages_consumed_total
Gauge, текущее значение
Семантика: «значение прямо сейчас». Может расти и падать.
node_memory_MemAvailable_bytes 4521234432
goroutines_active 142
queue_depth{queue="orders"} 87В PromQL, напрямую:
node_memory_MemAvailable_bytes / 1024 / 1024 / 1024
avg_over_time(queue_depth[10m])
max(queue_depth) by (queue)
Не используй rate() на gauge! rate(temperature[5m]) бессмысленно.
Histogram, distribution с buckets
Cемантика: «сколько событий попало в каждый bucket по значению».
http_request_duration_seconds_bucket{le="0.1"} 4500http_request_duration_seconds_bucket{le="0.25"} 4800http_request_duration_seconds_bucket{le="0.5"} 4920http_request_duration_seconds_bucket{le="1"} 4980http_request_duration_seconds_bucket{le="+Inf"} 5000http_request_duration_seconds_sum 350.5
http_request_duration_seconds_count 5000
Каждый bucket, counter. le="0.5" = «сколько <=0.5 сек».
Percentile вычисляется query-time:
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
Aggregatable между instances: можно sum by (status). В отличие от
summary.
Подбирать buckets, внимательно. Default 10 buckets от 5ms до 10s подходит для HTTP, но не для DB queries (нужны microseconds) или batch jobs (минуты). Слишком много buckets → cardinality explosion (cardinality-explosion).
Summary, pre-computed quantiles
Клиент сам считает quantile (p50, p95, p99) и экспортирует:
http_request_duration_seconds{quantile="0.5"} 0.012http_request_duration_seconds{quantile="0.95"} 0.087http_request_duration_seconds{quantile="0.99"} 0.241http_request_duration_seconds_sum 350.5
http_request_duration_seconds_count 5000
Проблемы summary:
- Не aggregatable: нельзя
sum by (instance), это математически неверно. Имеет смысл только per-instance. - Дорого CPU/memory, клиент держит running quantile estimator.
- Quantile fixed at compile time, нельзя посчитать p99.9 если клиент не экспортит.
Использовать summary только когда p99 дёшево считать в клиенте и агрегация не нужна. В большинстве случаев, histogram лучше.
Histogram vs Summary
| Критерий | Histogram | Summary |
|---|---|---|
| Где считается | server (PromQL) | client |
| Aggregatable across instances | да | нет |
| Quantile precision | bucket-bounded | exact-ish |
| Quantile changeable post-fact | да (новый запрос) | нет |
| Memory in client | low | medium |
| Cardinality | bucket × labels | quantile × labels |
Правило: используй histogram. Summary, только для legacy.
Native histogram (Prom 2.40+)
Проблема classic histogram: фиксированные buckets, либо много (cardinality), либо мало (плохая precision).
Native histogram (a.k.a sparse histogram), буткеты на лету, логарифмическая шкала, sparse encoding:
metric_native_histogram{} {schema:1, count:5000, sum:350.5,positive_buckets: ...sparse...}
- Один time series на метрику (вместо N buckets)
- Точность ≈ 1-3% на любом quantile
- В 100× меньше storage чем classic с такой же precision
Требует:
- Client SDK с поддержкой (Go ≥1.16, Python ≥0.18, Java ≥1.0)
- Prometheus 2.40+ с
--enable-feature=native-histograms - Grafana 10+ для визуализации
Production-ready с Prom 2.50+ (2023). Должен стать дефолтом.
Exemplars, мост к трассам
Exemplar, конкретный sample, прикреплённый к bucket:
http_request_duration_seconds_bucket{le="0.5"} 4920 # {trace_id="abc123"} 0.42 1683456789.123«Один из 4920 запросов в этом bucket был с trace_id=abc123». В Grafana можно кликнуть на точку графика и прыгнуть в трассу (tracing-basics).
Поддержка:
- Prometheus 2.26+ (требует OpenMetrics format)
- SDK: Go, Java, Python, .NET
- Grafana 8+
OpenMetrics, formal spec
OpenMetrics, стандарт CNCF (RFC-style) для метрик, расширяет Prometheus exposition format:
- UTF-8, не ASCII
# UNITline- Exemplars формализованы
- JSON serialization (опционально)
В 2025, большинство SDK exportят в OpenMetrics автоматически. Prometheus и [[opentelemetry|OTel collector]] оба понимают.
Когда что-то пошло не так
rate(my_metric[5m])даёт 0, counter называется как gauge, PromQL вычисляетrateот непрерывно одинакового значения. Переименуй метрику в_total.- p99 latency скачет, недостаточно samples в окне (rate over
[5m]для 0.1 req/s = 30 событий, шумно). Расширь окно до[30m]. - histogram_quantile() возвращает NaN, buckets не покрывают
наблюдаемые значения, или нет данных в окне. Проверь
_count > 0. - Cardinality explosion, добавили
endpoint=/api/v1/user/123каждый user_id создаёт новый series. Рефактор:endpoint=/api/v1/user/:id. - Summary quantile неточный после рестарта, client estimator сбрасывается. Это особенность summary, не баг.
- Buckets
le=строкой, не числом, Prom expects strings"0.1","0.5".le=0.1(число) ломается.