awesome-everything EN
↑ Обратно к восхождению

Наблюдаемость

Cardinality как драйвер затрат: label, PII, exemplars и семплирование

Суть Каждая уникальная комбинация label — отдельный временной ряд, биллируемый отдельно в RAM и в hosted-биллинге. Железная дисциплина: только ограниченные, actionable label идут на метрики — всё остальное живёт в логах и трейсах, связанных через exemplars.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Cloudflare, 2022: глобальный outage preceded Prometheus-серверами, упавшими по OOM от cardinality нового label на метрике длительности запросов. Фикс занял 90 минут — но post-mortem обязал ввести per-team cardinality budget и CI-проверку, отклоняющую новые label-измерения выше порога. Алерт пришёл от собственного мета-мониторинга Prometheus — не от сервисов, которые он должен был наблюдать.

Математика cardinality

Каждый временной ряд Prometheus живёт в head block TSDB примерно при 3 КБ RAM. Число рядов — произведение cardinality всех label-значений:

число рядов = |route| × |method| × |status_class| × |service| × |region|

Для сервиса с 200 роутами, 5 методами, 4 классами статусов, 50 подами в 3 регионах:

200 × 5 × 4 × 50 × 3 = 600 000 рядов
600 000 × 3 КБ = ~1,8 ГБ только для этой одной метрики

Добавь user_id с 100к активными пользователями:

600 000 × 100 000 = 60 миллиардов рядов

Это обрушит 16 ГБ Prometheus-сервер за секунды. TSDB не может индексировать столько рядов, а путь append сериализуется на head mutex.

Стоимость в hosted-бэкендах

При тарификации Datadog ~$0,05 / custom metric / host / month (цены 2024), неограниченный label user_id, вырастающий до 1 млн рядов, добавляет ~$50k/month за ночь из-за одного небрежного label.

Линейность cardinality-к-затратам делает это security- и финансовым инцидентом, а не просто проблемой производительности.

PII и угрозы безопасности

Наивный счётчик Errors с label error_message или stack_trace публикует текст исключения в метрики-скрейп, которые обычно менее защищены, чем база данных приложения. Если сообщение содержит ввод пользователя — “could not find user alice@example.com” — этот PII оказывается в метрическом бэкенде, доступном всей инженерной организации.

Реальный инцидент: платёжный сервис в 2021 году утёк телефонные номера клиентов через неудачно названный label failed_phone. Post-mortem обязал ввести глобальный pre-commit hook, помечающий любые новые label с паттернами известных PII.

Правило аудита label: называй label по классу ошибки (auth_failed, db_timeout, parse_error), никогда по содержимому ошибки. Аудитируй имена label как security review item, а не только performance review.

Тип labelПримерГде должен быть
Ограниченный, actionableroute, method, status_class, regionLabel на метрике ✓
Неограниченный, высокая cardinalityuser_id, request_id, session_tokenТолько в логах / трейсах ✗
Содержимое PIIemail, phone, ip_address, stack_traceНикогда в метриках ✗✗

Exemplars: мост между метриками и трейсами

Если нельзя поместить trace_id в label метрики (неограниченная cardinality), как перейти от скачка p99 к медленному запросу, вызвавшему его? Exemplars.

Prometheus 2.32+ и реализация гистограмм OpenTelemetry поддерживают exemplars: семплированные trace ID, прикреплённые к отдельным наблюдениям гистограммы. Когда histogram_quantile показывает p99 в 800 мс, клик по скачку в Grafana открывает exemplar — trace ID из запроса, попавшего в этот бакет. Один клик — и ты видишь полное дерево спанов.

# HELP http_request_duration_seconds
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.2"} 14324 # {trace_id="abc123"} 0.183
http_request_duration_seconds_bucket{le="0.4"} 14329

Exemplar trace_id="abc123" прикреплён к конкретному наблюдению 0.183, а не добавлен как label к метрике. Cardinality остаётся плоской; возможность углубиться сохраняется.

Агрегация против семплирования

RED + USE метрики — предварительно агрегированные: они суммируют по всем запросам или по всему wall-clock времени без семплирования. Счётчики бакетов гистограммы обновляются инкрементально; ни одно наблюдение не выбрасывается.

Трейсы — противоположное: семплированные (обычно 0,1–5%), потому что каждый трейс несёт полный путь запроса со всеми спанами. Senior-паттерн:

  • Предварительно агрегируй RED + USE на источнике — 100% покрытие, ограниченное хранилище.
  • Семплируй трейсы: head-based на 5% для экономии, tail-based на 100% для ошибок и медленных запросов (duration > цели SLO) — чтобы редкий медленный путь всегда имел трейс.
  • Exemplars связывают оба: метрика показывает скачок (агрегат), exemplar указывает на конкретный трейс (семпл).

Четырёхсигнальный стек — RED-метрики, USE-метрики, семплированные трейсы, семплированные профили — компонуется тогда и только тогда, когда они делят ключи label (http.route, service.name, status_class). Семантические конвенции OpenTelemetry формализуют эти join-ключи.

Почему это работает

Самореференциальная наблюдаемость: Prometheus сам эмитирует RED- и USE-метрики. prometheus_tsdb_head_series (растёт слишком быстро → cardinality explosion), prometheus_engine_query_duration_seconds_p99 (слишком медленно → запросы с таймаутом) и prometheus_rule_evaluation_duration_seconds_p99 (слишком медленно → задержки алертов) — это сигналы, поймавшие инциденты Cloudflare и Discord 2022–2023. В обоих случаях собственный мета-мониторинг Prometheus сработал раньше, чем RED-алерты затронутых сервисов. Мониторинг монитора — не опциональная возможность.

Викторина

Команда добавляет новый label 'country_code' (220 возможных значений) к существующим RED-метрикам. Текущее число рядов — 10 000. Сколько рядов станет после изменения?

Викторина

Инженер хочет перейти от скачка p99 latency в Prometheus-гистограмме к конкретному медленному запросу. Команда не может добавить trace_id как label метрики (cardinality). Какое правильное решение?

Вспомните перед уходом
  1. 01
    Сервис с 50 роутами × 5 методами × 4 классами статусов имеет 1000 рядов для RED-метрик. Команда добавляет 'customer_tier' с 3 значениями. Сколько рядов теперь и почему?
  2. 02
    Какой PII-риск несёт label метрик по error_message и какова правильная альтернатива?
  3. 03
    Что такое exemplars и как они решают проблему cardinality trace_id?
Итог

Cardinality — это количество уникальных комбинаций label-значений на Prometheus-метрике — каждая комбинация является отдельным временным рядом, хранимым в RAM при ~3 КБ и биллируемым отдельно в hosted-бэкендах. Один неограниченный label (user_id, request_id, содержимое error_message) может вырастить сервис с 200 рядами до миллионов и обрушить Prometheus TSDB или добавить десятки тысяч долларов к ежемесячному счёту за ночь. Железное правило: только ограниченные, actionable label идут на метрики — шаблоны роутов, HTTP-методы, классы статусов, имя сервиса, регион. Всё high-cardinality (trace ID, user ID, содержимое ошибок) живёт в логах и трейсах. Exemplars закрывают пробел: Prometheus 2.32+ и OTel-гистограммы поддерживают прикрепление семплированного trace ID к конкретным наблюдениям, позволяя Grafana переходить от скачка p99 к полному дереву спанов медленного запроса без добавления trace_id как label-множителя cardinality. PII в label — одновременно проблема cardinality и утечки данных — аудитируй имена label как security review item.

Связанные уроки
встречается в167
Продолжить восхождение ↑Native histograms, SLO и паттерны production-сбоев
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.