10. Storage backends
10. Storage backends
Jaeger no almacena por sí mismo: delega en un backend externo. La decisión de qué backend usar es estratégica: condiciona costos, ops, performance de búsqueda y retención.
Comparación rápida
| Backend | Persistencia | Escala | Búsqueda rica | Costo | Ops |
|---|---|---|---|---|---|
| memory | No | Single node | Básica | 0 | 0 |
| badger | Sí (disco local) | Single node | Básica | Bajo | Mínima |
| cassandra | Sí (cluster) | Horizontal | Limitada | Alto | Alta |
| elasticsearch / opensearch | Sí (cluster) | Horizontal | Rica | Alto | Media-alta |
| clickhouse (v2) | Sí (cluster o single) | Horizontal | Rica | Medio | Media |
Memory
Default del all-in-one. Ya lo vimos en el capítulo 3.
- Pros: cero setup, perfecto para dev / demo / CI.
- Contras: se pierde al reiniciar. Cap por
MEMORY_MAX_TRACES. No escala más allá de un proceso.
docker run -e SPAN_STORAGE_TYPE=memory \
-e MEMORY_MAX_TRACES=100000 \
jaegertracing/all-in-one:1.62
Badger (LSM tree embebido)
Embebido en el binario, persiste a disco local. Ideal para entornos chicos donde no querés operar Cassandra/ES.
docker run -d --name jaeger \
-e SPAN_STORAGE_TYPE=badger \
-e BADGER_EPHEMERAL=false \
-e BADGER_DIRECTORY_VALUE=/badger/data \
-e BADGER_DIRECTORY_KEY=/badger/key \
-v jaeger-badger:/badger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 -p 4317:4317 \
jaegertracing/all-in-one:1.62
Cuándo usar Badger
- Equipos chicos con un solo Jaeger.
- Entornos de staging persistentes.
- Edge / sucursal / IoT con tracing local.
Cuándo NO usar Badger
- Multi-instance (no soporta cluster).
- Necesidad de retención larga (gigas a discreción del disco).
- Búsquedas complejas por atributos de alta cardinalidad.
Retención
-e BADGER_SPAN_STORE_TTL=72h0m0s
72 horas por default. Después de ese tiempo, las trazas se borran.
Cassandra
El backend “histórico” de Jaeger. Diseñado para volumen alto.
Setup mínimo con docker-compose
services:
cassandra:
image: cassandra:4.1
environment:
MAX_HEAP_SIZE: "1G"
HEAP_NEWSIZE: "256M"
volumes:
- cassandra-data:/var/lib/cassandra
jaeger-init:
image: jaegertracing/jaeger-cassandra-schema:1.62
environment:
MODE: "test"
DATACENTER: "dc1"
KEYSPACE: "jaeger_v1_dc1"
CASSANDRA_PROTOCOL_VERSION: "4"
depends_on: [cassandra]
jaeger-collector:
image: jaegertracing/jaeger-collector:1.62
environment:
SPAN_STORAGE_TYPE: cassandra
CASSANDRA_SERVERS: "cassandra"
CASSANDRA_KEYSPACE: "jaeger_v1_dc1"
ports:
- "4317:4317"
- "4318:4318"
depends_on: [jaeger-init]
jaeger-query:
image: jaegertracing/jaeger-query:1.62
environment:
SPAN_STORAGE_TYPE: cassandra
CASSANDRA_SERVERS: "cassandra"
CASSANDRA_KEYSPACE: "jaeger_v1_dc1"
ports:
- "16686:16686"
depends_on: [cassandra]
volumes:
cassandra-data:
Retención (TTL)
Cassandra implementa retención con TTL nativo de la base, configurado en el schema:
docker run --rm jaegertracing/jaeger-cassandra-schema \
MODE=prod \
DATACENTER=dc1 \
KEYSPACE=jaeger_v1_dc1 \
TRACE_TTL=172800 # 2 días en segundos
Pros
- Aguanta volúmenes muy altos (decenas de miles de spans/segundo).
- Cluster multi-nodo, replicación, sin SPOF.
- Eficiente en escrituras (LSM internamente).
Contras
- Búsqueda limitada: solo por
service,operation,tagsindexados,duration. No búsqueda full-text en atributos arbitrarios. - Operación compleja: tuning de compactación, repair, monitoring.
- El dependency graph se calcula con un job Spark batch — no en vivo.
Elasticsearch / OpenSearch
El segundo backend más usado, especialmente cuando ya tenés ES corriendo para logs.
Setup con docker-compose
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
environment:
discovery.type: "single-node"
xpack.security.enabled: "false"
ES_JAVA_OPTS: "-Xms2g -Xmx2g"
ports:
- "9200:9200"
jaeger-collector:
image: jaegertracing/jaeger-collector:1.62
environment:
SPAN_STORAGE_TYPE: elasticsearch
ES_SERVER_URLS: "http://elasticsearch:9200"
ES_NUM_SHARDS: "5"
ES_NUM_REPLICAS: "1"
ports:
- "4317:4317"
depends_on: [elasticsearch]
jaeger-query:
image: jaegertracing/jaeger-query:1.62
environment:
SPAN_STORAGE_TYPE: elasticsearch
ES_SERVER_URLS: "http://elasticsearch:9200"
ports:
- "16686:16686"
depends_on: [elasticsearch]
Pros
- Búsqueda rica: full-text en atributos, filtros complejos, agregaciones.
- Dependency graph en vivo sin job batch.
- Si ya operás ES para logs, infra y skills compartidas.
- Soporta OpenSearch (fork open source de Elasticsearch).
Contras
- Memoria intensivo. ES quiere RAM.
- Storage size: índices ES son más pesados que Cassandra para los mismos datos.
- Operación de cluster ES no es trivial.
Retención (rollover de índices)
ES no tiene TTL por documento. La retención se hace por rollover de índices:
- Jaeger crea índices por día:
jaeger-span-2026-05-10,jaeger-span-2026-05-11, … - Un script o ILM (Index Lifecycle Management) borra índices viejos.
# Borrado simple con curl (script en cron)
INDEX_DATE=$(date -d '7 days ago' +%Y-%m-%d)
curl -X DELETE "http://elasticsearch:9200/jaeger-span-${INDEX_DATE}"
curl -X DELETE "http://elasticsearch:9200/jaeger-service-${INDEX_DATE}"
O configurás ILM (recomendado en producción).
ClickHouse (Jaeger v2)
Soporte oficial en Jaeger v2 (capítulo 11). Excelente ratio costo/performance.
Pros
- Compresión brutal: trazas ocupan 5-10x menos que en ES.
- Queries analíticas extremadamente rápidas.
- Single-node aguanta volúmenes que en ES requerirían cluster.
Contras
- ClickHouse es columnar, optimizado para writes en bulk: low-latency lookups por trace_id puntual son menos veloces que en ES/Cassandra.
- Operación de ClickHouse en cluster es compleja (replicación, ZooKeeper/Keeper).
ClickHouse ha pasado a ser una recomendación fuerte en deployments nuevos a partir de 2025.
Comparación de queries soportadas
| Operación | Memory | Badger | Cassandra | ES/OS | ClickHouse |
|---|---|---|---|---|---|
| Lookup por trace_id | ✅ | ✅ | ✅ | ✅ | ✅ |
| Filtro por service | ✅ | ✅ | ✅ | ✅ | ✅ |
| Filtro por operation | ✅ | ✅ | ✅ | ✅ | ✅ |
| Filtro por tag (k=v) | ✅ | ✅ | parcial | ✅ | ✅ |
| Filtro por tag wildcard | ❌ | ❌ | ❌ | ✅ | ✅ |
| Filtro por duration range | ✅ | ✅ | ✅ | ✅ | ✅ |
| Búsqueda full-text en logs | ❌ | ❌ | ❌ | ✅ | ✅ |
| Dependency graph en vivo | ✅ | ✅ | ❌ (batch) | ✅ | ✅ |
Capacity planning rápido
Reglas del pulgar para dimensionar:
- Tamaño promedio de span: ~500 bytes - 2 KB en disco (depende de atributos).
- Spans por trace promedio: 5-50 según complejidad.
- Compresión: 3-5x en Cassandra, 5-10x en ClickHouse.
Ejemplo:
- 10.000 trazas/seg × 20 spans/traza = 200k spans/seg
- Si guardás 1% (sampling 1%): 2k spans/seg
- × 1 KB promedio = 2 MB/seg = ~170 GB/día crudo
- Con compresión 3x: ~57 GB/día efectivo
Multiplicá por días de retención y agregá margen para metadata, índices.
Decisión: árbol de bolsillo
flowchart TD
Q1{Volumen?} -->|"Bajo / dev"| M[memory o badger]
Q1 -->|"Medio"| Q2{Stack ya tiene ES?}
Q1 -->|"Alto"| Q3{Búsqueda rica?}
Q2 -->|"Sí"| ES[elasticsearch]
Q2 -->|"No"| Q4{Greenfield?}
Q4 -->|"Sí"| CH[clickhouse v2]
Q4 -->|"No"| C[cassandra]
Q3 -->|"Sí"| ES
Q3 -->|"No, costo importa"| CH
¿Qué viene?
Tenés tracing operativo con storage real. Hora de hablar del futuro: en el próximo capítulo entramos en Jaeger v2, la versión basada en OpenTelemetry Collector que reemplaza la arquitectura clásica.