Elasticsearch MigrationClickHouse Workshops

03 Ejecutar la migración

Aprovisiona ClickHouse Cloud, escribe en ambos destinos mediante OpenTelemetry, valida la paridad, explora ClickStack y realiza la transición.

Ejecuta este módulo desde el directorio de artefactos del taller:

cd "$(git rev-parse --show-toplevel)/workshops/elasticsearch_migration_lab/part3"

Objetivo: Ejecuta el plan elaborado en la Parte 2. Despliega un OTel Collector, crea tablas de ClickHouse optimizadas, configura el enriquecimiento con diccionarios, valida la paridad con Elasticsearch y realiza la transición.

Tiempo estimado: 120–180 minutos

Requisitos previos: Partes 1 y 2 completadas; el entorno Docker de la Parte 1 está en ejecución (Elasticsearch, Kibana, generadores de logs y APM Server). Tienes las credenciales de ClickHouse Cloud.

Archivos:

part3/
├── exercises/
│   ├── setup-checklist.md           ← Fill in as you work through each step
│   └── sql-exercises.md             ← 6 SQL exercises (attempt before checking solutions)
├── clickhouse/
│   ├── schema.sql                   ← All DDL (run first)
│   ├── dictionaries.sql             ← GeoIP dictionary DDL
│   ├── geoip-sample-data.csv        ← ~400 CIDR rows (no MaxMind account needed)
│   ├── alert-tables.sql             ← Alert pre-computation + summary MVs
│   └── validation-queries.sql       ← Spot-checks and parity queries
├── configs/
│   ├── otel-collector-config.parallel.yaml ← File-based log collector — parallel run (CH + ES dual-write)
│   ├── otel-collector-config.cutover.yaml  ← File-based log collector — cutover (CH only)
│   ├── otelcol-demo-config.parallel.yml    ← OTel Demo collector — parallel run (APM + CH)
│   └── otelcol-demo-config.cutover.yml     ← OTel Demo collector — cutover (CH only)
├── docker/
│   ├── docker-compose.otel-demo.parallel.yml  ← Compose override for parallel-run swap
│   └── docker-compose.otel-demo.cutover.yml   ← Compose override for cutover swap
├── diagrams/
│   ├── step3-architecture.mmd       ← Mermaid source — post-Step-3 architecture
│   ├── step3-architecture.png       ← Rendered PNG embedded in Step 3
│   └── render.sh                    ← Re-render *.mmd → *.png via Docker (mermaid-cli)
├── images/
│   └── *.png                        ← Screenshots referenced by hyperdx-guide.md
└── scripts/
    ├── swap-otelcol-demo-config.sh  ← Swap otelcol-demo config (parallel|cutover)
    ├── validate_migration.sh        ← Automated parity check
    └── validate_enrichment.sh       ← Enrichment column verification

Paso 1: aprovisionar ClickHouse Cloud

  1. Regístrate en clickhouse.cloud (la prueba gratuita y la capa Basic bastan para el laboratorio)
  2. Crea un servicio nuevo; la capa Basic es suficiente
  3. Anota host y password (el usuario es default y el puerto TLS nativo es 9440)
  4. Prueba la conexión:
clickhouse client \
    --host <your-host>.clickhouse.cloud \
    --port 9440 \
    --user default \
    --password <your-password> \
    --secure \
    --query "SELECT version()"

Arquitectura de ClickHouse Cloud: A diferencia de Elasticsearch, ClickHouse Cloud almacena todos los datos en almacenamiento de objetos (S3/GCS) con caché local automática. No existen capas de nodos hot/warm/cold: el motor obtiene y guarda en caché los datos de forma transparente. Toda la maquinaria ILM hot→warm→cold de ES carece de equivalente. Solo necesitas eliminación por TTL, que configurarás en el Paso 2.

Define las variables de entorno para el resto del laboratorio:

# Copy and fill in once, then source before every session
cp ../common/env.sh.example ../common/env.sh
# edit ../common/env.sh with your CH_HOST and CH_PASSWORD (from Cloud console → Connect → Native protocol)
source ../common/env.sh

Paso 2: crear las tablas y los diccionarios de destino

Base de datos: Todos los objetos de la Parte 3 viven en una base dedicada otel, creada automáticamente por dictionaries.sql y schema.sql mediante CREATE DATABASE IF NOT EXISTS otel. Así quedan aislados. Todas las configuraciones del collector, scripts y fuentes de HyperDX ya apuntan a otel.

Orden de ejecución: Los diccionarios GeoIP (Paso 2a) deben crearse antes que las tablas (Paso 2b), porque otel_logs_v2 tiene columnas MATERIALIZED que hacen referencia a otel.geoip_country y otel.geoip_city. ClickHouse valida estas referencias al ejecutar CREATE TABLE.

2a. Cargar datos GeoIP y crear diccionarios

# 1. Create the otel database, geoip_data source table, and empty dictionaries
#    (the table must exist before you can INSERT into it)
clickhouse client \
    --host ${CH_HOST} --port 9440 \
    --user default --password ${CH_PASSWORD} --secure \
    < clickhouse/dictionaries.sql

# 2. Load sample data into the source table (note: --database otel)
clickhouse client \
    --host ${CH_HOST} --port 9440 \
    --user default --password ${CH_PASSWORD} --secure \
    --database otel \
    --query "INSERT INTO geoip_data FORMAT CSVWithNames" \
    < clickhouse/geoip-sample-data.csv

# 3. Reload dictionaries — they were created with an empty table; force reload now
clickhouse client \
    --host ${CH_HOST} --port 9440 \
    --user default --password ${CH_PASSWORD} --secure \
    --query "SYSTEM RELOAD DICTIONARY otel.geoip_country"
clickhouse client \
    --host ${CH_HOST} --port 9440 \
    --user default --password ${CH_PASSWORD} --secure \
    --query "SYSTEM RELOAD DICTIONARY otel.geoip_city"

# 4. Verify
clickhouse client \
    --host ${CH_HOST} --port 9440 \
    --user default --password ${CH_PASSWORD} --secure \
    --query "SELECT dictGet('otel.geoip_country', 'country', toIPv4('8.8.8.8'))"
# Expected: United States

Punto didáctico: En Elasticsearch, el procesador geoip es una caja negra integrada. En ClickHouse utilizas un diccionario basado en los mismos datos MaxMind y controlas el origen, el intervalo de actualización y la búsqueda. El diseño IP_TRIE está optimizado para rangos CIDR: dictGet() realiza una coincidencia por el prefijo más largo en microsegundos.

2b. Crear tablas y la vista materializada

clickhouse client \
    --host ${CH_HOST} --port 9440 \
    --user default --password ${CH_PASSWORD} --secure \
    < clickhouse/schema.sql

Se crean nueve objetos:

ObjetoTipoFinalidad
otel_logsMotor NullDestino de ingesta del OTel Collector; no almacena nada
otel_logs_v2MergeTreeAlmacena realmente los datos, con columnas enriquecidas
otel_logs_mvVista materializadaDirige otel_logs → otel_logs_v2 con transformaciones
otel_tracesMergeTreeSpans de trazas OTel
otel_metrics_gaugeMergeTreeMétricas gauge de OTel (sustituye APM Server como backend de métricas)
otel_metrics_sumMergeTreeMétricas de suma/contador de OTel
otel_metrics_histogramMergeTreeMétricas de histograma de OTel
otel_metrics_exponentialhistogramMergeTreeMétricas de histograma exponencial de OTel
otel_metrics_summaryMergeTreeMétricas de resumen de OTel

¿Por qué el patrón Null → MV → destino?

El motor Null acepta inserciones y descarta los datos inmediatamente. La MV adjunta se dispara en cada inserción y escribe filas transformadas en otel_logs_v2. Así no se almacenan dos veces: el collector escribe el esquema OTel bruto en otel_logs y solo las filas enriquecidas y optimizadas llegan a otel_logs_v2.

Las columnas materializadas sustituyen todos los procesadores de ingesta de ES:

Procesador ESEquivalente de ClickHouse
geoipGeoCountry, GeoCity MATERIALIZED mediante dictGetOrDefault('otel.geoip_country', ...)
user_agentBrowserFamily, OSFamily, IsBot MATERIALIZED mediante regexpExtract / position
script (derivación de severidad)DerivedSeverity MATERIALIZED mediante multiIf(StatusCode >= 500, 'critical', ...)
grok/dissect (extracción de campos)RequestType, RequestPath, RequestPage, HostName MATERIALIZED desde LogAttributes['key']
default-enrichment (event.ingested)IngestTime DEFAULT now()

2c. Crear tablas de precálculo de alertas y resumen

clickhouse client \
    --host ${CH_HOST} --port 9440 \
    --user default --password ${CH_PASSWORD} --secure \
    < clickhouse/alert-tables.sql

Esto crea:

  • alert_error_rate + alert_error_rate_mv: precalculan la tasa de 5xx por minuto durante la inserción
  • logs_summary_1min + logs_summary_1min_mv: rollup AggregatingMergeTree (sustituye las transformaciones de ES)

Paso 3: desplegar y configurar OTel Collector

Arquitectura de destino

Al finalizar, habrás pasado de la referencia de la Parte 1 (Filebeat → ES, otelcol-demo → solo APM) a una ejecución paralela en la que dos OTel Collectors distribuyen cada señal al Elasticsearch existente y a ClickHouse Cloud:

Arquitectura después del Paso 3

Cambios de este paso:

  • 3a: detener Filebeat (en rojo, abajo a la izquierda).
  • 3b–3c: iniciar otelcol-lab (collector basado en archivos): sigue los mismos logs y escribe tanto en ES (logs-{web_access,application,infrastructure}-lab) como en ClickHouse (otel.otel_logs → MV → otel.otel_logs_v2).
  • 3d: cambiar otelcol-demo a la configuración paralela para que el tráfico OTLP de los 16 servicios también se distribuya a APM Server y ClickHouse.

Fuente del diagrama: diagrams/step3-architecture.mmd. Para volver a renderizarlo, ejecuta bash diagrams/render.sh (usa minlag/mermaid-cli mediante Docker; no requiere Node/npm).

3a. Detener Filebeat

El OTel Collector seguirá los mismos archivos y escribirá en los mismos flujos ES (logs-web_access-lab, logs-application-lab, logs-infrastructure-lab), además de ClickHouse. Si ambos se ejecutan, cada línea se indexa dos veces en ES.

Detén Filebeat para que el collector sea el único productor:

docker compose -f ../part1/docker/docker-compose.source.yml stop filebeat
docker compose -f ../part1/docker/docker-compose.source.yml ps filebeat
# Should show: filebeat ... exited

Elasticsearch, Kibana, los generadores y APM Server siguen activos. Solo se detiene Filebeat. OTel Collector toma el control de ambos backends y conserva comparaciones significativas. En la transición (Paso 10a) se eliminan los exportadores ES.

3b. Ejecutar OTel Collector

Los logs están en el volumen Docker docker_log-data. En macOS y donde estén dentro de volúmenes, ejecuta el collector como contenedor para acceder directamente:

# macOS / Docker volume approach (recommended)
docker run -d \
  --name otelcol-lab \
  --restart unless-stopped \
  --network docker_default \
  -v docker_log-data:/var/log/generators:ro \
  -v "$(pwd)/configs/otel-collector-config.parallel.yaml:/etc/otelcol-contrib/config.yaml:ro" \
  -e CH_HOST="${CH_HOST}" \
  -e CH_PASSWORD="${CH_PASSWORD}" \
  otel/opentelemetry-collector-contrib:0.146.1

Nota sobre dial_timeout: otel-collector-config.parallel.yaml y otel-collector-config.cutover.yaml definen dial_timeout=60s en el URI de ClickHouse para dar tiempo al handshake en frío. En un CH autogestionado de inicio instantáneo puedes volver a 10s.

Host Linux con acceso directo al volumen: Si los archivos están accesibles directamente en el host, puedes usar el binario:

wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.146.1/otelcol-contrib_0.146.1_linux_amd64.tar.gz
tar -xzf otelcol-contrib_0.146.1_linux_amd64.tar.gz
CH_HOST=${CH_HOST} CH_PASSWORD=${CH_PASSWORD} ./otelcol-contrib --config configs/otel-collector-config.parallel.yaml

3c. Verificar el inicio

docker logs otelcol-lab --tail=10

Una salida sana se parece a:

info  Everything is ready. Begin running and processing data.
info  Started watching file  path=/var/log/generators/web-access-api-gateway.log

Decisiones clave de configs/otel-collector-config.parallel.yaml (collector de logs basado en archivos, variante paralela):

  • Distribución de escritura doble: cada pipeline enumera [clickhouse, elasticsearch/<stream>] para enviar cada línea a ambos backends. El Paso 10a elimina ES.
  • Un exportador ES por flujo (elasticsearch/web → logs-web_access-lab, elasticsearch/app → logs-application-lab, elasticsearch/infra → logs-infrastructure-lab) para mantener los destinos de Filebeat.
  • mapping.mode: ecs en los exportadores ES: convierte registros OTel (Body, Attributes, ...) en documentos ECS compatibles con las plantillas.
  • create_schema: false en el exportador CH: ya creamos tablas optimizadas.
  • logs_table_name: otel_logs: escribe en la tabla Null, que activa la MV hacia otel_logs_v2.
  • compress=lz4 en el endpoint CH: compresión LZ4 por la red.

Nota: Este collector solo procesa logs de archivos. Los 16 servicios OTel Demo envían OTLP a un segundo collector (otelcol-demo), aprovisionado en la Parte 1 y que ahora solo reenvía a APM Server. El Paso 3d cambia su configuración sin modificar el archivo de referencia.

3d. Cambiar otelcol-demo a la configuración paralela (APM + ClickHouse)

Para que distribuya a ambos destinos:

source ../common/env.sh   # CH_HOST, CH_PASSWORD must be set
bash scripts/swap-otelcol-demo-config.sh parallel

Esto recrea el contenedor mediante docker compose -f ... -f docker/docker-compose.otel-demo.parallel.yml up -d --force-recreate --no-deps otelcol-demo. El archivo de la Parte 1 no se modifica; el override monta una configuración distinta y sustituye command:.

Verifica que se inició correctamente:

docker logs "$(docker ps -qf name=otelcol-demo)" --tail=10
# Expected: "Everything is ready. Begin running and processing data."
# No "Failed to start component" or "connection refused" errors.

¿Por qué cambiar en lugar de editar la Parte 1? Editar part1/docker/configs/otelcol-demo-config.yml alteraría la referencia y haría que un futuro cleanup.sh && start from Part 1 necesitase ya CH_HOST/CH_PASSWORD. El cambio mantiene la Parte 1 independiente y reproducible.


Paso 4: validar la ejecución paralela

Espera al menos 5 minutos y ejecuta:

bash scripts/validate_migration.sh

El script comprueba:

  • Recuentos de ClickHouse y Elasticsearch (tolerancia del 5 % para datos recientes)
  • Cobertura de GeoCountry ≥ 20 % (con datos de muestra; MaxMind completo ofrece >90 %)
  • Estado de los diccionarios (ambos LOADED)
  • Que se rellenan las MV de alertas y resumen
  • Que las tablas de métricas (otel_metrics_sum) reciben datos
  • Que TTL está configurado

Para examinar el enriquecimiento:

bash scripts/validate_enrichment.sh

Comprobación manual de paridad: 10 principales rutas:

En Elasticsearch:

curl -s "http://localhost:9200/logs-web_access-lab/_search" \
    -H "Content-Type: application/json" \
    -d '{"size":0,"aggs":{"top_paths":{"terms":{"field":"request_path.keyword","size":10}}}}' \
    | jq '.aggregations.top_paths.buckets'

En ClickHouse:

SELECT RequestPage AS path, count() AS c
FROM otel_logs_v2
WHERE RequestType != ''
GROUP BY path
ORDER BY c DESC
LIMIT 10;

Las rutas principales y su orden relativo deben coincidir.


Paso 5: explorar los datos en HyperDX (interfaz de ClickStack)

HyperDX es la interfaz de observabilidad integrada en ClickStack. Iníciala, conéctala a otel y confirma que logs, trazas y métricas pueden consultarse.

→ Sigue la guía con capturas: hyperdx-guide.md

La guía cubre:

A. Iniciar ClickStackAbrir HyperDX desde la barra lateral del console de Cloud
B. Crear tres fuentesConectar otel.otel_traces, otel.otel_logs_v2 y las cinco tablas otel.otel_metrics_*
C. Buscar logs en tiempo realConfirmar el flujo y usar facetas y búsqueda de texto completo
D. Crear un gráfico con AI AssistantTraducir lenguaje natural ("Error count by services for past 2 hours") en un gráfico

Punto didáctico: Service Map, la búsqueda y AI Assistant consultan directamente las tablas MergeTree otel.*: sin índice aparte, rollups ni redistribución de shards. Los casos de uso de Kibana están disponibles y también pueden escribirse ad hoc en SQL (Paso 9), algo que Kibana no ofrecía.


Paso 6: verificar el ciclo de vida (TTL)

TTL ya está en schema.sql. Compruébalo:

SELECT name, extractAll(create_table_query, 'TTL[^\\n]+') AS ttl_clauses
FROM system.tables
WHERE database = 'otel'
  AND name IN ('otel_logs_v2', 'otel_traces',
               'otel_metrics_gauge', 'otel_metrics_sum', 'otel_metrics_histogram',
               'otel_metrics_exponentialhistogram', 'otel_metrics_summary');

Comprueba tamaño y edad de particiones:

SELECT
    partition,
    sum(rows)                              AS total_rows,
    formatReadableSize(sum(bytes_on_disk)) AS disk_size,
    min(min_time)                          AS oldest_data,
    max(max_time)                          AS newest_data
FROM system.parts
WHERE database = 'otel' AND table = 'otel_logs_v2' AND active
GROUP BY partition
ORDER BY partition;

Punto didáctico: por qué ILM se convierte en una línea de DDL

En la Parte 1 configuraste tres fases: rollover a 5GB/1d (hot), shrink + forcemerge a los 2d (warm) y delete a los 30d. Exigía roles de nodos, conocimiento de shards y JSON de políticas.

En ClickHouse Cloud todo se reduce a una cláusula:

TTL TimestampDate + INTERVAL 30 DAY DELETE
SETTINGS ttl_only_drop_parts = 1
  • Rollover: innecesario. ClickHouse usa una tabla con particiones por fecha.
  • Fase warm (shrink + forcemerge): innecesaria. MergeTree combina partes automáticamente.
  • Capas hot/warm/cold: innecesarias. Los datos están en almacenamiento de objetos con caché automática.
  • Fase delete: TTL ... DELETE la reproduce. ttl_only_drop_parts = 1 elimina particiones enteras, mucho más eficientemente.

Paso 7: tabla de resumen de agregaciones (sustituye transformaciones de ES)

logs_summary_1min, creada en el Paso 2c, es un AggregatingMergeTree que guarda estados parciales. Consúltala con combinadores -Merge:

SELECT
    minute,
    ServiceName,
    SeverityText,
    countMerge(count)                           AS total_events,
    avgMerge(avg_run_time)                      AS avg_run_time_ms,
    quantileMerge(0.99)(p99_run_time)           AS p99_run_time_ms,
    uniqMerge(uniq_remote_addr)                 AS unique_ips
FROM logs_summary_1min
WHERE minute >= now() - INTERVAL 1 HOUR
GROUP BY minute, ServiceName, SeverityText
ORDER BY minute DESC;

Punto didáctico: patrón de combinadores State / Merge

La tabla guarda estados parciales, no valores finales. countState() guarda un recuento parcial serializado; avgState() guarda suma + recuento. countMerge() combina estados y calcula el valor final.

A diferencia de las transformaciones de ES, que vuelven a agregar datos brutos periódicamente, AggregatingMergeTree acumula datos nuevos sin releer registros históricos, lo que escala mejor.


Paso 8: migrar las reglas de alertas

Migra las dos reglas de Kibana a una de estas opciones:

HyperDX tiene una vista Alerts integrada (barra lateral, entre Chart Explorer y Client Sessions). Las alertas se asocian a búsquedas guardadas o gráficos: defines consulta, umbral, ventana y canal. Las fuentes del Paso 5 ya apuntan a otel.

Flujo exacto: consulta ClickStack Alerts — clickhouse.com/docs. La tabla siguiente contiene los valores del laboratorio; la documentación guía por Save Search → Create Alert → Configure threshold → Notify.

#NombreFuente HyperDXCriterio de búsquedaCondiciónVentanaRegla de Kibana sustituida
1web-5xx-errorslogRequestType:* AND StatusCode:>=500count() > 0 (absoluto). Para una tasa, crea un gráfico con countIf(StatusCode >= 500) / count() y alerta si value > 0.05.5 minutos, cada 1 minuto"5xx rate > 5% over 5 minutes"
2heartbeat-<service>logServiceName:"<service-name>" (una búsqueda por servicio, por ejemplo payment-service, order-service)count() == 03 minutos, cada 1 minuto"Service went silent for 3+ minutes"

Aviso sobre la alerta nº 2: Las alertas de búsquedas guardadas evalúan una consulta; detectar silencio por servicio exige una búsqueda y alerta por servicio. Para más de ~5 servicios, la Opción B (patrón SQL NOT IN en una MV) es más adecuada.

Opción B: tabla de alertas precalculada

alert_error_rate y alert_error_rate_mv (Paso 2c) precalculan la tasa de 5xx al insertar. Un proceso externo consulta una tabla diminuta:

-- Poll this every 1 minute (via cron or any scheduler)
SELECT minute, error_rate
FROM alert_error_rate
WHERE minute >= now() - INTERVAL 5 MINUTE
  AND error_rate > 0.05
ORDER BY minute DESC;

Si devuelve filas, se dispara la alerta.

Punto didáctico: Las alertas de Elasticsearch vuelven a agregar datos brutos en cada intervalo. La MV desplaza el coste a la inserción y la consulta lee ~1 fila/minuto, no millones de logs.


Paso 9: ejercicios SQL — lo que no era posible en Elasticsearch

Abre exercises/sql-exercises.md y completa los 6 ejercicios antes de consultar solutions/sql-exercises-solution.md.

EjercicioConceptoLimitación de ES
1JOIN entre señales (logs + trazas)No hay JOIN en el DSL de ES
2Función de ventana LAG() (anomalías)No hay funciones de ventana
3GROUP BY ilimitado (inventario de endpoints)Terms exige size; límite max_buckets
4sequenceMatch() (flujo de solicitudes)No hay coincidencia ordenada de secuencias
5Combinadores -If (varias métricas)Cada métrica condicional exige otra agregación anidada
6Investigación de causa raíz con CTENo hay subconsultas ni CTE en el DSL

Paso 10: desactivar Elasticsearch (transición final)

Continúa solo cuando validate_migration.sh pase sin errores.

10a. Cambiar el collector de archivos a la configuración de transición (solo ClickHouse)

configs/otel-collector-config.cutover.yaml elimina los tres exportadores ES. Recrea otelcol-lab montando ese archivo:

docker stop otelcol-lab && docker rm otelcol-lab

source ../common/env.sh
docker run -d \
  --name otelcol-lab \
  --restart unless-stopped \
  --network docker_default \
  -v docker_log-data:/var/log/generators:ro \
  -v "$(pwd)/configs/otel-collector-config.cutover.yaml:/etc/otelcol-contrib/config.yaml:ro" \
  -e CH_HOST="${CH_HOST}" \
  -e CH_PASSWORD="${CH_PASSWORD}" \
  otel/opentelemetry-collector-contrib:0.146.1

Verifica que solo se cargó ClickHouse:

docker logs otelcol-lab --tail=20 | grep -iE "ready|exporter|fail"
# Expected: "Everything is ready. Begin running and processing data."
# No `elasticsearch/web`, `elasticsearch/app`, `elasticsearch/infra` references.

10b. Validación final

bash scripts/validate_migration.sh

Las comprobaciones de ClickHouse deben pasar. Las de recuento ES fallarán (esperado, ya no recibe datos); confirma que los recuentos de CH crecen.

10c. Verificar la paridad del enriquecimiento

bash scripts/validate_enrichment.sh

Confirma cobertura de GeoCountry ≥ 20 % y BrowserFamily en todos los logs web.

10d. Detener la pila Elasticsearch

docker compose -f ../part1/docker/docker-compose.source.yml stop elasticsearch kibana elastic-apm-server filebeat

Los datos de ES caducarán mediante ILM, o puedes eliminar los volúmenes si no hace falta retención.

10e. Cambiar otelcol-demo a la configuración de transición (solo ClickHouse)

Tras detener elastic-apm-server, su exportador empieza a llenar la cola y ejerce contrapresión sobre todo el pipeline, incluido ClickHouse. Cambia a una configuración sin APM:

source ../common/env.sh
bash scripts/swap-otelcol-demo-config.sh cutover

Esto monta configs/otelcol-demo-config.cutover.yml y recrea el contenedor. La referencia de la Parte 1 queda intacta, por lo que cleanup.sh && start from Part 1 siempre vuelve a un estado APM limpio.

Verifica que las métricas fluyen:

SELECT table, count() AS rows
FROM system.parts
WHERE database = 'otel' AND table LIKE 'otel_metrics%' AND active
GROUP BY table ORDER BY table;

Resultado esperado: otel_metrics_gauge, otel_metrics_sum y otel_metrics_histogram muestran filas.

Enhorabuena: la migración ha terminado.


Lista de comprobación de finalización

ComponenteEstado
otel_logs_v2 recibe datos[ ]
otel_traces recibe datos[ ]
otel_metrics_sum recibe datos[ ]
Diccionario GeoIP LOADED y enriqueciendo[ ]
Fuentes de HyperDX (Traces / log / otel_metrics) configuradas y Search devuelve datos[ ]
validate_migration.sh pasó[ ]
TTL configurado en todas las tablas[ ]
logs_summary_1min AggMergeTree acumula datos[ ]
Al menos una regla de alerta activa[ ]
Los 6 ejercicios SQL completados[ ]
Exportador ES eliminado de OTel[ ]
Contenedores ES detenidos[ ]

Solución de problemas

OTel Collector termina inmediatamente:

  • Comprueba que CH_HOST está definido y es accesible: nc -zv ${CH_HOST} 9440
  • Confirma que el nombre de la base configurada es otel
  • Verifica create_schema: false; si intenta crear su esquema puede entrar en conflicto
  • Verifica que la configuración tiene database: otel y que existe: SHOW DATABASES
  • Comprueba que existe otel_logs: SHOW TABLES FROM otel LIKE 'otel_logs'

OTel Collector termina con schema detection: ... i/o timeout (inicio en frío de CH Cloud):

  • La detección superó dial_timeout, algo habitual en servicios Basic inactivos.
  • Reinicia el contenedor, ahora que CH está caliente: docker start otelcol-lab && sleep 10 && docker logs otelcol-lab --tail=15
  • El docker run del Paso 3b incluye --restart unless-stopped, por lo que se recupera automáticamente. Si lo omitiste, recrea el contenedor.
  • Precalienta CH: clickhouse client --host "$CH_HOST" --port 9440 --user default --password "$CH_PASSWORD" --secure --query "SELECT 1"

GeoCountry está vacío en todas las filas:

  • Comprueba el diccionario: SELECT status FROM system.dictionaries WHERE database = 'otel' AND name = 'geoip_country'
  • Si está NOT_LOADED o FAILED, comprueba que geoip_data contiene datos: SELECT count() FROM otel.geoip_data
  • Ejecuta SYSTEM RELOAD DICTIONARY otel.geoip_country y SYSTEM RELOAD DICTIONARY otel.geoip_city tras cargar el CSV; los diccionarios se crean vacíos y necesitan recarga explícita (el Paso 2b lo hace)

Los recuentos de CH son mucho menores que los de ES:

  • Comprueba el contenedor: docker ps | grep otelcol
  • Examina errores: docker logs otelcol-lab --tail=30
  • Comprueba métricas: curl http://localhost:8888/metrics | grep otelcol_exporter
  • Busca failed to send en los logs

logs_summary_1min está vacío:

  • La MV se activa al insertar en otel.otel_logs (Null), no en otel.otel_logs_v2
  • Confirma que el collector escribe en otel.otel_logs y que el resultado llega a otel_logs_v2 dentro de la base otel
  • En otro terminal, ejecuta clickhouse client --database otel --query "SELECT count() FROM otel_logs_v2" cada 30 segundos y confirma que crece

HyperDX Search no muestra datos:

  • Confirma Database = otel y que otelcol-demo sigue activo con las tablas correctas (otel_logs_v2, otel_traces y las cinco otel_metrics_*)
  • En la fuente Log, Timestamp Column debe ser TimestampTime (no Timestamp); consulta hyperdx-guide.md
  • Prueba el intervalo "Last 24 hours"
  • Verifica las filas: clickhouse client --database otel --query "SELECT count() FROM otel_logs_v2"

Siguiente: Parte 4: Validación de conocimientos →

En esta página

¿Quieres seguir tu progreso?

Opcional. Enviaremos un enlace por correo para confirmar tu dirección; el progreso se registrará cuando lo abras.

Usa tu correo de trabajo, no uno personal.

Para seguir el progreso también debes aceptar los Términos del servicio actuales en la Configuración de privacidad.

ES