Кардинальность: почему часть имён сгруппирована

Если на странице производительности или метрик вы увидели предупреждение о лимите различных значений, а в списке появилось <cardinality-limit> — эта страница объясняет, что произошло и что с этим делать.

Что случилось

Проект прислал больше различных значений какого-то поля, чем допускает потолок (по умолчанию 10 000 за час). Значения сверх потолка не отбрасываются, а группируются под общим именем <cardinality-limit>.

Ограничиваются поля, значения которых приходят от вашего кода и по природе открыты:

ПолеОткуда берётся
Имя транзакцииtransaction у Sentry SDK, имя корневого спана у OTLP
Окружениеenvironment в конфигурации SDK
Имя метрикиимя инструмента OTLP
Сервисservice.name в ресурсных атрибутах
Операция спанаop у Sentry SDK, span.kind/имя у OTLP
Хостимя хоста (host.name в ресурсных атрибутах OTLP)

Почему потолок вообще есть

Эти значения попадают в ключи сортировки ClickHouse и в группировку предагрегатов. Каждое новое значение создаёт отдельную строку агрегата — со своими состояниями перцентилей, — которая не схлопнётся ни с чем и проживёт всю ретенцию.

Десяток эндпойнтов — это десяток строк на пятиминутку. Сто тысяч «эндпойнтов» — сто тысяч строк на пятиминутку, и хранилище общее на все проекты инстанса. Потолок защищает не столько нас от вас, сколько соседние проекты друг от друга.

Настоящая причина — почти всегда переменная в имени

В подавляющем большинстве случаев это не злой умысел и не реальный рост, а идентификатор, попавший в имя:

GET /users/8812/profile     ← должно быть  GET /users/:id/profile
GET /orders/a7f3e9.../items ← должно быть  GET /orders/:id/items
queue.process.job-88213     ← должно быть  queue.process
environment = "web-07"      ← должно быть  environment = "production"

Узнать это можно по примерам в предупреждении: если значения отличаются только числом или хэшем — причина найдена.

Как исправить

Sentry SDK (любой язык). Имя транзакции задаётся фреймворковой интеграцией из шаблона маршрута. Если вы задаёте его вручную — подставляйте шаблон, а не готовый путь:

# плохо
sentry_sdk.set_transaction_name(f"GET /users/{user_id}/profile")
# хорошо
sentry_sdk.set_transaction_name("GET /users/:id/profile")

Если интеграция сама даёт «сырой» путь, обычно это значит, что маршрут не зарегистрирован во фреймворке (например, обработчик повешен на префикс). Зарегистрируйте маршрут — и имя станет шаблонным само.

OpenTelemetry. Имя спана должно быть низкокардинальным по спецификации: путь с идентификатором кладут в атрибут http.route или url.path, а не в имя. Проверьте, что инструментация не переопределяет имя вручную.

Окружение. Это production, staging, dev — короткий фиксированный список. Если туда попадает имя хоста, номер пода или версия — уберите их: для этого есть release и server_name.

Метрики. Имя инструмента — константа. Всё переменное идёт в атрибуты, а не в имя.

Пока чините

Данные не потеряны: суммарная нагрузка, длительности и число ошибок по проекту остаются верными — сгруппированные значения продолжают учитываться, просто под общим именем. Разбивка по хвосту вернётся, как только имена станут шаблонными: набор различённых значений начинается заново каждое окно.

Если у вас честно много значений

Бывает и так: большой монолит с тысячами настоящих маршрутов. Потолок поднимается переменной окружения:

GOTCHA_CARDINALITY_LIMIT=50000        # 0 — снять ограничение совсем
GOTCHA_CARDINALITY_WINDOW_SECONDS=3600

Поднимая, учитывайте цену: каждое различное значение — это отдельные строки предагрегатов на каждую пятиминутку, то есть место на диске и время слияний в ClickHouse.

У самой защиты тоже есть граница: она помнит значения в памяти процесса, и суммарно по всем проектам их не больше миллиона. При исчерпании бюджета сначала выбрасываются наборы проектов с истёкшим окном, а затем новые значения перестают запоминаться — защита продолжает работать, но не растёт. Сколько значений она помнит прямо сейчас, показывает gotcha_cardinality_tracked_values в /metrics. Число различных ИМЁН полей на проект тоже ограничено (200): имя поля приходит от отправителя ровно так же, как значение.

Что дальше