Перейти к содержимому

Метрики: Prometheus в Go

Зачем знать: метрики — основа мониторинга и алертинга. Без них вы не узнаете о проблеме до жалобы клиента. Middle 1 Go-разработчик должен уметь экспортировать метрики через prometheus/client_golang, не положить TSDB cardinality-бомбой, понимать различия Counter/Gauge/Histogram и читать PromQL. В 2026 Prometheus — де-факто стандарт, VictoriaMetrics — альтернатива.

  1. Базовая концепция
  2. Как в Go (с примерами)
  3. Gotchas
  4. Best practices в production
  5. Вопросы на собесе
  6. Practice
  7. Источники

Prometheus — это pull-based time series database для мониторинга. Сервер периодически (по умолчанию каждые 15 секунд) опрашивает (scrape) HTTP endpoint /metrics ваших сервисов и сохраняет полученные числа во временные ряды.

Архитектура:

[ваш Go сервис] ← scrape /metrics → [Prometheus server] → [Grafana / Alertmanager]

Каждый временной ряд (time series) — это уникальная комбинация metric name + labels:

http_requests_total{method="GET", path="/api/users", status="200"}

Значение в каждый момент времени — число (float64).

  • Pull (Prometheus): сервер сам ходит за метриками. Преимущество: централизованная конфигурация, легко обнаружить мёртвый сервис (scrape failed), нет нужды в очереди.
  • Push (StatsD, Graphite): клиент шлёт метрики на сервер. Полезно для коротких задач (batch jobs), где scrape невозможен → отсюда Pushgateway в Prometheus.

Монотонно растущее значение. Никогда не уменьшается (только сбрасывается при перезапуске).

Пример: http_requests_total, errors_total, bytes_sent_total.

PromQL для скорости: rate(http_requests_total[5m]) — запросов в секунду за окно 5 минут.

Текущее значение, которое может расти и падать.

Пример: goroutines_count, memory_usage_bytes, queue_size, temperature.

Распределение значений по бакетам. Метрика автоматически создаёт:

  • metric_bucket{le="0.1"} 100 (количество значений ≤ 0.1)
  • metric_bucket{le="0.5"} 250
  • metric_bucket{le="+Inf"} 300
  • metric_sum — сумма всех значений
  • metric_count — общее количество

Пример: http_request_duration_seconds, db_query_duration_seconds.

Квантили (p50, p95, p99) считаются на стороне Prometheus через histogram_quantile(0.99, rate(metric_bucket[5m])).

Похож на histogram, но квантили предвычислены на клиенте (metric{quantile="0.95"} 0.123).

Минусы summary:

  • Невозможно агрегировать квантили между инстансами (нельзя сложить p95 с двух подов).
  • Дороже по CPU на клиенте (квантильный алгоритм).
  • Бакеты histogram можно перестроить ретроспективно через PromQL.

В 2026 предпочитают Histogram + recording rules.

Предвычисленные PromQL-выражения, сохраняемые как новые time series. Снижают нагрузку на запросы.

groups:
- name: http
rules:
- record: instance:http_requests:rate5m
expr: rate(http_requests_total[5m])

PromQL-условия, по которым отправляется alert в Alertmanager.

- alert: HighErrorRate
expr: rate(errors_total[5m]) > 0.05
for: 10m
  • SLI (Service Level Indicator) — метрика качества: latency p99, error rate, availability.
  • SLO (Service Level Objective) — цель: p99 < 200ms, errors < 0.1%.
  • SLA (Service Level Agreement) — контракт с клиентом (юридический документ).

Метрики Prometheus — основа SLI/SLO. Error budget = (1 − SLO) × time.

  • USE (Brendan Gregg) — для ресурсов: Utilization, Saturation, Errors.
  • RED (Tom Wilkie) — для сервисов: Rate, Errors, Duration.
  • The Four Golden Signals (Google SRE) — Latency, Traffic, Errors, Saturation.

Окно терминала
go get github.com/prometheus/client_golang@latest

В 2026 актуальная версия — v1.20+.

package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":2112", nil)
}

promhttp.Handler() экспортирует Go runtime метрики (GC, goroutines, memory) автоматически. Проверьте: curl localhost:2112/metrics.

package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
)
var (
requestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
)
func handler(w http.ResponseWriter, r *http.Request) {
requestsTotal.WithLabelValues(r.Method, r.URL.Path, "200").Inc()
w.Write([]byte("ok"))
}
func main() {
http.HandleFunc("/api", handler)
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}

promauto.NewCounterVec автоматически регистрирует метрику в дефолтном регистре. Без promauto пришлось бы вызывать prometheus.MustRegister(metric).

var (
goroutineCount = promauto.NewGauge(prometheus.GaugeOpts{
Name: "goroutines_in_pool",
Help: "Current number of goroutines in worker pool",
})
)
func work() {
goroutineCount.Inc()
defer goroutineCount.Dec()
// работа
}
// Или прямая установка
goroutineCount.Set(42)
var (
requestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request latency",
Buckets: prometheus.DefBuckets, // [.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10]
},
[]string{"method", "path"},
)
)
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
duration := time.Since(start).Seconds()
requestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
})
}

prometheus.ExponentialBuckets(0.001, 2, 10) создаёт бакеты [0.001, 0.002, 0.004, ..., 0.512] — экспоненциальный шаг для измерения latency на широком диапазоне.

По умолчанию promauto использует prometheus.DefaultRegisterer. Если нужен отдельный регистр (например, тесты):

reg := prometheus.NewRegistry()
factory := promauto.With(reg)
myCounter := factory.NewCounter(prometheus.CounterOpts{Name: "test"})
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
package middleware
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type Metrics struct {
requestsTotal *prometheus.CounterVec
requestDuration *prometheus.HistogramVec
inFlight prometheus.Gauge
}
func NewMetrics(reg prometheus.Registerer) *Metrics {
factory := promauto.With(reg)
return &Metrics{
requestsTotal: factory.NewCounterVec(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "HTTP requests total",
}, []string{"method", "path", "status"}),
requestDuration: factory.NewHistogramVec(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 16),
}, []string{"method", "path"}),
inFlight: factory.NewGauge(prometheus.GaugeOpts{
Name: "http_in_flight_requests",
Help: "Currently in-flight requests",
}),
}
}
func (m *Metrics) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m.inFlight.Inc()
defer m.inFlight.Dec()
start := time.Now()
rw := &recorder{ResponseWriter: w, status: 200}
next.ServeHTTP(rw, r)
duration := time.Since(start).Seconds()
// ВАЖНО: route, а не URL.Path — иначе cardinality bomb!
route := getRoutePattern(r) // зависит от роутера: chi.RouteContext, gorilla.CurrentRoute
m.requestsTotal.WithLabelValues(r.Method, route, strconv.Itoa(rw.status)).Inc()
m.requestDuration.WithLabelValues(r.Method, route).Observe(duration)
})
}
type recorder struct {
http.ResponseWriter
status int
}
func (r *recorder) WriteHeader(code int) {
r.status = code
r.ResponseWriter.WriteHeader(code)
}
func getRoutePattern(r *http.Request) string {
// chi:
// if rctx := chi.RouteContext(r.Context()); rctx != nil { return rctx.RoutePattern() }
return r.URL.Path
}
import (
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/prometheus"
"google.golang.org/grpc"
)
srvMetrics := grpcprom.NewServerMetrics(
grpcprom.WithServerHandlingTimeHistogram(),
)
reg.MustRegister(srvMetrics)
server := grpc.NewServer(
grpc.UnaryInterceptor(srvMetrics.UnaryServerInterceptor()),
grpc.StreamInterceptor(srvMetrics.StreamServerInterceptor()),
)
// После регистрации всех сервисов:
srvMetrics.InitializeMetrics(server)

Экспортируются grpc_server_handled_total, grpc_server_handling_seconds_bucket и т.д.

var (
dbQueryDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "db_query_duration_seconds",
Help: "DB query duration",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 14),
}, []string{"query"})
dbQueryErrors = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "db_query_errors_total",
Help: "DB query errors",
}, []string{"query", "code"})
)
func (r *Repo) GetUser(ctx context.Context, id string) (*User, error) {
const queryName = "GetUser"
start := time.Now()
defer func() {
dbQueryDuration.WithLabelValues(queryName).Observe(time.Since(start).Seconds())
}()
var u User
err := r.db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id=$1", id).Scan(&u.ID, &u.Name)
if err != nil {
dbQueryErrors.WithLabelValues(queryName, classifyError(err)).Inc()
return nil, err
}
return &u, nil
}

Для database/sql:

import "github.com/prometheus/client_golang/prometheus/collectors"
reg.MustRegister(collectors.NewDBStatsCollector(db, "users_db"))

Экспортирует go_sql_* метрики: открытые соединения, idle, wait_duration и т.д.

Когда стандартные метрики не подходят:

type PoolCollector struct {
pool *MyPool
used *prometheus.Desc
}
func NewPoolCollector(p *MyPool) *PoolCollector {
return &PoolCollector{
pool: p,
used: prometheus.NewDesc("pool_used", "used connections", nil, nil),
}
}
func (c *PoolCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.used
}
func (c *PoolCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(c.used, prometheus.GaugeValue, float64(c.pool.Used()))
}
// reg.MustRegister(NewPoolCollector(pool))

Collect вызывается при каждом scrape — отдавайте «фотографию» состояния.

Sparse histograms с автоматическими бакетами:

hist := promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "...",
NativeHistogramBucketFactor: 1.1, // ~10% resolution
NativeHistogramMaxBucketNumber: 100,
NativeHistogramMinResetDuration: time.Hour,
}, []string{"path"})

Меньше места в TSDB, более точные квантили на длинном диапазоне. Требует Prometheus 2.40+ и поддержки в Grafana 9.2+.

import "github.com/prometheus/client_golang/prometheus/testutil"
func TestCounter(t *testing.T) {
reg := prometheus.NewRegistry()
c := promauto.With(reg).NewCounter(prometheus.CounterOpts{Name: "test"})
c.Inc()
c.Inc()
expected := `
# HELP test
# TYPE test counter
test 2
`
if err := testutil.GatherAndCompare(reg, strings.NewReader(expected), "test"); err != nil {
t.Fatal(err)
}
}
import "github.com/prometheus/client_golang/prometheus/push"
err := push.New("http://pushgateway:9091", "my_batch_job").
Collector(myCounter).
Push()

Pushgateway хранит последний пуш до следующего, Prometheus скрейпит pushgateway. Не используйте для долго живущих сервисов — теряется time-series natural lifecycle.

Совместима с Prometheus exposition format (тот же /metrics). Часто используется в РФ/CIS как drop-in замена с лучшей компрессией и vmagent для агрегации. Ваш Go-код не меняется.


// КАТАСТРОФА
requestsTotal.WithLabelValues(userID).Inc() // 10М уникальных user_id = 10М series

Каждая уникальная комбинация labels — отдельный time series. Prometheus хранит их в памяти. Cardinality > 10М на инстанс — OOM.

Правило: количество уникальных значений каждой label < 100, в произведении < 10К на metric. Никогда не label-ить:

  • user_id, email, request_id, trace_id (бесконечная cardinality)
  • URL целиком (вместо этого — route pattern типа /users/:id)
  • raw error message (классифицируйте: code="timeout", не msg="connection refused after 5s...")
requestsTotal.WithLabelValues("GET", "/api", 200).Inc() // BAD: 200 — int

WithLabelValues принимает ...string. int без конверсии не компилируется, но если у вас неявная конверсия (через fmt.Sprint) — каждый 200/201/202 создаст отдельную series.

Лучше группировать: status_class = "2xx", "4xx", "5xx".

При рестарте сервиса counter обнуляется. PromQL rate() и increase() корректно обрабатывают reset (находят падение и считают, что был reset). Не пытайтесь delta() на counter — она не учитывает resets.

Buckets: []float64{0.1, 0.5, 1, 5, 10} // BAD для latency 1-100ms

Если ваши latency 1-100ms, а первый бакет 100ms — все попадут в le=0.1 и квантили будут бесполезны.

Для HTTP latency:

Buckets: prometheus.ExponentialBuckets(0.001, 2, 16) // 1ms .. ~65s

Для DB:

Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5}

Подберите по своему профилю latency. Лучше изменить буферы один раз заранее, чем потом ломать историю.

Если бакет [+Inf] единственный с большим counter, вы знаете, что были outliers, но не знаете, насколько. Включите summary или native histograms или просто добавьте бакет повыше.

prometheus.Counter thread-safe. Но WithLabelValues — нет дешёвая операция (map lookup + mutex). В hot path делайте:

metric := requestsTotal.WithLabelValues("GET", "/api", "200") // один раз
for ... {
metric.Inc()
}

Если ваш сервис не отвечает на scrape — Prometheus отмечает up{job="myservice"} 0. Делайте alerting:

- alert: ServiceDown
expr: up == 0
for: 5m

promauto.NewCounter(...) падает (panic), если метрика с таким именем уже зарегистрирована. Это часто случается в тестах. Решение: отдельный prometheus.NewRegistry() на тест.

Проверьте:

  • handler установлен на правильный путь (/metrics).
  • порт открыт.
  • если используете chi/gin — handler должен быть зарегистрирован.
r := chi.NewRouter()
r.Handle("/metrics", promhttp.Handler())
r.Use(metricsMiddleware) // BAD: status всегда 200, не fired
r.Use(loggingMiddleware)

Metrics-middleware должен быть первым (обернуть всё), чтобы видеть финальный статус после всех middleware.

Дефолт — 15s. Если ставите 1s — TSDB взлетит по объёму. 15-30s — компромисс. Для бизнес-метрик 1m нормально.

prometheus.DefaultRegisterer.Unregister(myCounter) // в TestMain teardown

Иначе следующий тест паникует на double-register.


<namespace>_<subsystem>_<name>_<unit>
  • Все nameless lowercase, _ как разделитель.
  • Counter — суффикс _total.
  • Histogram — _seconds, _bytes.
  • Gauge — без суффикса (или _count, _size).

Примеры:

  • http_requests_total
  • db_query_duration_seconds
  • worker_pool_size
  • cache_hits_total, cache_misses_total

Для любого RPC-сервиса (HTTP, gRPC) экспортируйте:

  • Raterate(http_requests_total[5m]).
  • Errorsrate(http_requests_total{status=~"5.."}[5m]).
  • Durationhistogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])).

Для пулов, очередей, БД:

  • Utilization — % use (pool_used / pool_size).
  • Saturation — queue length (pool_wait_queue).
  • Errorspool_errors_total.

Допустимые labels:

  • method (GET/POST/…) — <10.
  • status или status_class (2xx/4xx/5xx).
  • route_pattern (/users/:id) — <1000.
  • service, version, env — несколько.

Запрещённые labels:

  • user_id, email, phone, request_id, trace_id.
  • error_message (raw).
  • query_string.

Если Prometheus 2.40+ и Grafana 9.2+, переходите на native histograms — меньше storage, лучше resolution. Но проверьте совместимость с alertmanager rules.

import "github.com/prometheus/client_golang/prometheus/collectors"
reg := prometheus.NewRegistry()
reg.MustRegister(
collectors.NewGoCollector(collectors.WithGoCollections(collectors.GoRuntimeMetricsCollection)),
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
)

Дают go_goroutines, go_memstats_*, process_cpu_seconds_total, process_resident_memory_bytes и десятки runtime-метрик.

Не публикуйте /metrics на бизнес-порту:

go func() {
http.ListenAndServe(":9090", promMux) // только metrics
}()
http.ListenAndServe(":8080", apiMux) // бизнес-API

В K8s одновременно открыты, но в Service публикуете только бизнес-порт.

- record: instance:http_requests:rate5m
expr: rate(http_requests_total[5m])

Dashboards используют precomputed series — меньше нагрузка на TSDB.

Не хардкодьте scrape targets. В K8s используйте prometheus-operator с PodMonitor/ServiceMonitor или kubernetes_sd_configs. Это автоматически добавит/удалит таргеты при rollout.

- alert: HighLatencyP99
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "p99 latency > 500ms"

Алерты — for ≥ scrape interval × 3, чтобы избежать flapping.

- record: service:request_duration_seconds:p99
expr: histogram_quantile(0.99, sum by (le, service) (rate(http_request_duration_seconds_bucket[5m])))

Используйте в alertmanager/dashboard.

Prometheus поддерживает OpenMetrics (расширение exposition format с _created timestamps). promhttp.HandlerFor(reg, promhttp.HandlerOpts{EnableOpenMetrics: true}) — включить.

Если ваш сервис мульти-тенантный, не используйте tenant_id как label если их > 1000. Лучше — отдельный registry или агрегация в коллекторе.

Храните JSON-дашборды в репозитории, деплойте через Grafana provisioning или Grafana Cloud API.

- record: slo:availability:5m
expr: sum(rate(http_requests_total{status!~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

Цель: slo:availability:5m > 0.999 для p99 за месяц.


  1. Чем pull-based отличается от push-based мониторинга? Pull (Prometheus): сервер скрейпит targets, легко обнаружить мёртвый сервис, целевая конфигурация на сервере. Push (StatsD): клиент шлёт, нужна очередь или Pushgateway для batch jobs.

  2. Counter vs Gauge vs Histogram? Counter — монотонно растёт (requests_total). Gauge — текущее значение (memory). Histogram — распределение по бакетам (latency).

  3. Почему Summary считается устаревшим? Квантили не агрегируются между инстансами, дороже CPU, нельзя пересчитать ретроспективно. Histogram + histogram_quantile в PromQL — гибче.

  4. Что такое cardinality? Произведение уникальных значений всех labels метрики. Высокая cardinality → много time series → OOM/медленные запросы.

  5. Почему нельзя label-ить user_id? Каждый user_id создаёт новую time series. 10М users = 10М series = OOM. user_id логировать, но не метрики.

  6. Что делает rate(metric[5m])? Среднюю скорость роста counter за окно 5 минут (per-second). Учитывает counter resets.

  7. Как вычислить p99 из histogram? histogram_quantile(0.99, sum by (le) (rate(metric_bucket[5m]))). Не забудьте by (le) — иначе квантиль усреднится по бакетам неверно.

  8. Что такое RED и USE? RED — Rate, Errors, Duration (для сервисов). USE — Utilization, Saturation, Errors (для ресурсов).

  9. Что такое SLI/SLO/SLA? SLI — метрика (latency p99). SLO — цель (p99 < 200ms). SLA — контракт с клиентом (юридический).

  10. Что такое Pushgateway? Промежуточный сервер для коротких задач (batch, CI/CD). Клиент пушит, Prometheus скрейпит pushgateway. Не для long-running сервисов.

  11. Чем promauto отличается от prometheus? promauto автоматически регистрирует метрику в DefaultRegisterer. Без него — prometheus.MustRegister(metric) вручную.

  12. Что такое recording rule? Precomputed PromQL, сохраняемый как новая series. Снижает нагрузку на тяжёлые запросы.

  13. Как добавить кастомную метрику для пула соединений? Реализовать prometheus.Collector интерфейс (Describe + Collect), регистрировать через MustRegister.

  14. Зачем http_request_duration_seconds_bucket суффикс le? le = “less than or equal”. Бакет le="0.5" содержит все наблюдения ≤ 0.5s. Кумулятивные.

  15. Что такое native histograms? Sparse-бакеты с автоматическим bucketing, экономия места в TSDB (Prometheus 2.40+).

  16. Как Prometheus обрабатывает counter reset? rate()/increase() детектируют падение значения и считают, что был reset, обновляют расчёт. Не используйте delta() на counters.

  17. Чем VictoriaMetrics лучше Prometheus? Лучшая компрессия, vmagent для агрегации/relabel, кластерный mode, не использует mmap → меньше OOM. Совместима с Prometheus exposition.

  18. Что такое scrape interval и почему нельзя ставить 1s? Период между scrapes (default 15s). Меньший интервал = больше data points = TSDB взлетает по объёму.

  19. Зачем разделять /metrics на отдельный порт? Безопасность (не выставлять метрики наружу), не путать с бизнес-трафиком, отдельный rate limit.

  20. Что такое up метрика? Автоматическая метрика Prometheus: 1 если последний scrape ОК, 0 если нет. Базис для алерта “сервис мёртв”.

  21. Как тестировать метрики? Создать отдельный prometheus.NewRegistry(), проверять через testutil.GatherAndCompare.

  22. Что такое exemplars? Точечные ссылки в метрике на trace_id — позволяют из Grafana прыгнуть в Tempo на конкретный медленный запрос.

  23. Как сделать service discovery в K8s? Prometheus Operator + PodMonitor/ServiceMonitor CRDs, или нативный kubernetes_sd_configs.

  24. Что такое Alertmanager? Сервер для маршрутизации, dedup, grouping и отправки алертов в Slack/PagerDuty/etc.

  25. Сколько метрик можно отправлять с одного инстанса? Безопасно — до 100К time series. Свыше — нужны recording rules, агрегация, VictoriaMetrics.


Напишите HTTP-сервер на :8080 (бизнес /api/hello) и :9090 (/metrics). Экспортируйте Counter app_hello_total, инкрементируйте при каждом GET /api/hello.

Реализуйте RED middleware с Counter http_requests_total{method,route,status} и Histogram http_request_duration_seconds. Замерьте через wrk/hey.

Сделайте функцию normalizeRoute(path string) string, которая /users/123/users/:id. Подключите в middleware.

Создайте WorkerPoolCollector, который при каждом scrape отдаёт pool_active (Gauge), pool_idle (Gauge), pool_queued (Gauge).

Подключите go-grpc-middleware/v2/interceptors/prometheus к gRPC-серверу. Сделайте dashboard в Grafana с RED для метода GetUser.

Для метрики db_query_duration_seconds подберите экспоненциальные бакеты от 0.5ms до 30s. Объясните выбор.

Напишите PromQL-выражения:

  • p99 latency за 5 минут.
  • Error rate (5xx / total) в %.
  • Скорость роста ошибок per route.
  • Saturation пула (pool_used / pool_size).

Создайте alerting rule в alerts.yaml: алертим если p99 latency > 500ms за 10 минут с severity warning.


  1. Официальная документация client_golanghttps://pkg.go.dev/github.com/prometheus/client_golang.
  2. Prometheus docs: Best Practiceshttps://prometheus.io/docs/practices/.
  3. Prometheus: Histograms and Summarieshttps://prometheus.io/docs/practices/histograms/.
  4. Brian Brazil, “Prometheus: Up & Running” (O’Reilly, 2nd edition, 2023).
  5. Google SRE Book: Monitoringhttps://sre.google/sre-book/monitoring-distributed-systems/.
  6. Brendan Gregg: USE Methodhttps://www.brendangregg.com/usemethod.html.
  7. Tom Wilkie: RED Methodhttps://www.weave.works/blog/the-red-method-key-metrics-for-microservices-architecture/.
  8. VictoriaMetrics bloghttps://victoriametrics.com/blog/.
  9. Native histograms KubeCon talk — поиск на YouTube за 2024-2026.
  10. Prometheus Operatorhttps://prometheus-operator.dev/.