Mid💻Практика7 min

Методологии мониторинга: RED, USE и Golden Signals

RED Method, USE Method и Four Golden Signals — структурированные подходы к мониторингу с PromQL, Grafana и примерами на PHP/Go

Методологии мониторинга: 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 │
                         └───────┘ └────────┘ └───────┘

Проверь себя

5 из 8
🧪

PromQL запрос `predict_linear(node_filesystem_avail_bytes[6h], 24*3600) < 0` выполняет:

🧪

Почему Google в Four Golden Signals рекомендует отслеживать latency успешных и ошибочных запросов отдельно?

🧪

Какая иерархия дашбордов рекомендуется для эффективного troubleshooting?

🧪

Для чего нужен параметр `for: 5m` в Prometheus alerting rule?

🧪

В методе USE буква S (Saturation) означает: