Почему Prometheus «раздувается» и чем опасна высокая кардинальность
Prometheus хранит данные как набор временных рядов (time series). Каждый ряд однозначно определяется именем метрики и полным набором лейблов. Появилось новое уникальное сочетание лейблов — появился новый ряд, и он начинает жить в TSDB, занимая память под индекс (head), дисковое место под чанки и ресурсы на компакцию.
High cardinality — это ситуация, когда число уникальных рядов становится очень большим (десятки/сотни тысяч и выше на один Prometheus, иногда миллионы). Обычно это проявляется ростом tsdb size, увеличением задержек запросов, удлинением компакций, а на пиках — упором в OOM или деградацией из‑за давления на память/GC.
«Плохая» кардинальность — не абсолютное число, а сочетание скорости появления новых серий, объёма данных и лимитов сервера. 300k серий на мощном узле могут быть нормой, а 300k на маленьком сервере — уже проблема. Почти всегда первопричина одна: лишние лейблы, плодящие уникальные комбинации.
Три источника проблем: метрики, лейблы, динамика
- Метрики с потенциально бесконечным набором значений: request_id, user_id, session, query, path с параметрами, stacktrace и т.д.
- Лейблы с высокой вариативностью: pod UID, container_id, instance с ephemeral портом, hostname на автоскейле, image digest.
- Частая смена целей: короткоживущие pods/jobs, которые постоянно создают новые
instance/pod/endpoint.
Кардинальность — это не только «сколько рядов сейчас», но и «как быстро появляются новые». Быстрое создание/удаление pod’ов может накручивать серии даже при умеренном текущем количестве целей.
Как быстро диагностировать high cardinality в Prometheus
Первый шаг — понять, что именно раздувает TSDB: конкретные метрики, конкретные лейблы или конкретные джобы. Чем быстрее вы найдёте «виновника», тем меньше будет соблазн лечить симптомы (retention/железо) вместо причины.
Базовые метрики Prometheus про себя
Эти метрики помогают понять, насколько TSDB «тяжёлая» прямо сейчас:
prometheus_tsdb_head_series— сколько серий сейчас в head (горячей части).prometheus_tsdb_head_chunks— число чанков в head.prometheus_tsdb_head_samples_appended_total— скорость записи (косвенно влияет на I/O и compaction).prometheus_tsdb_compactions_totalи длительности компакций — если времена растут, TSDB становится тяжелее обслуживать.
PromQL-запросы, чтобы найти виновников
Ниже — типовые запросы, которые помогают локализовать проблему. Если Prometheus уже «задыхается», начинайте с ограничения по job/namespace и избегайте запросов «по всему миру».
Топ метрик по числу серий:
topk(20, count by (__name__) ({__name__!=""}))
Топ джобов по числу серий (часто быстро показывает источник взрыва):
topk(20, count by (job) ({__name__!=""}))
Проверка конкретного лейбла на «взрыв» значений (пример для path):
topk(20, count by (path) ({job="api"}))
Сколько уникальных значений у лейбла (грубая оценка на примере pod):
count(count by (pod) ({job="kube-state-metrics"}))
Если такие запросы слишком дорогие, используйте TSDB status-страницу (в новых версиях Prometheus она хорошо помогает увидеть «тяжёлые» лейблы и метрики) или снимайте данные по частям: по одному job, по одному namespace, по одному экспортеру.
Для алертов, связанных с доступностью сервисов (а не с внутренней кардинальностью), часто полезно держать отдельный лёгкий мониторинг с blackbox‑проверками. В этом контексте пригодится материал про Prometheus Blackbox Exporter для HTTP/TCP/ICMP проверок.

Relabeling: где резать — в target relabeling или metric relabeling
В Prometheus есть два разных этапа relabeling, и их важно не путать:
relabel_configs— работает до скрейпа, на уровне целей (targets). Здесь вы фильтруете/переписываете цели, собираете адрес, правитеjob/instance, отсеиваете лишние endpoints.metric_relabel_configs— работает после скрейпа, на уровне каждой метрики. Именно здесь обычно лечат кардинальность: дропают метрики, выкидывают лейблы, нормализуют значения.
Если вы боретесь с high cardinality, чаще всего вам нужен
metric_relabel_configs: он не даёт метрикам попасть в TSDB.relabel_configsполезен, когда проблема в лишних таргетах или в неправильной разметкеjob/instance.
Приём №1: выкинуть «мусорные» метрики на входе
Классический вариант — отбрасывать метрики по имени или по лейблам, если вы точно понимаете, что они вам не нужны. Пример ниже показывает «жёсткий» режим: оставить только одну метрику (остальное отрезать).
scrape_configs:
- job_name: "api"
static_configs:
- targets: ["10.0.0.10:9100"]
metric_relabel_configs:
- source_labels: [__name__]
regex: "http_server_requests_seconds_bucket"
action: keep
Более мягко — точечно дропать самые тяжёлые метрики:
metric_relabel_configs:
- source_labels: [__name__]
regex: "(http_server_requests_seconds_bucket|http_request_duration_seconds_bucket)"
action: drop
Перед тем как дропать гистограммы, проверьте, не убьёте ли вы SLO/latency‑аналитику. Часто лучше уменьшить число bucket’ов на стороне приложения (это уменьшает число серий в разы), чем полностью выкинуть метрику.
Приём №2: labeldrop и labelkeep — когда метрика полезна, но лейблы слишком «жирные»
Если метрика нужна, но у неё есть лейблы с высокой вариативностью, обычно помогает выбросить конкретные метки или зафиксировать «белый список» разрешённых.
Пример: убираем лейбл request_id, который делает серию уникальной для каждого запроса:
metric_relabel_configs:
- regex: "request_id"
action: labeldrop
Вариант с политикой «разрешены только эти метки»:
metric_relabel_configs:
- regex: "(__name__|job|instance|method|status|le)"
action: labelkeep
labelkeep хорош тем, что фиксирует контракт: «в TSDB допускаем только такой набор измерений». Но будьте внимательны: можно случайно выкинуть важный разрез (например, namespace или pod) и затем получить слишком агрегированные графики.
Приём №3: нормализация лейбла через replace (аккуратно)
Иногда лейбл нужен, но его значения надо «укрупнить». Типичный пример — path, где есть идентификаторы: /users/12345, /users/67890. В идеале это делается в приложении (отдавать template route), но частично можно помочь relabel’ом.
metric_relabel_configs:
- source_labels: [path]
regex: "(.*)/[0-9]+(.*)"
target_label: path
replacement: "$1/:id$2"
action: replace
Учтите два ограничения: regex‑замены на потоке метрик добавляют CPU, а «универсальная» регулярка легко ломает полезную детализацию. Делайте это только при понятной структуре путей и измеримом эффекте.
Retention и tsdb size: как связаны кардинальность, частота скрейпа и срок хранения
Размер TSDB определяется в основном тремя параметрами: сколько серий, как часто вы скрейпите и сколько дней храните. Упрощённо:
- Больше серий → больше индекса и чанков.
- Меньше
scrape_interval(например, 5s вместо 30s) → больше сэмплов → выше I/O и больше данных на диске. - Больше retention → больше блоков на диске и дольше компакции при тех же объёмах.
Если лечить только retention, но не убирать high cardinality, вы лишь отложите проблему. Prometheus всё равно будет тяжёлым в runtime: head/индекс/compaction будут страдать даже при коротком хранении.
Практичный порядок действий обычно такой:
- Снизить кардинальность: drop/labeldrop/уменьшить buckets, убрать динамические лейблы.
- Подобрать
scrape_intervalи retention под задачи (разные интервалы для разных job — это нормально). - И только затем думать про downsampling и/или вынос истории через
remote_write.
Если Prometheus стоит на небольшом сервере, кардинальность особенно быстро упирается в RAM и IOPS. В таких случаях имеет смысл либо правильно «подрезать» метрики, либо переносить сборщик на более ресурсный узел, например на VDS с предсказуемыми лимитами по памяти и диску.
Remote write: как вынос метрик помогает (и почему он не лечит кардинальность сам по себе)
remote_write часто воспринимают как «спасение от раздувания TSDB». Частично это так: можно держать на локальном Prometheus небольшой retention (например, 6–24 часа) для оперативных алертов, а долгую историю отправлять в удалённое хранилище.
Но нюанс важный: remote_write не уменьшает кардинальность автоматически. Если вы продолжаете ingest’ить миллионы рядов локально, Prometheus всё равно платит цену за head/индекс, даже если хранит данные недолго. Поэтому remote_write — это про архитектуру и long‑term storage, а не про «волшебное сжатие».
Практичный паттерн: короткий локальный retention + внешний long-term storage
Схема обычно такая:
- Prometheus: ingestion + rules/alerts + короткая история.
- Удалённое хранилище: длительная история, отчёты, запросы по месяцам.
Что обязательно контролировать в эксплуатации:
- Очередь remote write: если удалённое хранилище тормозит, растёт буфер и потребление памяти.
- Фильтрация до отправки: иногда разумно отправлять только «сигнальные» метрики (SLO, инфраструктура), а «шум» отбрасывать ещё до TSDB.
- Разделение потоков: разные
remote_writeдля разных наборов метрик упрощают управление стоимостью и отказами.

Downsampling: когда нужен и где его делать правильно
Downsampling — это уменьшение детализации данных для долгого хранения: например, точные данные за последние 2 дня, а дальше — 1 точку в минуту/5 минут/час. Это экономит место и ускоряет запросы по большим диапазонам.
Ключевой момент: классический Prometheus не делает downsampling «из коробки» для собственного TSDB. Если вы хотите downsampling, обычно это делается в long‑term storage или через запись агрегатов отдельными рядами.
Практика №1: записывать агрегаты как отдельные метрики (recording rules)
Самый контролируемый способ — заранее создавать «сжатые» ряды через recording rules и хранить их дольше, чем «сырьё». Пример: вместо хранения per‑instance метрик годами — хранить агрегаты по сервисам.
groups:
- name: downsample_rules
interval: 1m
rules:
- record: job:http_requests_total:rate1m
expr: sum by (job, method, status) (rate(http_requests_total[1m]))
Плюсы: предсказуемая кардинальность и быстрые дашборды на больших периодах. Минусы: нужно заранее решить, что агрегировать, и следить за изменениями лейблов, чтобы не потерять нужные разрезы.
Практика №2: downsampling на стороне remote-хранилища
Если long‑term storage умеет хранить несколько уровней детализации или эффективно агрегировать, логичнее оставить Prometheus «оперативным сборщиком», а downsampling вынести наружу. Это снижает количество логики в rules и уменьшает риск «не туда агрегировать», но зависит от выбранного стека и требований к запросам.
Чек-лист: как снижать кардинальность без потери наблюдаемости
Ниже — последовательность, которая обычно даёт максимум эффекта при минимуме сюрпризов.
1) Зафиксируйте базовую точку и цели
- Снимите текущие значения
prometheus_tsdb_head_seriesи размер диска под TSDB. - Составьте список критичных дашбордов/алертов: latency, error rate, saturation (CPU/RAM/disk), внешняя доступность.
2) Найдите топ-метрики по числу серий
- Если «виноваты» гистограммы — начните с уменьшения количества bucket’ов на стороне приложения.
- Если «виноваты» лейблы типа
path/query/user— почти всегда нужен drop/labeldrop.
3) Внедрите metric_relabel_configs как «отсечку»
- Точечные
dropдля самых дорогих метрик. labelkeepкак политика для разрешённых лейблов (контракт на измерения).- Минимизируйте regex‑магию; если можно — исправьте экспортер/приложение.
4) Настройте retention осознанно
- Оперативные разборы инцидентов обычно укладываются в часы/дни, не месяцы.
- Долгая история нужна для трендов — там чаще выигрывают агрегаты и downsampling.
5) Подключайте remote_write как следующий шаг
- Выносите долгую историю, но не оставляйте high cardinality «как есть».
- Следите за стабильностью канала и метриками очередей remote write.
Типовые анти-паттерны (и чем заменить)
- Лейбл с уникальным значением на каждый запрос → оставьте в метриках статус/метод/handler, а детализацию унесите в логи или трассировку.
- Хранить всё «на всякий случай» → «сырьё» храните кратко, тренды — агрегатами.
- Пытаться лечить только retention → сначала снижайте кардинальность, иначе head и индекс всё равно будут тяжёлыми.
- Один и тот же маленький
scrape_intervalдля всего → сделайте разные интервалы по джобам: критичное 10–15s, остальное 30–60s.
Мини-итог
Борьба с high cardinality в Prometheus — это дисциплина по лейблам и метрикам. Правильно применённые metric_relabel_configs обычно дают самый быстрый эффект: меньше серий → меньше TSDB → проще удерживать нужный retention и стабильную скорость запросов. remote_write и downsampling — следующий уровень: про архитектуру хранения и стоимость долгой истории, но они не заменяют гигиену кардинальности.


