Elasticsearch MigrationClickHouse Workshops

02 Analizar y diseñar

Inspecciona la carga de Elasticsearch, traduce su modelo de datos y sus consultas, y registra las decisiones de arquitectura de la migración.

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

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

Tiempo estimado: 60–90 minutos

Descripción general

Antes de utilizar herramientas de migración, analizarás el entorno de Elasticsearch en ejecución, mapearás sus conceptos a ClickHouse (el destino), traducirás consultas representativas a SQL de ClickHouse y redactarás un registro de decisiones de arquitectura (ADR) que documente la estrategia de migración. En esta parte no se modifica la infraestructura: es un ejercicio de reflexión que guía el plan de ejecución de la Parte 3.

Requisitos previos

  • El entorno de la Parte 1 está en ejecución (consulta la comprobación de la Parte 1)
  • Pasan todas las comprobaciones: bash ../part1/validation/check.sh
  • jq está instalado (brew install jq en macOS, apt-get install jq en Debian/Ubuntu)

Lo que producirás

Tres entregables, todos en exercises/:

#EntregableArchivo
2AHoja de trabajo del modelo de datos que analiza el entorno ES, propone un diseño de ClickHouse y registra una referencia de latencia para compararla en la Parte 3exercises/worksheet.md
2BSQL de ClickHouse para 5 consultas representativas de Elasticsearchexercises/query-translation.md
2CRegistro de decisiones de arquitectura con 7 decisiones estratégicasexercises/adr-template.md

Las respuestas modelo están en solutions/. Completa cada ejercicio antes de consultarlas.


Referencia: mapeo de conceptos fundamentales

Utiliza esta tabla en los Ejercicios 2A y 2C.

Concepto de ElasticsearchEquivalente en ClickHouseDiferencia clave
ÍndiceTablaES crea índices nuevos por periodo (rollover de ILM); CH usa una sola tabla con particiones
Flujo de datosUna tabla MergeTreeES abstrae índices rotativos + ILM + rollover automático; CH los sustituye por una tabla particionada, sin rotación ni gestión de flujos
DocumentoFilaLos documentos de ES tienen esquema flexible; las filas de CH están ligadas al esquema (con tipos JSON/Map para aportar flexibilidad)
CampoColumnaEn ES se añaden campos dinámicamente; en CH las columnas se definen explícitamente (salvo con el tipo JSON)
Mapeo / plantilla de índice componibleEsquema de tabla (DDL)ES compone plantillas de componentes; CH utiliza un solo CREATE TABLE
ShardShard (lógico)Los shards de ES son estructuras físicas de Lucene ligadas al heap de JVM; los de CH son lógicos y escalan verticalmente
RéplicaRéplicaES usa replicación primaria-réplica síncrona; CH es asíncrono de forma predeterminada
Índice invertidoClave primaria + índices de saltoES indexa todos los campos; CH usa claves primarias ordenadas e índices de salto opcionales bloom_filter / text (tokenbf_v1 / ngrambf_v1 obsoletos >= 26.2)
ILM (hot → warm → cold → delete)TTL + particiones (solo eliminación)ClickHouse Cloud guarda todos los datos en almacenamiento de objetos con caché local automática; las capas hot/warm/cold son irrelevantes. Solo hace falta TTL … DELETE para caducar datos.
Pipeline de ingesta (geoip, user_agent, grok, script)Vistas materializadas + columnas materializadas + diccionariosES transforma antes de indexar con procesadores; CH usa vistas materializadas como disparadores durante la inserción, columnas materializadas para expresiones por fila y diccionarios para búsquedas de enriquecimiento
Transformaciones de Elasticsearch (rollups)Vistas materializadas incrementales + AggregatingMergeTreeES vuelve a agregar periódicamente; CH utiliza estados parciales de agregación incremental que se combinan automáticamente
Reglas de alertas de KibanaGrafana Alerting / HyperDX Alerts / MV precalculadasClickHouse no incorpora alertas: utiliza Grafana con la fuente de datos de ClickHouse, alertas de HyperDX o precalcula condiciones mediante vistas materializadas
KibanaHyperDXAmbas son interfaces de observabilidad; HyperDX es nativo de OTel
Elastic Agent / FilebeatOpenTelemetry CollectorES usa agentes propietarios; ClickStack usa OTel Collector, independiente del proveedor
ECS (Elastic Common Schema)Convenciones semánticas de OTelLos nombres de atributos difieren; ECS se está integrando en la especificación OTel

Referencia: mapeo de ECS a convenciones semánticas de OTel

En la Parte 3 configurarás OTel Collector para emitir campos nativos de OTel. Utiliza esta tabla para traducir cada campo ECS documentado en el Ejercicio 2A. Los campos que permanecen en los Maps LogAttributes / SpanAttributes / ResourceAttributes del collector se indican como tales; promuévelos a columnas de nivel superior solo cuando los patrones de consulta lo justifiquen (consulta la Decisión 3 del ADR).

Campo ECS (Elasticsearch)Equivalente OTel (ClickHouse)Observaciones
@timestampTimestamp (nivel superior DateTime64(9))
messageBody (nivel superior String)
log.level / levelSeverityText + SeverityNumberOTel añade un nivel numérico 1–24
event.severitySeverityText
service / service.nameServiceName (nivel superior LowCardinality(String))
service.versionServiceVersion / ResourceAttributes['service.version']
host.hostname / hostnamehost.name (ResourceAttributes['host.name'])ECS admite el alias host.name; OTel solo usa host.name
host.iphost.ip (atributo de recurso)
trace.idTraceId (nivel superior String)
span.id / transaction.idSpanId
parent.idParentSpanId
http.request.methodhttp.request.methodIgual en ambos: OTel adoptó el nombre de ECS
http.response.status_codehttp.response.status_codeIgual
client.ip / source.ip / remote_addrclient.addressAlmacenado como string
user_agent.original / user_agentuser_agent.original
user_agent.name / user_agent_parsed.nameuser_agent.nameResultado del analizador UA
error.messageexception.message (evento de span)
error.stack_traceexception.stacktrace
transaction.namespan.name (para transacciones)
transaction.duration.usDuration (Int64 nanosegundos)Cambio importante de unidad: µs → ns
labels.*ResourceAttributes / SpanAttributes / LogAttributesTipo Map

Ejercicio 2A: mapeo del modelo de datos

Entregable: Completa exercises/worksheet.md.

Examina tu entorno de Elasticsearch y rellena una sección de la hoja por cada flujo (logs-web_access-lab, logs-application-lab, logs-infrastructure-lab). Después propón un diseño de tabla de ClickHouse para cada uno.

Comandos de inspección

Ejecútalos desde cualquier host que pueda acceder a Elasticsearch en http://localhost:9200 (el mismo host de la Parte 1).

Enumerar flujos de datos y sus índices subyacentes:

curl -s "http://localhost:9200/_data_stream/logs-*" | jq '.data_streams[] | {name, indices: [.indices[].index_name], generation}'

Obtener el mapeo de un flujo:

curl -s "http://localhost:9200/logs-web_access-lab/_mapping" | jq .

Contar campos hoja únicos del mapeo — excluye metacampos de ES (_id, _index, _source, _doc_count, etc.) que inflarían el recuento en ~14:

curl -s "http://localhost:9200/logs-web_access-lab/_field_caps?fields=*" \
  | jq '[.fields | to_entries[] | select(.key | startswith("_") | not)] | length'

Obtener estadísticas del índice (documentos y tamaño):

curl -s "http://localhost:9200/logs-web_access-lab/_stats" \
  | jq '.indices | to_entries[] | {index: .key, docs: .value.primaries.docs.count, size_bytes: .value.primaries.store.size_in_bytes}'

Obtener la asignación de shards:

curl -s "http://localhost:9200/_cat/shards/logs-web_access-*?v"

Inspeccionar la política ILM y la fase actual:

curl -s "http://localhost:9200/_ilm/policy/lab-observability-policy" | jq .
curl -s "http://localhost:9200/logs-web_access-lab/_ilm/explain" \
  | jq '.indices | to_entries[] | {index: .key, phase: .value.phase, age: .value.age}'

Inspeccionar los pipelines de ingesta:

for p in default-enrichment web-access-enrichment app-log-enrichment infra-log-parsing; do
  echo "=== $p ==="
  curl -s "http://localhost:9200/_ingest/pipeline/$p" | jq ".\"$p\".processors"
done

Comprobar estadísticas de pipelines (documentos y fallos):

curl -s "http://localhost:9200/_nodes/stats/ingest?filter_path=nodes.*.ingest.pipelines" \
  | jq '.nodes | to_entries[0].value.ingest.pipelines'

Verificar que funciona el enriquecimiento:

curl -s "http://localhost:9200/logs-web_access-lab/_search?size=1" \
  | jq '.hits.hits[0]._source | {remote_addr, geo, user_agent_parsed, "event.severity": ."event".severity}'

Contar valores únicos en campos de alta cardinalidad:

curl -s -H 'Content-Type: application/json' "http://localhost:9200/logs-web_access-lab/_search?size=0" -d '{
  "aggs": {
    "services": {"cardinality": {"field": "service"}},
    "paths":    {"cardinality": {"field": "request_path.keyword"}},
    "ips":      {"cardinality": {"field": "remote_addr"}}
  }
}' | jq .aggregations

Inspección: Carga de trabajo 2 (trazas y logs APM)

OTel Demo escribe en traces-apm-* y en unos 18 flujos logs-apm.app.*. La Sección 4 de la hoja cubre esta carga.

Enumerar flujos APM:

curl -s "http://localhost:9200/_data_stream/traces-apm*,logs-apm*" \
  | jq '.data_streams[] | {name, generation}'

Muestrear las claves de nivel superior de una traza:

curl -s "http://localhost:9200/traces-apm-*/_search?size=1" \
  | jq '.hits.hits[0]._source | keys'

Cardinalidades de campos de traza y división por tipo de evento:

curl -s -H 'Content-Type: application/json' "http://localhost:9200/traces-apm-*/_search?size=0" -d '{
  "aggs": {
    "services":    {"cardinality": {"field": "service.name"}},
    "transactions":{"cardinality": {"field": "transaction.name"}},
    "languages":   {"terms": {"field": "service.language.name"}},
    "event_types": {"terms": {"field": "processor.event"}}
  }
}' | jq .aggregations

Percentiles de duración de transacciones (para p50/p95 de la Sección 4a):

curl -s -H 'Content-Type: application/json' "http://localhost:9200/traces-apm-*/_search?size=0" -d '{
  "query": {"term": {"processor.event": "transaction"}},
  "aggs": {
    "dur_pct": {"percentiles": {"field": "transaction.duration.us", "percents": [50, 95, 99]}}
  }
}' | jq .aggregations

Documentos de flujos de logs por servicio de OTel Demo:

curl -s "http://localhost:9200/_cat/indices/logs-apm.app.*?h=index,docs.count&format=json" \
  | jq 'sort_by(-(."docs.count" | tonumber)) | .[] | {index, docs: .["docs.count"]}'

Registrar una referencia de latencia

Antes de diseñar el esquema de destino, registra cuánto tardan 3 consultas canónicas en la pila ES actual. En la Parte 3 repetirás los equivalentes de ClickHouse. Usa el campo .took (milisegundos que ES dedicó a servir la consulta, sin red ni serialización).

# Q1 — Top 10 request paths (status 200)
for i in 1 2 3; do
  curl -s -H 'Content-Type: application/json' \
    "http://localhost:9200/logs-web_access-lab/_search?size=0" -d '{
      "query":{"bool":{"filter":[{"term":{"status":"200"}}]}},
      "aggs":{"top_paths":{"terms":{"field":"request_path.keyword","size":10}}}
    }' | jq '.took'
done

# Q2 — 5xx count per 1m in last 1h
for i in 1 2 3; do
  curl -s -H 'Content-Type: application/json' \
    "http://localhost:9200/logs-web_access-lab/_search?size=0" -d '{
      "query":{"bool":{"filter":[
        {"range":{"status":{"gte":"500"}}},
        {"range":{"@timestamp":{"gte":"now-1h"}}}]}},
      "aggs":{"errors":{"date_histogram":{"field":"@timestamp","fixed_interval":"1m"}}}
    }' | jq '.took'
done

# Q3 — Trace lookup by any trace.id (grab a real one first)
TID=$(curl -s "http://localhost:9200/traces-apm-*/_search?size=1" | jq -r '.hits.hits[0]._source.trace.id')
for i in 1 2 3; do
  curl -s -H 'Content-Type: application/json' \
    "http://localhost:9200/traces-apm-*/_search?size=10" -d "{\"query\":{\"term\":{\"trace.id\":\"$TID\"}}}" \
    | jq '.took'
done

Registra la mediana de las 3 ejecuciones de cada consulta en la Sección 5 de exercises/worksheet.md.

Consejo: Al proponer la clave ORDER BY de ClickHouse, piensa qué combinación reúne mejor las filas consultadas juntas. Los dashboards de la Parte 1 son la guía principal de patrones de acceso.


Ejercicio 2B: traducción de patrones de consulta

Entregable: Completa exercises/query-translation.md.

El archivo contiene 5 consultas representativas de Elasticsearch extraídas de los dashboards y la interfaz APM de la Parte 1. Escribe el SQL de ClickHouse equivalente contra un esquema de destino llamado otel_logs (o otel_traces para búsquedas de trazas) con estas columnas:

ColumnaTipoSe mapea desde el campo ES
TimestampDateTime64(9)@timestamp
ServiceNameLowCardinality(String)service
BodyStringmessage / línea de log bruta
SeverityTextLowCardinality(String)event.severity
LogAttributesMap(LowCardinality(String), String)todos los demás campos (request_path, status, remote_addr, …)
TraceIdStringtrace.id (solo tabla de trazas)

¿Por qué Map en lugar de columnas individuales? El esquema OTel utiliza un Map para almacenar atributos con flexibilidad y sin depender del proveedor. En la Parte 3 extraerás los campos consultados con frecuencia (como status) a columnas materializadas para mejorar el rendimiento.


Ejercicio 2C: registro de decisiones de arquitectura

Entregable: Completa exercises/adr-template.md.

Escribe un ADR breve que cubra siete decisiones estratégicas. No hay una única respuesta «correcta»: la justificación debe ser coherente con la configuración ES actual (Ejercicio 2A) y las restricciones de ClickHouse Cloud (almacenamiento columnar basado en objetos y sin alertas integradas).

Las siete decisiones:

  1. Enfoque de migración — ¿ejecución paralela, transición total o fases por tipo de log?
  2. Estrategia de agentes — ¿OTel Collector directo, puente Filebeat → Vector → OTel o Filebeat → Kafka → Vector → OTel?
  3. Estrategia de esquema — ¿esquema OTel predeterminado, personalizado con columnas materializadas o tabla de origen Null + MV? Especifícalo para cada tipo de log y las trazas APM, propón una clave ORDER BY para cada uno y decide cómo tratar los campos nuevos tras el lanzamiento (solo Map, promoción automática, columna JSON o híbrido).
  4. Traducción del pipeline de ingesta — ¿dónde se implementa cada procesador de ES? GeoIP (diccionario o procesador del collector), análisis de user-agent y grok, derivación de severidad y marca de tiempo de ingesta.
  5. Ciclo de vida — fases ILM innecesarias (y por qué), periodo de retención y cláusula TTL.
  6. Migración de alertas — herramienta y recreación de 2 reglas de Kibana (High Error Rate: >5% de 5xx en 5m; Service Heartbeat: ningún log de un servicio durante >3m).
  7. Datos históricos — ¿backfill de los ~62 millones de documentos de ES, inicio desde cero conservando ES solo para lectura o solución híbrida? Si haces backfill, ¿con qué herramienta y cómo eliminas duplicados?

Comprobación

Antes de continuar a la Parte 3, verifica:

  • exercises/worksheet.md tiene todos los campos cumplimentados para los 3 flujos de datos
  • Cada sección propone un diseño de tabla de ClickHouse (clave de partición, ORDER BY, columnas materializadas y TTL)
  • Has identificado qué fases de ILM son innecesarias en ClickHouse Cloud y por qué
  • exercises/query-translation.md contiene SQL de ClickHouse funcional para las 5 consultas
  • exercises/adr-template.md contiene una justificación para cada una de las 7 decisiones (no solo una elección)
  • Has comparado tu hoja y el ADR con solutions/ y puedes explicar las desviaciones deliberadas

Siguiente: Parte 3: Ejecución de la migración →

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