Elasticsearch MigrationClickHouse Workshops

02 Analisar e projetar

Inspecione a carga de trabalho do Elasticsearch, traduza seu modelo de dados e suas consultas e registre as decisões da arquitetura de migração.

Execute este módulo a partir do diretório de artefatos do workshop:

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

Tempo estimado: 60–90 minutos

Visão geral

Antes de usar qualquer ferramenta de migração, você analisará o ambiente Elasticsearch em execução, mapeará seus conceitos para o ClickHouse (o destino), traduzirá consultas representativas para SQL do ClickHouse e escreverá um Registro de Decisões de Arquitetura (ADR) documentando sua estratégia de migração. Nenhuma alteração de infraestrutura ocorre nesta parte — este é um exercício de raciocínio que orienta o plano de execução da Parte 3.

Pré-requisitos

  • O ambiente da Parte 1 está em execução (consulte o Checkpoint da Parte 1)
  • Todas as verificações de validação estão passando: bash ../part1/validation/check.sh
  • O jq está instalado (brew install jq no macOS, apt-get install jq no Debian/Ubuntu)

O que você produzirá

Três entregáveis, todos em exercises/:

#EntregávelArquivo
2APlanilha do modelo de dados que analisa seu ambiente ES, propõe um projeto no ClickHouse e registra uma linha de base da latência das consultas para comparação na Parte 3exercises/worksheet.md
2BSQL do ClickHouse para 5 consultas representativas do Elasticsearchexercises/query-translation.md
2CRegistro de Decisões de Arquitetura que aborda 7 decisões estratégicasexercises/adr-template.md

As respostas-modelo estão em soluções/. Conclua cada exercício antes de consultá-las.


Referência: mapeamento dos conceitos principais

Use esta tabela ao realizar os Exercícios 2A e 2C.

Conceito do ElasticsearchEquivalente no ClickHousePrincipal diferença
ÍndiceTabelaO ES cria novos índices por período (rollover de ILM); o CH usa uma única tabela com partições
Fluxo de dadosUma única tabela MergeTreeO ES abstrai índices rotativos + ILM + rollover automático; o CH substitui isso por uma única tabela particionada — sem rotação nem gerenciamento de fluxos
DocumentoLinhaOs documentos do ES têm esquema flexível; as linhas do CH seguem um esquema (com tipos JSON/Map para flexibilidade)
CampoColunaCampos do ES podem ser adicionados dinamicamente; colunas do CH são definidas explicitamente (exceto com o tipo JSON)
Mapeamento/modelo de índice combinávelEsquema da tabela (DDL)O ES combina modelos de componentes; o CH usa um único CREATE TABLE
ShardShard (lógico)Shards do ES são estruturas físicas do Lucene vinculadas ao heap da JVM; shards do CH são lógicos e escaláveis verticalmente
RéplicaRéplicaO ES usa replicação síncrona entre primário e réplica; o CH usa replicação assíncrona por padrão
Índice invertidoChave primária + índices de skippingO ES indexa todos os campos por padrão; o CH usa chaves primárias ordenadas e índices de skipping opcionais bloom_filter/text (tokenbf_v1/ngrambf_v1 obsoletos a partir da 26.2)
ILM (hot → warm → cold → delete)TTL + partições (somente exclusão)O ClickHouse Cloud armazena todos os dados em armazenamento de objetos com cache local automático — as camadas hot/warm/cold são irrelevantes. Para expiração, basta TTL … DELETE.
Pipeline de ingestão (geoip, user_agent, grok, script)Views materializadas + colunas materializadas + dicionáriosO ES transforma os dados antes da indexação com processadores; o CH usa views materializadas como gatilhos no momento da inserção, colunas materializadas para expressões por linha e dicionários para consultas de enriquecimento
Transformações do Elasticsearch (rollups)Views materializadas incrementais + AggregatingMergeTreeAs transformações do ES reagregam periodicamente; o CH usa estados parciais de agregação incremental que se mesclam automaticamente
Regras de alerta do KibanaAlertas do Grafana/HyperDX/MVs pré-computadasO ClickHouse não possui alertas integrados — use o Grafana com a fonte de dados do ClickHouse, alertas do HyperDX ou pré-calcule condições de alerta por meio de views materializadas
KibanaHyperDXAmbos são interfaces de observabilidade; o HyperDX é nativo do OTel
Elastic Agent/FilebeatOpenTelemetry CollectorO ES usa agentes proprietários; o ClickStack usa o OTel Collector independente de fornecedor
ECS (Elastic Common Schema)Convenções semânticas do OTelNomenclatura de atributos diferente; o ECS está sendo incorporado à especificação do OTel

Referência: mapeamento do ECS para as convenções semânticas do OTel

Na Parte 3, você configurará o OTel Collector para emitir campos nativos do OTel. Use esta tabela para traduzir cada campo ECS documentado no Exercício 2A. Os campos mantidos nos Maps LogAttributes/SpanAttributes/ResourceAttributes do collector do OTel são indicados dessa forma — promova-os a colunas de nível superior somente quando os padrões de consulta justificarem isso (consulte a Decisão 3 do ADR).

Campo ECS (Elasticsearch)Equivalente OTel (ClickHouse)Observações
@timestampTimestamp (top-level DateTime64(9))
messageBody (top-level String)
log.level / levelSeverityText + SeverityNumberO OTel adiciona o nível numérico 1–24
event.severitySeverityText
service / service.nameServiceName (top-level LowCardinality(String))
service.versionServiceVersion / ResourceAttributes['service.version']
host.hostname / hostnamehost.name (ResourceAttributes['host.name'])O ECS usa host.name como alias; o OTel usa apenas host.name
host.iphost.ip (resource attribute)
trace.idTraceId (top-level String)
span.id / transaction.idSpanId
parent.idParentSpanId
http.request.methodhttp.request.methodIgual nos dois — o OTel adotou a nomenclatura do ECS
http.response.status_codehttp.response.status_codeIgual
client.ip / source.ip / remote_addrclient.addressArmazenado como string
user_agent.original / user_agentuser_agent.original
user_agent.name / user_agent_parsed.nameuser_agent.nameSaída do analisador de UA
error.messageexception.message (evento de span)
error.stack_traceexception.stacktrace
transaction.namespan.name (para transações)
transaction.duration.usDuration (Int64 nanossegundos)Mudança importante de unidade: µs → ns
labels.*ResourceAttributes / SpanAttributes / LogAttributesTipo Map

Exercício 2A: mapeamento do modelo de dados

Entregável: conclua exercises/worksheet.md.

Examine o ambiente Elasticsearch em execução e preencha uma seção da planilha para cada fluxo de dados (logs-web_access-lab, logs-application-lab, logs-infrastructure-lab). Em seguida, proponha um projeto de tabela do ClickHouse para cada um.

Comandos de inspeção

Execute-os em qualquer host que consiga acessar o Elasticsearch em http://localhost:9200 (o mesmo host em que você executou a Parte 1).

Liste os fluxos de dados e seus índices subjacentes:

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

Obtenha o mapeamento de um fluxo de dados:

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

Conte os campos folha exclusivos do mapeamento — isso exclui os metacampos do ES (_id, _index, _source, _doc_count etc.), que aumentariam a contagem em cerca de 14:

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

Obtenha as estatísticas do índice (contagem e tamanho dos documentos):

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}'

Obtenha a alocação de shards:

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

Inspecione a política de ILM e a fase atual:

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}'

Inspecione os pipelines de ingestão:

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

Verifique as estatísticas dos pipelines (documentos processados e falhas):

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

Verifique se o enriquecimento está funcionando:

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}'

Conte os valores exclusivos em campos de alta cardinalidade:

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

Inspeção: Carga de trabalho 2 (traces e logs de APM)

O OTel Demo grava em traces-apm-* e em cerca de 18 fluxos de dados logs-apm.app.*. A Seção 4 da planilha aborda essa carga de trabalho.

Liste os fluxos de dados de APM:

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

Obtenha uma amostra das chaves de nível superior de um documento de trace:

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

Cardinalidades dos campos de trace e divisão 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

Percentis da duração das transações (use para p50/p95 da Seção 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 dos fluxos de logs por serviço do 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 uma linha de base da latência das consultas

Antes de projetar o esquema de destino, registre quanto tempo 3 consultas canônicas levam na stack atual do ES. Na Parte 3, você executará novamente as consultas equivalentes no ClickHouse e fará a comparação. Use o campo .took (milissegundos gastos pelo ES para atender à consulta, sem incluir rede/serialização).

# 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

Registre a mediana das 3 execuções de cada consulta na Seção 5 de exercises/worksheet.md.

Dica: Ao propor a chave ORDER BY do ClickHouse, pense em qual combinação de campos melhor agrupa as linhas consultadas juntas. Os dashboards criados na Parte 1 são seu principal guia dos padrões de acesso.


Exercício 2B: tradução de padrões de consulta

Entregável: conclua exercises/query-translation.md.

O arquivo do exercício contém 5 consultas representativas do Elasticsearch extraídas dos dashboards e da interface de APM da Parte 1. Para cada uma, escreva o SQL equivalente do ClickHouse para um esquema de destino chamado otel_logs (ou otel_traces para consultas de traces), com as seguintes colunas:

ColunaTipoMapeada do campo do ES
TimestampDateTime64(9)@timestamp
ServiceNameLowCardinality(String)service
BodyStringmessage/linha de log bruta
SeverityTextLowCardinality(String)event.severity
LogAttributesMap(LowCardinality(String), String)todos os demais campos de log (request_path, status, remote_addr, …)
TraceIdStringtrace.id (somente tabela de traces)

Por que usar Map em vez de colunas individuais? O esquema do OTel usa um Map para armazenar atributos de forma flexível e independente de fornecedor. Na Parte 3, você extrairá campos consultados com frequência (como status) para colunas materializadas a fim de melhorar o desempenho.


Exercício 2C: Registro de Decisões de Arquitetura

Entregável: conclua exercises/adr-template.md.

Escreva um ADR curto que aborde sete decisões estratégicas. Não há uma única resposta "correta" — o objetivo é que sua justificativa seja coerente tanto com a configuração atual do ES (do Exercício 2A) quanto com as restrições do ClickHouse Cloud (armazenamento colunar, baseado em objetos e sem alertas integrados).

As sete decisões:

  1. Abordagem de migração — execução paralela, transição direta ou em fases por tipo de log?
  2. Estratégia de agentes — OTel Collector direto, ponte Filebeat → Vector → OTel ou Filebeat → Kafka → Vector → OTel?
  3. Estratégia de esquema — esquema padrão do OTel, personalizado com colunas materializadas ou tabela de origem Null + MVs? Especifique-a para cada um dos 3 tipos de log e para os traces de APM, proponha uma chave ORDER BY para cada um e decida como tratar novos campos que surgirem após o lançamento (somente Map, promoção automática, coluna JSON ou solução híbrida).
  4. Tradução do pipeline de ingestão — onde fica cada processador do ES? GeoIP (dicionário ou processador do collector), análise de user-agent, análise grok, derivação de severidade e timestamp de ingestão.
  5. Ciclo de vida dos dados — fases de ILM que se tornam desnecessárias (explique por quê), período de retenção e cláusula TTL.
  6. Migração de alertas — escolha da ferramenta e como recriar as 2 regras de alerta do Kibana (High Error Rate: >5% de 5xx em 5 min; Service Heartbeat: nenhum log de um serviço por >3 min).
  7. Estratégia para dados históricos — carregar retroativamente todos os cerca de 62 milhões de documentos do ES no ClickHouse, começar do zero e manter o ES como somente leitura ou adotar uma solução híbrida? Em caso de backfill, qual ferramenta usar e como remover duplicatas?

Checkpoint

Antes de prosseguir para a Parte 3, verifique:

  • Todos os campos de exercises/worksheet.md estão preenchidos para os 3 fluxos de dados
  • Cada seção de fluxo de dados inclui uma proposta de projeto de tabela do ClickHouse (chave de partição, ORDER BY, colunas materializadas e TTL)
  • Você identificou quais fases de ILM se tornam desnecessárias no ClickHouse Cloud e por quê
  • exercises/query-translation.md contém SQL funcional do ClickHouse para as 5 consultas
  • exercises/adr-template.md contém uma justificativa para cada uma das 7 decisões (não apenas uma escolha)
  • Você comparou suas respostas da planilha e do ADR com as soluções/ e consegue explicar qualquer desvio intencional

Próximo: Parte 3: execução da migração →

Nesta página

Acompanhar seu progresso?

Opcional. Enviaremos um link por e-mail para confirmar seu endereço; o progresso será registrado depois que você o abrir.

Use seu e-mail corporativo, não um endereço pessoal.

O acompanhamento do progresso também exige a aceitação dos Termos de Serviço atuais nas Configurações de privacidade.

PT