10. Storage backends

Por: Artiko
jaegerstoragecassandraelasticsearchclickhousebadger

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

BackendPersistenciaEscalaBúsqueda ricaCostoOps
memoryNoSingle nodeBásica00
badgerSí (disco local)Single nodeBásicaBajoMínima
cassandraSí (cluster)HorizontalLimitadaAltoAlta
elasticsearch / opensearchSí (cluster)HorizontalRicaAltoMedia-alta
clickhouse (v2)Sí (cluster o single)HorizontalRicaMedioMedia

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, tags indexados, 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ónMemoryBadgerCassandraES/OSClickHouse
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.