Опирается на правила: R-OBS-MTR-1R-OBS-MTR-7 и R-OBS-MTR-X1R-OBS-MTR-X4 из Observability Style Guide → раздел 2. Metrics.

Важно знать

  • Micrometer + Prometheus registry через spring-boot-starter-actuator + micrometer-registry-prometheus. Endpoint /actuator/prometheus.
  • Стандартизованные dimensions service/env/version через management.metrics.tags.* в application.yml — не дублировать в коде.
  • RED для HTTP автоматически: rate, errors, duration через http_server_requests_seconds.
  • USE для resources автоматически: JVM memory, executor pools, HikariCP connections.
  • Custom business metrics через MeterRegistry: Counter, Gauge, Timer, DistributionSummary.
  • Низкая cardinality в tags — payment_method (CARD/SBP/CRYPTO) ОК; user_id / order_id → OOM в Prometheus.
  • /actuator/prometheus не публично — только internal scraper через network policy / VPN.

Метрики — основа понимания работы сервиса под нагрузкой. RED (Rate, Errors, Duration) для request-driven, USE (Utilization, Saturation, Errors) для resources, плюс business metrics. UCP опирается на Micrometer как фасад и Prometheus как backend.

Подключение

R-OBS-MTR-1: зависимости Gradle:

implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-registry-prometheus")

Endpoint exposed:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus

После старта /actuator/prometheus отдаёт текстовый формат Prometheus с метриками. Prometheus scraper тянет каждые 15s, складывает в TSDB.

Стандартные dimensions

R-OBS-MTR-2: три тега на каждой метрике, конфигурируются один раз:

spring:
  application:
    name: order-service

management:
  metrics:
    tags:
      service: ${spring.application.name}
      env: ${ENV:dev}
      version: ${BUILD_VERSION:unknown}

Это даёт возможность фильтровать в Grafana по service="order-service", env="prod". Без этих тегов невозможно отличить prod от staging на одной dashboard, или сравнить deploy v1.2.3 с v1.2.4.

Не дублировать в каждой custom-метрике через .tag("service", "order-service")management.metrics.tags.* применяется глобально. Дублирование .tag() приводит к ошибке IllegalArgumentException: duplicate tag.

RED для HTTP — автоматически

R-OBS-MTR-3: Spring Boot Actuator + Micrometer автоматически собирают:

# Rate
sum(rate(http_server_requests_seconds_count[5m])) by (uri, method)

# Errors
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (uri)

# Duration p95
histogram_quantile(0.95, sum by (le, uri) (rate(http_server_requests_seconds_bucket[5m])))

Включить percentiles в application.yml:

management:
  metrics:
    distribution:
      percentiles-histogram:
        http.server.requests: true
      slo:
        http.server.requests: 100ms,500ms,1s,5s

slo: 100ms,500ms,1s,5s — это threshold-buckets, по которым Prometheus считает «сколько запросов уложилось в 100ms». percentiles-histogram: true включает классический histogram с 64 bucket-ами для точных квантилей.

USE для resources — автоматически

R-OBS-MTR-4: JVM и infrastructure-метрики собираются actuator/prometheus без настройки.

МетрикаЧто показывает
jvm_memory_used_bytes{area="heap"}utilization heap
jvm_memory_max_bytes{area="heap"}потолок heap
jvm_gc_pause_seconds_sumсколько суммарно GC паузил
executor_active_threads{name="taskExecutor"}saturation thread pool
executor_pool_size_threadsразмер пула
hikaricp_connections_activeactive DB connections
hikaricp_connections_maxmax pool size
hikaricp_connections_pendingwaiters (saturation indicator)

Алерты для saturation:

- alert: HeapMemoryHigh
  expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85
  for: 10m

- alert: HikariPoolSaturated
  expr: hikaricp_connections_pending > 0
  for: 5m

Custom business metrics

R-OBS-MTR-5: бизнес-события измеряются через MeterRegistry.

@Component
public class OrderMetrics {

    private final Counter orderCreatedCounter;
    private final Timer paymentProcessingTimer;
    private final DistributionSummary orderAmountSummary;

    public OrderMetrics(MeterRegistry registry) {
        this.orderCreatedCounter = Counter.builder("order_created_total")
            .description("Total orders created")
            .tag("channel", "web")
            .register(registry);
        this.paymentProcessingTimer = Timer.builder("payment_processing_seconds")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
        this.orderAmountSummary = DistributionSummary.builder("order_amount_rubles")
            .baseUnit("rubles")
            .register(registry);
    }

    public void orderCreated() { orderCreatedCounter.increment(); }

    public void recordPaymentDuration(Duration duration) {
        paymentProcessingTimer.record(duration);
    }

    public void recordOrderAmount(BigDecimal amount) {
        orderAmountSummary.record(amount.doubleValue());
    }
}

Применение в handler:

@UseCase
@RequiredArgsConstructor
public class CreateOrderHandler implements UseCaseHandler<CreateOrderCommand, Order> {

    private final OrderRepository orderRepository;
    private final OrderMetrics metrics;

    @Override
    @Transactional
    public Order handle(CreateOrderCommand command) {
        var order = orderRepository.save(Order.create(command));
        metrics.orderCreated();
        metrics.recordOrderAmount(order.amount());
        return order;
    }
}

Типы:

  • Counter — монотонно растущий счётчик. order_created_total, payment_failed_total.
  • Gauge — текущее значение, может расти и падать. queue_size, active_users_total.
  • Timer — длительность операции с histogram. payment_processing_seconds.
  • DistributionSummary — произвольные числовые значения с histogram. order_amount_rubles, email_size_bytes.

Имена метрик — snake_case с единицей

R-OBS-MTR-6: соглашение Prometheus — snake_case, единица измерения в имени.

order_created_total                  ✓ — Counter с _total суффиксом
payment_processing_seconds           ✓ — Timer с _seconds
queue_size                           ✓ — Gauge, число элементов
order_amount_rubles                  ✓ — DistributionSummary с единицей валюты
orderCreatedCount                    ✗ — camelCase, без _total
paymentTime                          ✗ — без единицы

Низкая cardinality в tags

R-OBS-MTR-7: tag value — это уникальная категория, не уникальный ID.

// ХОРОШО — низкая cardinality
orderCreatedCounter
    .tag("channel", "web")        // 3-5 значений: web, mobile, api
    .tag("payment_method", "card") // 5-10 значений
    .increment();

// ПЛОХО — high cardinality, миллион time series в Prometheus
orderCreatedCounter
    .tag("user_id", String.valueOf(userId))    // миллион уникальных значений
    .tag("order_id", String.valueOf(orderId))  // миллион уникальных значений
    .increment();

Prometheus хранит отдельный time series для каждой комбинации tag values. Миллион user_id × миллион order_id × дата × N retention dimension = десятки миллиардов time series = OOM scraper-а и Prometheus-а.

Для high-cardinality observability — distributed tracing (Tempo/Jaeger), не метрики. См. Tracing.

Что запрещено

High-cardinality tags

R-OBS-MTR-X1 — главная ошибка метрик. Не использовать user_id, order_id, request_id, session_id, email как tag value.

// КАТАСТРОФА — миллион time series, OOM scraper
Counter.builder("api_request_total")
    .tag("user_id", String.valueOf(userId))
    .register(registry)
    .increment();

Если нужна детализация по user — использовать tracing (один span per request) или structured logs.

Не-стандартизованные dimensions

R-OBS-MTR-X2: один сервис пишет app=foo, другой — service_name=foo, третий — service=foo. Это ломает cross-service dashboards.

# СТАНДАРТ — все сервисы UCP используют
management:
  metrics:
    tags:
      service: ${spring.application.name}
      env: ${ENV}
      version: ${BUILD_VERSION}

Micrometer без registry

R-OBS-MTR-X3: только micrometer-core без micrometer-registry-prometheus — метрики собираются в SimpleMeterRegistry (in-memory) и теряются на рестарте.

Всегда подключаем Prometheus registry (или другой production-registry: Datadog, NewRelic).

/actuator/prometheus без auth публично

R-OBS-MTR-X4: prometheus endpoint раскрывает внутренние метрики бизнеса (order_amount_rubles_sum, revenue_total). Не должен быть доступен извне.

Варианты защиты:

  • NetworkPolicy в K8s — /actuator/prometheus доступен только из namespace мониторинга.
  • Отдельный management port (management.server.port: 8081) — этот port не expose-ится наружу через Ingress.
  • Spring Security basic auth для /actuator/** — но добавляет работы scraper-у.

Что запрещено — таблица

АнтипаттернПравилоЧто взамен
user_id / request_id / order_id как tag valueR-OBS-MTR-X1бизнес-категории (channel, method)
app=foo вместо service=fooR-OBS-MTR-X2management.metrics.tags.service
micrometer-core без Prometheus registryR-OBS-MTR-X3micrometer-registry-prometheus
/actuator/prometheus публичноR-OBS-MTR-X4NetworkPolicy / management port
Tag("service", ...) руками в кодеR-OBS-MTR-2management.metrics.tags.* глобально
paymentTime без единицыR-OBS-MTR-6payment_duration_seconds
orderCreatedCount (camelCase)R-OBS-MTR-6order_created_total
Counter без _total suffixR-OBS-MTR-6order_created_total

Куда дальше

  • Observability → раздел 2. Metrics — нормативные формулировки.
  • Logging — structured JSON, MDC.
  • Tracing — high-cardinality observability через spans.
  • SLO и алерты — multi-window burn rate, error budget.
  • Configuration — management port, exposure.
  • Resilience → observability — CB/Bulkhead/Retry metrics.
  • Health checks — liveness vs readiness.