Методологии мониторинга: RED, USE и Golden Signals
Зачем нужны методологии мониторинга
В типичной системе можно собрать тысячи метрик: CPU, memory, disk, network, HTTP requests, database queries, queue lengths, cache hit rates... Без структурированного подхода мониторинг превращается в хаос — десятки бесполезных дашбордов, сотни алертов, на которые никто не реагирует, и полная растерянность при инциденте.
Методологии мониторинга решают три задачи:
| Задача | Без методологии | С методологией |
|---|---|---|
| Что мониторить? | Всё подряд → информационный шум | Конкретные метрики для каждого типа компонента |
| Как строить дашборды? | Хаотичные графики | Структурированные панели по методологии |
| Когда алертить? | Алерт на каждый чих → alert fatigue | Алерты на симптомы, не на причины |
Три ключевые методологии:
┌─────────────────────────────────────────────────┐
│ Что мониторим? │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Сервисы │ │ Ресурсы │ │
│ │ (endpoints) │ │ (CPU, RAM) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌────▼────┐ ┌─────▼─────┐ │
│ │ RED │ │ USE │ │
│ │ Method │ │ Method │ │
│ └────┬────┘ └─────┬─────┘ │
│ │ │ │
│ └───────────┬────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Golden Signals │ │
│ │ (объединяет) │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────┘
RED Method — мониторинг сервисов
RED Method разработан Tom Wilkie (Grafana/Weaveworks) и оптимален для request-driven сервисов — API, микросервисов, web-приложений. Для каждого сервиса отслеживаем три метрики:
| Метрика | Что измеряет | Единица |
|---|---|---|
| Rate | Количество запросов в секунду | req/s |
| Errors | Количество ошибочных запросов в секунду | errors/s |
| Duration | Распределение времени обработки запроса | ms (p50, p95, p99) |
Rate — запросы в секунду
Rate показывает текущую нагрузку на сервис. Резкий рост может означать DDoS или вирусный трафик, резкое падение — проблемы upstream.
PromQL запросы для Rate:
# Общий RPS (requests per second)
rate(http_requests_total[5m])
# RPS по endpoint
sum by (handler) (rate(http_requests_total[5m]))
# RPS по HTTP-методу
sum by (method) (rate(http_requests_total{job="api-server"}[5m]))
# Сравнение с прошлой неделей (для аномалий)
rate(http_requests_total[5m]) / rate(http_requests_total[5m] offset 7d)
Errors — частота ошибок
Errors — количество неуспешных запросов. Важно отслеживать и абсолютные числа, и процент от общего количества.
# Error rate (абсолютный)
sum(rate(http_requests_total{status=~"5.."}[5m]))
# Error rate (процент от всех запросов)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) * 100
# Ошибки по типу (4xx отдельно от 5xx)
sum by (status) (rate(http_requests_total{status=~"[45].."}[5m]))
# Error rate по endpoint (найти проблемный)
sum by (handler) (rate(http_requests_total{status=~"5.."}[5m]))
/
sum by (handler) (rate(http_requests_total[5m])) * 100
Duration — время ответа
Duration отслеживается через перцентили. Среднее значение бесполезно — оно скрывает «длинный хвост» медленных запросов.
# p50 (медиана) — типичный пользовательский опыт
histogram_quantile(0.50, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# p95 — 95% запросов быстрее этого значения
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# p99 — хвост распределения, самые медленные запросы
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# p99 по endpoint
histogram_quantile(0.99,
sum by (le, handler) (rate(http_request_duration_seconds_bucket[5m]))
)
PHP: экспорт RED-метрик из Symfony
::code-group
<?php
declare(strict_types=1);
namespace App\EventSubscriber;
use Prometheus\CollectorRegistry;
use Prometheus\Histogram;
use Prometheus\Counter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
final class MetricsSubscriber implements EventSubscriberInterface
{
private Counter $requestCounter;
private Histogram $requestDuration;
private float $startTime;
public function __construct(
private readonly CollectorRegistry $registry,
) {
// RED: Rate + Errors counter
$this->requestCounter = $this->registry->getOrRegisterCounter(
'app',
'http_requests_total',
'Total HTTP requests',
['method', 'handler', 'status']
);
// RED: Duration histogram
$this->requestDuration = $this->registry->getOrRegisterHistogram(
'app',
'http_request_duration_seconds',
'HTTP request duration in seconds',
['method', 'handler'],
[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onRequest', 1000],
KernelEvents::RESPONSE => ['onResponse', -1000],
KernelEvents::EXCEPTION => ['onException', 0],
];
}
public function onRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$this->startTime = microtime(true);
}
public function onResponse(ResponseEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
$response = $event->getResponse();
$handler = $request->attributes->get('_route', 'unknown');
$method = $request->getMethod();
$status = (string) $response->getStatusCode();
// Rate + Errors (status 5xx = errors)
$this->requestCounter->inc(['method' => $method, 'handler' => $handler, 'status' => $status]);
// Duration
$duration = microtime(true) - $this->startTime;
$this->requestDuration->observe(
$duration,
['method' => $method, 'handler' => $handler]
);
}
public function onException(ExceptionEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
$handler = $request->attributes->get('_route', 'unknown');
$this->requestCounter->inc([
'method' => $request->getMethod(),
'handler' => $handler,
'status' => '500',
]);
}
}
::
Go: экспорт RED-метрик
::code-group
package middleware
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
// RED: Rate + Errors
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests",
},
[]string{"method", "handler", "status"},
)
// RED: Duration
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
},
[]string{"method", "handler"},
)
)
// Metrics is a middleware that records RED metrics for every HTTP request.
func Metrics(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Wrap ResponseWriter to capture status code
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(rw, r)
duration := time.Since(start).Seconds()
handler := r.URL.Path // or extract route pattern from mux
method := r.Method
status := strconv.Itoa(rw.statusCode)
httpRequestsTotal.WithLabelValues(method, handler, status).Inc()
httpRequestDuration.WithLabelValues(method, handler).Observe(duration)
})
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
::
USE Method — мониторинг ресурсов
USE Method разработан Brendan Gregg и предназначен для ресурсов — CPU, Memory, Disk, Network. Для каждого ресурса отслеживаем:
| Метрика | Что измеряет | Пример |
|---|---|---|
| Utilization | % времени, когда ресурс занят | CPU 75% |
| Saturation | Очередь работы, которую ресурс не успевает обработать | CPU run queue 15 |
| Errors | Количество ошибок ресурса | disk I/O errors |
USE для каждого ресурса
| Ресурс | Utilization | Saturation | Errors |
|---|---|---|---|
| CPU | node_cpu_seconds_total |
node_load1 (load average) |
Machine check exceptions |
| Memory | node_memory_MemAvailable_bytes |
node_vmstat_pgmajfault (page faults) |
ECC errors, OOM kills |
| Disk | node_disk_io_time_seconds_total |
node_disk_io_time_weighted_seconds_total |
node_disk_io_errors_total |
| Network | node_network_receive_bytes_total |
node_network_transmit_drop_total |
node_network_receive_errs_total |
PromQL для USE метрик
CPU:
# Utilization: процент использования CPU
1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))
# Saturation: load average vs количество CPU
node_load1 / count without (cpu) (node_cpu_seconds_total{mode="idle"})
# High CPU alert (>85% за 10 минут)
1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[10m])) > 0.85
Memory:
# Utilization: процент использованной памяти
1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
# Saturation: major page faults (swapping)
rate(node_vmstat_pgmajfault[5m])
# OOM kills (errors)
increase(node_vmstat_oom_kill[1h])
Disk:
# Utilization: % времени, когда диск занят
rate(node_disk_io_time_seconds_total[5m])
# Saturation: средняя очередь I/O
rate(node_disk_io_time_weighted_seconds_total[5m])
# Available disk space (предсказание заполнения)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 24*3600) < 0
Network:
# Utilization: bandwidth usage (Mbps)
rate(node_network_receive_bytes_total{device="eth0"}[5m]) * 8 / 1e6
# Saturation: dropped packets
rate(node_network_transmit_drop_total{device="eth0"}[5m])
# Errors
rate(node_network_receive_errs_total{device="eth0"}[5m])
Four Golden Signals — Google SRE
Four Golden Signals описаны в книге «Site Reliability Engineering» от Google. Это четыре ключевые метрики, которые нужно мониторить для любого пользовательского сервиса:
| Сигнал | Описание | Аналог в RED/USE |
|---|---|---|
| Latency | Время обработки запроса (успешных и ошибочных отдельно) | RED: Duration |
| Traffic | Объём нагрузки на систему | RED: Rate |
| Errors | Процент неуспешных запросов | RED: Errors, USE: Errors |
| Saturation | Насколько «загружена» система (ресурсы, очереди) | USE: Saturation |
Важный нюанс: Latency ошибок
Google подчёркивает: нужно отслеживать latency отдельно для успешных и ошибочных запросов. Ошибка HTTP 500, возвращённая за 5ms, может скрыть проблему с latency — если считать её наравне с успешными запросами, средний latency «улучшится».
# Latency успешных запросов
histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket{status!~"5.."}[5m]))
)
# Latency ошибочных запросов (должен быть близок к 0 для fast-fail)
histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket{status=~"5.."}[5m]))
)
Сравнительная таблица методологий
| Аспект | RED | USE | Golden Signals |
|---|---|---|---|
| Автор | Tom Wilkie | Brendan Gregg | Google SRE |
| Для чего | Сервисы (request-driven) | Ресурсы (CPU, RAM, Disk) | Любой сервис |
| Метрики | Rate, Errors, Duration | Utilization, Saturation, Errors | Latency, Traffic, Errors, Saturation |
| Когда использовать | Микросервисы, API, Web | Серверы, БД, очереди | Универсальный подход |
| Сильная сторона | Простота, фокус на UX | Глубокий анализ ресурсов | Полнота покрытия |
| Слабая сторона | Не видит ресурсы | Не видит пользователей | Более абстрактный |
На практике все три методологии используются вместе: RED для сервисов, USE для инфраструктуры, Golden Signals как общий фреймворк для обсуждения в команде.
Alerting на основе методологий
Symptom-based vs Cause-based алерты
| Подход | Пример | Когда срабатывает |
|---|---|---|
| Symptom-based (рекомендуется) | «Error rate > 1%» | Когда пользователь затронут |
| Cause-based (осторожно) | «CPU > 90%» | Когда ресурс перегружен (может быть ложным) |
Symptom-based алерты предпочтительны, потому что они срабатывают, когда пользователь реально страдает. CPU 95% — это не проблема, если latency в норме. А latency 5 секунд — проблема, даже если CPU 30% (возможно, проблема в IO).
Severity levels
| Severity | Действие | Время реакции | Пример |
|---|---|---|---|
| Page (critical) | Разбудить дежурного | 5 мин | Error rate > 5%, latency p99 > 5s |
| Ticket (warning) | Создать задачу | Следующий рабочий день | Error rate > 1%, disk 80% |
| Log (info) | Записать | При расследовании | Единичные ошибки, CPU > 70% |
Prometheus Alerting Rules для RED
# alerts/red-alerts.yml
groups:
- name: red-method-alerts
rules:
# HIGH ERROR RATE — page on-call
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: page
annotations:
summary: "Error rate above 5% for 5 minutes"
description: "Current error rate: {{ $value | humanizePercentage }}"
runbook: "https://wiki.example.com/runbooks/high-error-rate"
# HIGH LATENCY — page on-call
- alert: HighLatencyP99
expr: |
histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
) > 2.0
for: 5m
labels:
severity: page
annotations:
summary: "p99 latency above 2 seconds"
description: "p99 latency: {{ $value | humanizeDuration }}"
# LOW TRAFFIC — possible upstream issue
- alert: LowTraffic
expr: |
sum(rate(http_requests_total[10m])) < 10
for: 15m
labels:
severity: ticket
annotations:
summary: "Traffic dropped below 10 rps for 15 minutes"
# ERROR BUDGET BURN — SLO-based
- alert: ErrorBudgetBurn
expr: |
(
1 - (sum(rate(http_requests_total{status!~"5.."}[1h]))
/ sum(rate(http_requests_total[1h])))
) > (1 - 0.999) * 14.4
for: 5m
labels:
severity: page
annotations:
summary: "Error budget burning 14.4x faster than allowed"
Alert fatigue и как бороться
Alert fatigue — состояние, когда инженер перестаёт реагировать на алерты из-за их количества. Это самая опасная проблема в мониторинге.
| Причина | Решение |
|---|---|
| Слишком много алертов | Оставить только symptom-based |
| Flapping алерты (вкл/выкл) | Увеличить for (5-15 мин) |
| Неактуальные пороги | Пересматривать пороги ежемесячно |
| Алерт без действия | Если нет runbook — удалить алерт |
| Дублирующие алерты | Группировка в AlertManager |
Правило: Каждый алерт с severity=page должен иметь runbook. Если инженер получил алерт и не знает что делать — алерт бесполезен.
Dashboards
Иерархия дашбордов
┌─────────────────────────────────────────────────────┐
│ Level 1: Overview │
│ Все сервисы на одном экране: RPS, errors, latency │
│ Используется: при беглой проверке, начало инцидента │
└──────────────────────┬──────────────────────────────┘
│ click on service
┌──────────────────────▼──────────────────────────────┐
│ Level 2: Service Detail │
│ RED метрики конкретного сервиса по endpoints │
│ Используется: локализация проблемы │
└──────────────────────┬──────────────────────────────┘
│ click on instance
┌──────────────────────▼──────────────────────────────┐
│ Level 3: Instance Detail │
│ USE метрики конкретного инстанса + логи + трейсы │
│ Используется: root cause analysis │
└─────────────────────────────────────────────────────┘
SLO Dashboard — Error Budget Burn Rate
# SLO: 99.9% availability (error budget = 0.1%)
# Текущий error rate за 30 дней (SLI)
1 - (
sum(increase(http_requests_total{status!~"5.."}[30d]))
/
sum(increase(http_requests_total[30d]))
)
# Оставшийся error budget (%)
1 - (
(1 - (sum(increase(http_requests_total{status!~"5.."}[30d]))
/ sum(increase(http_requests_total[30d]))))
/ (1 - 0.999)
)
# Burn rate (1.0 = штатный расход, >1 = бюджет расходуется быстрее)
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/ sum(rate(http_requests_total[1h]))
)
/ (1 - 0.999)
PromQL для типичных дашбордов
# Top 5 endpoints по latency
topk(5,
histogram_quantile(0.99,
sum by (le, handler) (rate(http_request_duration_seconds_bucket[5m]))
)
)
# Apdex score (порог 0.5s)
(
sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
+
sum(rate(http_request_duration_seconds_bucket{le="2.0"}[5m]))
)
/ 2
/ sum(rate(http_request_duration_seconds_count[5m]))
# Request rate heatmap (по часам дня)
sum by (handler) (increase(http_requests_total[1h]))
Monitoring Stack — общая архитектура
┌──────────────────────────────────────────────────────────────────┐
│ Applications │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ PHP API │ │ Go Svc │ │ Worker │ │ Frontend │ │
│ │ /metrics │ │ /metrics │ │ /metrics │ │ (RUM) │ │
│ └─────┬─────┘ └─────┬────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
└─────────┼──────────────┼─────────────┼──────────────┼────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────┐
│ Prometheus (scrape) │
│ Pull-based collection every 15s │
│ ┌──────────────────────────────────┐ │
│ │ TSDB: 15-day retention │ │
│ │ Recording rules (pre-compute) │ │
│ │ Alerting rules │ │
│ └──────────────────────────────────┘ │
└──────────┬────────────────────────┬─────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ Grafana │ │ AlertManager │
│ ┌─────────────┐ │ │ ┌──────────────┐ │
│ │ RED Dash │ │ │ │ Group alerts │ │
│ │ USE Dash │ │ │ │ Deduplicate │ │
│ │ SLO Dash │ │ │ │ Route │ │
│ │ On-Call Dash│ │ │ │ Silence │ │
│ └─────────────┘ │ │ └──────┬───────┘ │
└──────────────────┘ └─────────┼──────────┘
│
┌─────────┼──────────┐
▼ ▼ ▼
┌───────┐ ┌────────┐ ┌───────┐
│ Slack │ │PagerDuty│ │ Email │
└───────┘ └────────┘ └───────┘