Опирается на правила:
R-OBS-MTR-1…R-OBS-MTR-7иR-OBS-MTR-X1…R-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_active | active DB connections |
hikaricp_connections_max | max pool size |
hikaricp_connections_pending | waiters (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 value | R-OBS-MTR-X1 | бизнес-категории (channel, method) |
app=foo вместо service=foo | R-OBS-MTR-X2 | management.metrics.tags.service |
micrometer-core без Prometheus registry | R-OBS-MTR-X3 | micrometer-registry-prometheus |
/actuator/prometheus публично | R-OBS-MTR-X4 | NetworkPolicy / management port |
Tag("service", ...) руками в коде | R-OBS-MTR-2 | management.metrics.tags.* глобально |
paymentTime без единицы | R-OBS-MTR-6 | payment_duration_seconds |
orderCreatedCount (camelCase) | R-OBS-MTR-6 | order_created_total |
Counter без _total suffix | R-OBS-MTR-6 | order_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.