Escalabilidad en Prometheus: Alta Cardinalidad y Cómo Solucionarla
Tu instancia de Prometheus consume 20 GB de RAM. Las queries en Grafana tienen timeout. Las alertas llegan tarde —o directamente no llegan— justo cuando más las necesitas. Y el culpable, casi siempre, es el mismo: la cardinalidad.
La cardinalidad es el problema de escalabilidad más subestimado de Prometheus. No es un bug, ni una mala configuración del cluster, ni un exporter mal escrito. Es una consecuencia directa de cómo Prometheus modela los datos: cada combinación única de etiquetas genera una time series independiente que debe almacenarse, indexarse y consultarse. Añade un label con miles de valores distintos —un user_id, un pod dinámico, una IP— y tu instancia puede pasar de 200.000 series a 20 millones en cuestión de horas.
Este artículo cubre el problema en profundidad: qué es la cardinalidad, por qué degrada Prometheus, cómo detectarlo antes de que explote, y las opciones que tienes desde arreglos tácticos inmediatos hasta rediseños arquitecturales completos con Thanos, Grafana Mimir y VictoriaMetrics.
El modelo de datos de Prometheus y por qué la cardinalidad importa
Para entender el problema hay que entender el modelo. Prometheus almacena datos en time series: secuencias de pares (timestamp, valor) identificadas de forma única por un nombre de métrica y un conjunto de etiquetas (labels). Una métrica como:
http_requests_total{method="GET", status="200", endpoint="/api/v1/users"}
es una time series. Cambias cualquier label, tienes una nueva serie. Este diseño es extremadamente potente para hacer slicing y dicing de datos —es lo que hace que PromQL sea tan expresivo— pero tiene un coste directo: cada serie consume memoria, espacio en disco y ciclos de CPU para indexación.
La cardinalidad es el número total de combinaciones únicas de etiquetas que existen en todas tus métricas. La fórmula es simple: es el producto cartesiano de todos los valores posibles de cada label en una métrica dada.
Ejemplo concreto. Una métrica http_requests_total con:
method: 4 valores (GET, POST, PUT, DELETE)status_code: 6 valores (200, 201, 400, 401, 404, 500)endpoint: 50 rutas distintas
Genera 4 × 6 × 50 = 1.200 series activas. Manejable.
Ahora añades un label customer_id con 10.000 clientes activos. El resultado: 1.200 × 10.000 = 12.000.000 series desde una sola métrica. Eso equivale a entre 36 y 48 GB de RAM solo para el head block, la zona de datos recientes que Prometheus mantiene completamente en memoria.
Una instancia de Prometheus con 1 millón de series activas consume típicamente entre 4 y 6 GB de RAM solo para el head block. A 10 millones de series, estás mirando a 40–60 GB, y el rendimiento de compactación empieza a deteriorarse de forma no lineal.
Labels de alta cardinalidad: los habituales
Estos son los labels que hay que vigilar porque su cardinalidad puede crecer sin límite o con límite muy alto:
- User IDs y session tokens — tantos valores como usuarios activos tengas
- Request IDs y trace IDs — cardinalidad infinita por definición
- Pod names en entornos con autoscaling — cada nueva réplica añade nuevas series; cuando el pod muere, las series quedan como «stale» hasta que expiran
- Mensajes de error en texto libre — cada variación del mensaje genera una serie nueva
- Direcciones IP en entornos con alta rotación de clientes
- Versiones de build si cada deployment tiene su propio hash o timestamp
La cardinalidad no es solo un problema de espacio. También es un problema de series churn: la tasa a la que se crean y eliminan series. Un entorno con 500.000 series totales pero 100.000 nuevas series por minuto puede ser más problemático que uno con 2 millones de series estables.
Síntomas: cómo saber que tienes un problema de cardinalidad
El problema no aparece de golpe. Crece gradualmente y se manifiesta de formas que a veces se confunden con otros problemas.
Crecimiento del head block sin plateau
El indicador más directo es prometheus_tsdb_head_series. En un sistema sano, esta métrica sube y baja siguiendo los patrones de tus aplicaciones —más series durante el día laboral, menos por la noche. Cuando tienes un problema de cardinalidad, sube de forma monótona sin estabilizarse. Eventualmente termina en OOM (Out of Memory).
prometheus_tsdb_head_series
Monitoriza también la tasa de creación:
rate(prometheus_tsdb_head_series_created_total[5m])
Si esta tasa es sostenidamente alta —más de 50.000 series/minuto— tienes un problema estructural, independientemente del total actual.
Query timeouts y dashboards lentos
Las queries de PromQL que antes tardaban 200 ms ahora tardan 10 segundos. Los dashboards de Grafana empiezan a mostrar «query timed out». La evaluación de alertas se retrasa y los AIOps alertan con minutos de lag.
Este síntoma es especialmente peligroso porque ocurre precisamente cuando más lo necesitas: durante un incidente, con alta carga, cuando las métricas son más importantes.
Scrape failures y gaps en los datos
La presión de memoria activa el garbage collector de Go con más frecuencia. Los ciclos largos de GC hacen que Prometheus no pueda completar scrapes dentro del timeout. El resultado: targets marcados como down, huecos en las series temporales, y en el peor caso, alertas de «instancia caída» cuando la instancia está funcionando pero con degradación de rendimiento.
Compactación lenta
El TSDB de Prometheus compacta bloques periódicamente para reducir fragmentación y mejorar el rendimiento de queries. Con millones de series, esta compactación puede tomar 30 segundos o incluso varios minutos. Durante ese tiempo, el rendimiento de escritura se degrada y las queries sobre datos históricos son más lentas.
Monitoriza:
prometheus_tsdb_head_chunks_storage_size_bytes
rate(prometheus_tsdb_compactions_total[1h])
Diagnóstico: encontrar qué genera la cardinalidad
Antes de actuar, necesitas saber qué está causando el problema. Prometheus tiene herramientas integradas para esto.
El endpoint /api/v1/status/tsdb
curl http://localhost:9090/api/v1/status/tsdb | jq '.data.headStats'
Devuelve estadísticas del head block: número de series, chunks, samples. También incluye los 10 label names y los 10 nombres de métricas con mayor número de series. Es el punto de partida para cualquier investigación de cardinalidad.
# Top 10 métricas por número de series
curl -s http://localhost:9090/api/v1/status/tsdb | \
jq '.data.seriesCountByMetricName[:10]'
# Top 10 label names por número de pares de valores
curl -s http://localhost:9090/api/v1/status/tsdb | \
jq '.data.labelValueCountByLabelName[:10]'
PromQL para diagnóstico
Cardinalidad total estimada de una métrica específica:
count(http_requests_total)
Distribución de series por label:
count by (job) (http_requests_total)
Identificar labels con alta cardinalidad en una métrica:
count(count by (customer_id) (http_requests_total))
Si este último número es muy alto —decenas de miles o más— ese label es el culpable.
Soluciones tácticas: arreglos inmediatos
Estas soluciones pueden implementarse sin cambios arquitecturales y dan resultados rápidos.
1. Recording rules: pre-computar y agregar
Las recording rules son la herramienta más potente para reducir cardinalidad de forma estructural. Consiste en pre-calcular aggregations costosas y almacenar el resultado como una nueva métrica con menos labels.
La convención de nombres es level:metric:operations:
groups:
- name: http_request_aggregations
interval: 30s
rules:
- record: job:http_requests_total:rate5m
expr: |
sum by (job, namespace, method, status_code) (
rate(http_requests_total[5m])
)
- record: job:http_request_duration_seconds:p99_5m
expr: |
histogram_quantile(0.99,
sum by (job, namespace, le) (
rate(http_request_duration_seconds_bucket[5m])
)
)
En el primer ejemplo, eliminamos el label pod de la aggregation. Si tienes 500 pods, esto reduce la cardinalidad de esa métrica agregada en un factor de 500. Las queries que antes escaneaban millones de series ahora escanean cientos.
Principios clave para las recording rules:
- Úsalas para métricas que consultas frecuentemente en dashboards y alertas
- Agrega al nivel más grueso que satisfaga tu caso de uso
- El resultado debe ser una métrica útil por sí misma, no solo un artefacto de optimización
- Los dashboards de Grafana deben usar las recorded metrics, no las series originales
2. metric_relabel_configs: filtrar en el scrape
Esta configuración actúa después de que Prometheus hace el scrape pero antes de almacenar los datos. Es el lugar correcto para:
- Eliminar métricas que no usas
- Normalizar valores de alta cardinalidad
- Renombrar o reetiquetas
scrape_configs:
- job_name: application-pods
kubernetes_sd_configs:
- role: pod
metric_relabel_configs:
# Eliminar métricas de runtime de Go que no usamos
- source_labels: [__name__]
regex: 'go_gc_.*|go_memstats_.*|process_.*'
action: drop
# Normalizar endpoints con IDs dinámicos
- source_labels: [endpoint]
regex: '/api/v1/users/[0-9]+'
target_label: endpoint
replacement: '/api/v1/users/:id'
# Eliminar label de alta cardinalidad antes de almacenar
- regex: 'customer_id|session_id|request_id'
action: labeldrop
La normalización de endpoints es especialmente importante. Una ruta como /api/v1/users/12345 y /api/v1/users/67890 generan series distintas; normalizarlas a /api/v1/users/:id colapsa todas esas series en una sola.
Diferencia entre relabeling y metric_relabel_configs:
relabel_configs: actúa durante el descubrimiento de targets, antes del scrape. Afecta a qué targets se scraping.metric_relabel_configs: actúa después del scrape, sobre las métricas recibidas. Es donde filtras series.
3. Límites de cardinalidad por scrape
Prometheus permite imponer límites per-job para evitar que un exporter mal configurado pueda saturar la instancia:
scrape_configs:
- job_name: application-pods
sample_limit: 50000 # máximo de samples por scrape
label_limit: 64 # máximo de labels por sample
label_name_length_limit: 128
label_value_length_limit: 1024
Cuando un scrape supera sample_limit, Prometheus rechaza todo el scrape y marca el target como up=0 con la razón en prometheus_target_scrape_pool_exceeded_target_limit_total. Esto protege la instancia global a costa de perder datos de ese target temporalmente.
Configura alertas sobre estas métricas para detectar exporters que están creciendo hacia el límite antes de que lo superen:
# Targets que superaron el límite en las últimas 24h
increase(prometheus_target_scrapes_exceeded_sample_limit_total[24h]) > 0
4. Reducir la retención local
Si el problema es de espacio en disco más que de memoria, ajusta la retención:
# En los argumentos de arranque de Prometheus
--storage.tsdb.retention.time=15d
--storage.tsdb.retention.size=50GB
Esto no reduce la cardinalidad activa (head block), pero evita que el disco se llene con datos históricos si no los necesitas localmente.
Soluciones arquitecturales: cuando el tuning no es suficiente
Las soluciones tácticas tienen límites. Cuando tu instancia supera el millón de series o necesitas alta disponibilidad real, necesitas cambios arquitecturales.
Federation: la opción más simple
La federación jerárquica consiste en que una instancia «global» de Prometheus hace scrape del endpoint /federate de otras instancias «leaf». Las instancias leaf hacen el scrape real de las aplicaciones y pre-agregan sus métricas; la instancia global consume solo los agregados.
# En la instancia global
scrape_configs:
- job_name: federate
honor_labels: true
metrics_path: /federate
params:
match[]:
- '{job="kubernetes-pods"}'
- 'job:http_requests_total:rate5m' # Solo recorded metrics
static_configs:
- targets:
- 'prometheus-region-eu:9090'
- 'prometheus-region-us:9090'
Cuándo usar federation:
– Tienes múltiples instancias de Prometheus por región o cluster y quieres un dashboard global
– Solo necesitas métricas pre-agregadas en la vista global
– No necesitas hacer range queries sobre datos históricos federados
Limitaciones serias:
– No puedes hacer range queries contra datos federados (solo el snapshot actual)
– La instancia global es un single point of failure para dashboards globales
– No escala bien más allá de 4–5 instancias leaf
– No es una solución para alta disponibilidad real
Remote Write: hacer Prometheus stateless
Con remote_write, Prometheus envía todas las samples a un storage externo en tiempo real. La instancia local puede mantener una retención mínima (2–6 horas) solo para evaluar alertas y reglas.
remote_write:
- url: https://thanos-receive.monitoring.svc:19291/api/v1/receive
queue_config:
capacity: 10000
max_shards: 200
min_shards: 1
max_samples_per_send: 500
batch_send_deadline: 5s
min_backoff: 30ms
max_backoff: 5s
write_relabel_configs:
# Opcional: filtrar antes de enviar
- source_labels: [__name__]
regex: 'debug_.*'
action: drop
Tuning del queue_config:
El parámetro más importante es max_shards. Cada shard es una goroutine que envía datos al endpoint remoto. Más shards = mayor throughput pero también mayor presión de memoria. En sistemas de alta ingestión, valores entre 50 y 200 son habituales.
Monitoriza el lag de remote write:
# Lag en segundos entre lo que Prometheus tiene y lo que ha enviado
prometheus_remote_storage_highest_timestamp_in_seconds
- ignoring(remote_name, url) prometheus_remote_storage_queue_highest_sent_timestamp_seconds
Un lag sostenido de más de 60 segundos indica que el endpoint remoto no puede absorber la tasa de ingestión o que hay un problema de red.
Soluciones a largo plazo: Thanos, Mimir y VictoriaMetrics
Estas tres opciones son las más adoptadas en producción para escalar más allá de lo que una instancia de Prometheus puede manejar.
| Criterio | Thanos | Grafana Mimir | VictoriaMetrics |
|---|---|---|---|
| Arquitectura | Sidecar + object store | Microservicios distribuidos | Binario único o cluster |
| Storage | S3-compatible | S3-compatible | Formato TSDB propio |
| Complejidad operacional | Media | Alta | Baja |
| Escalabilidad de ingestión | Via Receive fan-out | Distributors + ingesters | Millones de samples/s por nodo |
| Multi-tenancy nativa | Limitada | Completa | Solo Enterprise |
| Compatibilidad PromQL | Completa | Completa | MetricsQL (superset) |
| Mejor caso de uso | Migración incremental desde Prometheus existente | Plataformas multi-tenant con gobernanza | Simplicidad y rendimiento |
| Licencia | Apache 2.0 | AGPLv3 | Community: Apache 2.0 / Enterprise: propietaria |
Thanos: la ruta incremental
Thanos es la opción menos disruptiva si ya tienes Prometheus en producción. No reemplaza Prometheus; lo extiende.
Componentes principales:
Thanos Sidecar corre junto a cada instancia de Prometheus, sube los bloques TSDB completados (cada 2 horas) a object storage (S3, GCS, Azure Blob), y expone los datos recientes via gRPC para queries federadas.
# thanos-sidecar en el mismo pod que Prometheus
containers:
- name: thanos-sidecar
image: quay.io/thanos/thanos:v0.35.0
args:
- sidecar
- --tsdb.path=/prometheus
- --prometheus.url=http://localhost:9090
- --objstore.config-file=/etc/thanos/s3.yaml
- --grpc-address=0.0.0.0:10901
Thanos Query federa queries contra múltiples sidecars y el Store Gateway (que lee de object storage):
# thanos-query puede consultar datos actuales (sidecar) e históricos (store)
args:
- query
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:10902
- --endpoint=thanos-sidecar-eu:10901
- --endpoint=thanos-sidecar-us:10901
- --endpoint=thanos-store:10901
- --query.replica-label=prometheus_replica # Para deduplicación
Thanos Receive es el componente para escenarios push. En lugar de que el sidecar suba bloques, las instancias de Prometheus envían datos via remote_write a Thanos Receive, que los almacena localmente y los sube a object storage. Permite alta disponibilidad activa-activa:
# Configuración de Thanos Receive con hashring para distribución
hashring.json:
- endpoints:
- thanos-receive-0.thanos-receive:10907
- thanos-receive-1.thanos-receive:10907
- thanos-receive-2.thanos-receive:10907
tenants: [] # Acepta todos los tenants
Cuándo elegir Thanos:
– Ya tienes Prometheus en producción y quieres añadir retención a largo plazo sin grandes cambios
– Necesitas queries multi-cluster con deduplicación
– Tu equipo tiene experiencia con Kubernetes y object storage
– Prefieres añadir componentes incrementalmente
Limitaciones:
– El número de componentes crece rápidamente (Sidecar, Query, Store, Compactor, Ruler, Receive, Query Frontend)
– Thanos Query no soporta todos los features de PromQL con el mismo rendimiento que Prometheus nativo
– La latencia de queries sobre datos históricos depende de la velocidad de lectura de object storage
Grafana Mimir: multi-tenancy enterprise
Mimir es la evolución de Cortex, rediseñada por Grafana Labs. Su propuesta de valor es la multi-tenancy nativa: cada tenant tiene sus métricas aisladas, con límites de cardinalidad por tenant, quotas de ingestión y namespaces separados.
La arquitectura es de microservicios completa:
- Distributor: recibe los writes de remote_write, los valida y los distribuye a los ingesters via consistent hashing
- Ingester: almacena datos recientes en memoria y los vuelca periódicamente a object storage
- Querier: ejecuta queries distribuyéndolas entre ingesters (datos recientes) y Store-Gateway (datos históricos)
- Compactor: compacta bloques en object storage para optimizar almacenamiento y rendimiento
- Ruler: evalúa recording rules y alertas
- Query Frontend: cachea queries y las divide en subqueries paralelas
# mimir.yaml - configuración simplificada
multitenancy_enabled: true
ingester:
ring:
replication_factor: 3
limits:
# Por tenant, sobrescribibles via API
ingestion_rate: 10000 # samples/segundo
ingestion_burst_size: 200000
max_global_series_per_user: 1500000
max_label_names_per_series: 30
max_label_value_length: 2048
blocks_storage:
backend: s3
s3:
bucket_name: mimir-blocks
endpoint: s3.eu-west-1.amazonaws.com
Los límites por tenant son la funcionalidad más relevante para problemas de cardinalidad: puedes aislar un tenant problemático sin que afecte al resto de la plataforma.
Cuándo elegir Mimir:
– Gestionas monitorización como servicio interno para múltiples equipos o clientes
– Necesitas garantías de aislamiento entre tenants
– Tienes capacidad operacional para gestionar una arquitectura de microservicios compleja
– Quieres integración nativa con el stack de Grafana (Grafana Cloud usa Mimir internamente)
Consideraciones:
– La complejidad operacional es la más alta de las tres opciones
– Requiere un orquestador (Kubernetes) para funcionar bien
– La licencia AGPLv3 tiene implicaciones si redistribuyes el software
VictoriaMetrics: rendimiento y simplicidad
VictoriaMetrics adopta una filosofía diferente: resolver los mismos problemas con menos componentes y mejor rendimiento. Su motor de almacenamiento propio ofrece compresión 5–10x mejor que el TSDB de Prometheus y rendimiento de queries superior en benchmarks.
Modo single-node (recomendado hasta ~10 millones de series activas):
./victoria-metrics \
-storageDataPath=/var/lib/victoria-metrics \
-retentionPeriod=12 \
-httpListenAddr=:8428 \
-maxConcurrentInserts=16 \
-insert.maxQueueDuration=1m
Es un único binario. No hay sidecars, no hay componentes de object storage, no hay distributed systems que operar. El scrape se configura igual que en Prometheus (soporta el formato de configuración de Prometheus) o puedes enviarle datos via remote_write desde Prometheus.
Modo cluster para escala horizontal:
┌─────────────┐
│ vminsert │ ← recibe remote_write
└──────┬──────┘
│ replica
┌───────────┼───────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ vmstorage│ │ vmstorage│ │ vmstorage│
└──────────┘ └──────────┘ └──────────┘
│ │ │
└───────────┼───────────┘
▼
┌─────────────┐
│ vmselect │ ← responde queries
└─────────────┘
MetricsQL: VictoriaMetrics implementa MetricsQL, un superset de PromQL con funciones adicionales como keep_last_value(), range_quantile() y mejoras en el handling de series con gaps. Es compatible hacia atrás con PromQL estándar.
# MetricsQL: interpolación de valores ausentes
keep_last_value(http_requests_total[5m])
# Percentil sobre un rango temporal (no disponible en PromQL estándar)
range_quantile(0.99, rate(http_request_duration_seconds_bucket[1h]))
vmagent es el agente ligero de VictoriaMetrics que reemplaza a Prometheus para el scrape: consume menos memoria, soporta sharding de scrape nativo para distribuir la carga entre múltiples instancias, y envía datos via remote_write a VictoriaMetrics o cualquier otro endpoint compatible.
Cuándo elegir VictoriaMetrics:
– Prioridad en simplicidad operacional y bajo coste de gestión
– Equipos pequeños o medianos sin dedicación a platform engineering
– Altos volúmenes de ingestión con recursos limitados (el ahorro en RAM y disco es real y significativo)
– Migración desde Prometheus sin querer adoptar Kubernetes adicional para el stack de métricas
Guía de decisión por escala
Menos de 1 millón de series activas
Una instancia única de Prometheus con tuning es suficiente. Prioriza:
- Identificar y eliminar labels de alta cardinalidad con
metric_relabel_configs - Implementar recording rules para las queries frecuentes de dashboards y alertas
- Configurar
sample_limitpor scrape job - Ajustar retención y
--storage.tsdb.max-block-durationsi es necesario
Hardware recomendado: 16 GB RAM, SSD para el storage del TSDB. No necesitas arquitectura distribuida.
De 1 a 5 millones de series activas
Aquí empieza a ser necesario sharding funcional o una solución de almacenamiento externo.
Opción A: Sharding funcional
Divide las responsabilidades de scrape entre múltiples instancias de Prometheus, cada una responsable de un subconjunto de targets (por namespace, por cluster, por tipo de workload). Cada instancia mantiene su propio TSDB. Las alertas se evalúan localmente; para dashboards globales, usa federation o Thanos Query.
Opción B: VictoriaMetrics single-node
Un nodo de VictoriaMetrics puede manejar varios millones de series activas con mucha menos RAM que Prometheus. Si el cuello de botella es la memoria de Prometheus, esta es la migración más rápida.
Más de 5 millones de series o requisitos globales
Aquí necesitas una arquitectura distribuida. La elección entre Thanos, Mimir y VictoriaMetrics depende de tus prioridades:
- Migración incremental con mínima disrupción → Thanos Sidecar
- Multi-tenancy nativa con aislamiento completo → Grafana Mimir
- Máximo rendimiento con mínima complejidad operacional → VictoriaMetrics Cluster
Monitorizar la salud de Prometheus
Antes de que el problema escale, necesitas alertas sobre las propias métricas de Prometheus.
Métricas clave a monitorizar
# Total de series activas en el head block
prometheus_tsdb_head_series
# Tasa de creación de nuevas series (churn)
rate(prometheus_tsdb_head_series_created_total[5m])
# Tamaño del head block en bytes
prometheus_tsdb_head_chunks_storage_size_bytes
# Lag de remote write (si aplica)
max_over_time(
(prometheus_remote_storage_highest_timestamp_in_seconds
- ignoring(remote_name, url)
prometheus_remote_storage_queue_highest_sent_timestamp_seconds)[5m:]
)
# Duración de la evaluación de alertas
prometheus_rule_group_last_duration_seconds
# Targets con scrape fallido
up == 0
Alertas recomendadas
groups:
- name: prometheus-health
rules:
- alert: PrometheusHighCardinality
expr: prometheus_tsdb_head_series > 2000000
for: 5m
labels:
severity: warning
annotations:
summary: "Prometheus con alta cardinalidad ({{ $value }} series)"
description: "La instancia {{ $labels.instance }} supera 2M de series activas"
- alert: PrometheusHighSeriesChurn
expr: rate(prometheus_tsdb_head_series_created_total[5m]) > 50000
for: 10m
labels:
severity: warning
annotations:
summary: "Alta tasa de creación de series en Prometheus"
- alert: PrometheusRemoteWriteLag
expr: |
(prometheus_remote_storage_highest_timestamp_in_seconds
- ignoring(remote_name, url)
prometheus_remote_storage_queue_highest_sent_timestamp_seconds) > 120
for: 5m
labels:
severity: critical
annotations:
summary: "Remote write con lag superior a 2 minutos"
Preguntas frecuentes
¿Cuántas series puede manejar Prometheus de forma segura?
No hay un límite universal. La regla práctica es calcular aproximadamente 3–4 GB de RAM por millón de series en el head block, más un 50% de margen para queries y otras operaciones. Con 16 GB de RAM, estás cómodo hasta 2–3 millones de series activas. Con 32 GB, hasta 5–6 millones.
El número relevante no es solo el total sino también el churn. Una instancia con 1 millón de series estables es mucho más cómoda que una con 500.000 series pero 80.000 nuevas series por minuto (como ocurre en entornos Kubernetes con muchos deployments frecuentes).
¿Cómo implemento alta disponibilidad de Prometheus sin soluciones externas?
La forma estándar es desplegar dos instancias de Prometheus idénticas (misma configuración, mismos scrape targets) y usar Alertmanager con el flag --cluster.* para deduplicar alertas entre ambas instancias. Para queries, necesitas algo que agregue las dos instancias: Thanos Query con deduplicación, VictoriaMetrics con vmselect, o simplemente apuntar Grafana a las dos instancias como datasources con «prefer one, fallback to other».
Esta configuración no elimina el split-brain para datos históricos —cada instancia tiene su propio TSDB— pero cubre el escenario de «una instancia cae y pierdes alertas».
¿Remote_write o Thanos Sidecar?
Depende del caso de uso:
- Remote_write es mejor si quieres que Prometheus sea stateless desde el principio, enviando datos a medida que llegan. Menor latencia hasta que los datos están disponibles en el store remoto. Mayor presión en el endpoint receptor.
- Thanos Sidecar sube bloques TSDB completos (cada 2 horas) a object storage. Prometheus sigue siendo el source of truth para datos recientes. Menos presión de escritura pero 2 horas de ventana donde los datos recientes solo están en la instancia local.
Para la mayoría de los casos prácticos, la combinación de remote_write + Thanos Receive es la más flexible.
¿Afecta el número de scrape targets a la cardinalidad?
Indirectamente. Más targets no significa más cardinalidad si cada target expone series con los mismos conjuntos de labels. La cardinalidad sube cuando los targets exponen series con labels de alta cardinalidad. El caso típico son pods de Kubernetes donde el label pod es único por réplica: 500 pods del mismo deployment generan 500 valores distintos del label pod, multiplicando la cardinalidad de cualquier métrica que lo incluya.
¿VictoriaMetrics es compatible con mis dashboards de Grafana existentes?
Sí, en su mayor parte. VictoriaMetrics soporta el API de Prometheus, por lo que Grafana puede usarlo como datasource directamente en modo «Prometheus». Las queries PromQL estándar funcionan sin cambios. Solo las features de MetricsQL no disponibles en PromQL estándar requieren adaptación, y esas no las tendrás en dashboards existentes.
Conclusión
La escalabilidad de Prometheus no es un problema que se resuelve una vez. Es una disciplina continua: monitorizar la cardinalidad, aplicar límites preventivos, pre-agregar con recording rules, y rediseñar la arquitectura cuando los números lo justifican.
El camino habitual es:
- Detectar el problema temprano con alertas sobre
prometheus_tsdb_head_seriesy churn rate - Eliminar cardinalidad innecesaria con
metric_relabel_configsylabeldrop - Pre-agregar métricas frecuentes con recording rules
- Escalar horizontalmente cuando una instancia ya no sea suficiente, eligiendo entre Thanos (migración incremental), Mimir (multi-tenancy) o VictoriaMetrics (simplicidad y rendimiento)
La mayoría de los problemas graves de cardinalidad en producción son evitables. Unos cuantos labels bien elegidos —y sobre todo, la disciplina de no añadir labels que no se van a filtrar en queries— marcan la diferencia entre una instancia de Prometheus que funciona durante años y una que se cae en el peor momento posible.