АрхитектураНадёжность и производительностьИнженер по надёжности платформы

Наблюдаемость сервиса резко дорожает после добавления идентификатора пользователя в метки метрик. Какой мех...

Наблюдаемость сервиса резко дорожает после добавления идентификатора пользователя в метки метрик. Какой механизм вызывает эту проблему?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

Метрики развивались как способ хранить числовые измерения в агрегированном виде: количество запросов, ошибки, задержки, загрузку ресурсов. Метки сделали такие измерения многомерными, позволив отдельно анализировать, например, регион, метод или код ответа.

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

Постановка проблемы

Пусть метрика содержит метки метода, кода ответа и пользователя. Число рядов приблизительно определяется произведением количества уникальных значений всех меток. Даже при небольшом числе методов и кодов ответа добавление миллионов пользователей создаёт огромное количество комбинаций.

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

Подробное решение

Кардинальность — это число уникальных значений измерения или комбинаций значений меток. В системе временных рядов уникальная комбинация имени метрики и меток обычно идентифицирует отдельный ряд, поэтому значение user_id=один пользователь не является просто полем внутри общей записи.

Безопасными обычно считаются метки с ограниченным заранее известным набором значений: тип операции, регион, класс ответа или состояние компонента. Опасны идентификаторы пользователей, заказов, запросов, сессий, URL с переменными частями и текстовые сообщения об ошибках.

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

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

Главный компромисс состоит в потере детализации в метриках. Это обычно приемлемо: метрики должны быстро отвечать на агрегированные вопросы о состоянии системы, а расследование конкретного пользователя выполняется по логам или трассировкам.

Ситуация из практики

После добавления идентификатора клиента в метрику ошибок объём временных рядов резко вырос. Команда рассмотрела три варианта: оставить метку и увеличить хранилище, хешировать идентификатор или убрать его из метрики.

Увеличение хранилища временно отодвинуло бы проблему, но не устранило рост кардинальности. Хеширование сохранило бы почти то же число уникальных значений, поэтому дало бы слабый эффект. Удаление метки уменьшило бы детализацию метрики, зато восстановило бы предсказуемую нагрузку.

Выбрали третий вариант: в метрике оставили метод и класс ошибки, а идентификатор клиента начали записывать в структурированный лог и связывать с трассировкой запроса. Это позволило видеть агрегированную долю ошибок через метрики, а конкретные случаи находить по корреляционному идентификатору без создания отдельного временного ряда для каждого клиента.

Что кандидаты часто упускают

  1. Почему высокая кардинальность опаснее просто большого числа измерений?

    Число измерений само по себе не определяет проблему. Если каждая метка имеет несколько фиксированных значений, число комбинаций остаётся ограниченным и предсказуемым. Опасна именно комбинация большого числа значений с их произведением по нескольким меткам: добавление ещё одной высококардинальной метки может многократно увеличить число рядов.

  2. Почему удаление старых временных рядов не всегда сразу снижает нагрузку?

    Система может продолжать получать новые комбинации значений, поэтому поток создания рядов сохраняется. Кроме того, хранилище и индекс могут освобождать память и место постепенно, согласно политике хранения и внутренним процедурам уплотнения. Если источник продолжает отправлять высококардинальные метрики, удаление уже созданных данных не устраняет первопричину.

  3. Можно ли использовать идентификатор пользователя в метрике для поиска проблем конкретного клиента?

    Обычно это неправильный слой для такой задачи. Метрика должна отвечать на агрегированные вопросы: насколько часто возникает ошибка, где она происходит и как меняется её доля. Для конкретного клиента лучше сохранить идентификатор в логе или трассировке, применить контролируемый поиск и учесть требования к приватности, сроку хранения и доступу к персональным данным.