Componentes relevantes do ClickHouse
- O OpenTelemetry Collector é um proxy que recebe, processa e exporta dados de telemetria. Uma solução baseada em ClickHouse usa esse componente tanto para a coleta de logs quanto para o processamento de eventos antes do agrupamento em lotes e da inserção.
- SDKs de linguagem que implementam a especificação, as APIs e a exportação de dados de telemetria. Esses SDKs garantem, na prática, que traces sejam registrados corretamente no código da aplicação, gerando os spans que os compõem e assegurando a propagação do contexto entre serviços por meio de metadados — formando, assim, traces distribuídos e permitindo correlacionar spans. Esses SDKs são complementados por um ecossistema que instrumenta automaticamente bibliotecas e frameworks comuns, o que significa que o usuário não precisa alterar seu código e obtém instrumentação pronta para uso.
Distribuições
filelog, junto com o exportador ClickHouse, necessários para uma solução com ClickHouse, estão presentes apenas na OpenTelemetry Collector Contrib Distro.
Essa distribuição contém muitos componentes e permite experimentar várias configurações. No entanto, em produção, recomenda-se limitar o collector para incluir apenas os componentes necessários para o ambiente. Alguns motivos para fazer isso:
- Reduzir o tamanho do collector, diminuindo o tempo de implantação
- Melhorar a segurança do collector, reduzindo a superfície de ataque disponível
Ingestão de dados com OTel
Papéis de implantação do collector
- Agent - As instâncias de agente coletam dados na borda, por exemplo, em servidores ou nós do Kubernetes, ou recebem eventos diretamente de aplicações instrumentadas com um SDK do OpenTelemetry. Neste último caso, a instância do agente é executada junto com a aplicação ou no mesmo host da aplicação (como um sidecar ou um Conjunto de Daemon). Os agentes podem enviar seus dados diretamente para o ClickHouse ou para uma instância de gateway. No primeiro caso, isso é chamado de padrão de implantação de agente.
- Gateway - As instâncias de gateway fornecem um serviço independente (por exemplo, uma implantação no Kubernetes), normalmente por cluster, por data center ou por região. Elas recebem eventos de aplicações (ou de outros collectors atuando como agentes) por meio de um único endpoint OTLP. Normalmente, um conjunto de instâncias de gateway é implantado, com um balanceador de carga pronto para uso distribuindo a carga entre elas. Se todos os agentes e aplicações enviarem seus sinais para esse único endpoint, isso geralmente é chamado de padrão de implantação de gateway.
Coletando logs
- Coleta via filelog receiver - Esse receiver acompanha arquivos em disco e gera log messages, enviando-as ao ClickHouse. Esse receiver lida com tarefas complexas, como detectar mensagens de várias linhas, tratar rotações de logs, fazer checkpointing para maior robustez em reinicializações e extrair estrutura. Além disso, esse receiver também consegue acompanhar logs de contêineres Docker e Kubernetes, podendo ser implantado como um Chart do Helm, extraindo a estrutura deles e enriquecendo-os com os detalhes do pod do Kubernetes.
Dica:
otelbin.iootelbin.io é útil para validar e visualizar configurações.Estruturados vs. não estruturados
Exemplo
json_parser porque nossos logs são estruturados. Modifique o caminho para o arquivo access-structured.log.
Considere usar o ClickHouse para fazer o parsingO exemplo abaixo extrai o timestamp do log. Isso exige o uso do operador
json_parser, que converte toda a linha de log em uma string JSON, colocando o resultado em LogAttributes. Isso pode ter um custo computacional alto e pode ser feito com mais eficiência no ClickHouse - Extração de estrutura com SQL. Um exemplo equivalente com logs não estruturados, que usa o regex_parser para fazer isso, pode ser encontrado aqui.filelog); por exemplo, em vez de otelcol_0.102.1_darwin_arm64.tar.gz, os usuários baixariam otelcol-contrib_0.102.1_darwin_arm64.tar.gz. As versões podem ser encontradas aqui.
Depois de instalado, o OTel collector pode ser executado com os seguintes comandos:
Body, mas o JSON foi extraído automaticamente para o campo Attributes graças ao json_parser. Esse mesmo operador foi usado para extrair o timestamp para a coluna Timestamp apropriada. Para recomendações sobre como processar logs com OTel, consulte Processing.
OperadoresOperadores são a unidade mais básica do processamento de logs. Cada operador cumpre uma única função, como ler linhas de um arquivo ou fazer o parsing de JSON de um campo. Em seguida, os operadores são encadeados em um pipeline para alcançar o resultado desejado.
TraceID nem SpanID. Se esses campos estiverem presentes, por exemplo, em casos em que os usuários estejam implementando rastreamento distribuído, eles poderão ser extraídos do JSON usando as mesmas técnicas mostradas acima.
Para usuários que precisam coletar arquivos de log locais ou do Kubernetes, recomendamos que se familiarizem com as opções de configuração disponíveis para o filelog receiver e com a forma como offsets e o parsing de logs multilinha é tratado.
Coleta de logs do Kubernetes
ResourceAttributes. Atualmente, o ClickHouse usa o tipo Map(String, String) para essa coluna. Consulte Usando Maps e Extraindo de maps para mais detalhes sobre como tratar e otimizar esse tipo.
Coletando traces
Exemplo
telemetrygen para gerar dados de trace. Siga as instruções aqui para instalar.
A configuração a seguir recebe eventos de trace por um receiver OTLP antes de enviá-los para stdout.
config-traces.xml
telemetrygen:
stdout:
Processamento - filtragem, transformação e enriquecimento
-
Processadores - Os processadores pegam os dados coletados pelos receivers e os modificam ou transformam antes de enviá-los aos exporters. Os processadores são aplicados na ordem configurada na seção
processorsda configuração do collector. Eles são opcionais, mas o conjunto mínimo normalmente é recomendado. Ao usar um OTel collector com ClickHouse, recomendamos limitar os processadores a:- Um memory_limiter é usado para evitar situações de falta de memória no collector. Consulte Estimando recursos para recomendações.
- Qualquer processador que faça enriquecimento com base em contexto. Por exemplo, o Kubernetes Attributes Processor permite definir automaticamente atributos de recurso de spans, métricas e logs com metadados do k8s, por exemplo, enriquecendo eventos com o ID do pod de origem.
- Tail ou head sampling se necessário para traces.
- Filtragem básica - descarte de eventos desnecessários, caso isso não possa ser feito por meio de operator (veja abaixo).
- Batching - essencial ao trabalhar com ClickHouse para garantir que os dados sejam enviados em lotes. Consulte “Exportando para ClickHouse”.
- Operators - Operators fornecem a unidade mais básica de processamento disponível no receiver. Há suporte a parsing básico, permitindo definir campos como Severity e Timestamp. Há suporte a parsing de JSON e regex, além de filtragem de eventos e transformações básicas. Recomendamos fazer a filtragem de eventos aqui.
Exemplo
regex_parser) e filtrar eventos, juntamente com um processador para agrupar eventos em lotes e limitar o uso de memória.
config-unstructured-logs-with-processor.yaml
Exportando para o ClickHouse
Use o OpenTelemetry Collector ContribO ClickHouse exporter faz parte do OpenTelemetry Collector Contrib, não da distribuição principal. Você pode usar a distribuição contrib ou compilar seu próprio collector.
- pipelines - A configuração acima destaca o uso de pipelines, compostos por um conjunto de receivers, processors e exporters, com um pipeline para logs e traces.
- endpoint - A comunicação com o ClickHouse é configurada por meio do parâmetro
endpoint. A string de conexãotcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1faz com que a comunicação ocorra via TCP. Se você preferir HTTP por motivos de alternância de tráfego, modifique essa string de conexão conforme descrito aqui. Os detalhes completos da conexão, incluindo a possibilidade de especificar nome de usuário e senha nessa string de conexão, estão descritos aqui.
- ttl - o valor aqui determina por quanto tempo os dados são retidos. Mais detalhes em “Gerenciando dados”. Isso deve ser especificado como uma unidade de tempo em horas, por exemplo, 72h. Desabilitamos o TTL no exemplo abaixo, já que nossos dados são de 2019 e serão removidos pelo ClickHouse imediatamente se forem inseridos.
- traces_table_name e logs_table_name - determinam o nome das tabelas de logs e traces.
- create_schema - determina se as tabelas são criadas com os schemas padrão na inicialização. O padrão é true para getting started. Você deve defini-lo como false e definir seu próprio schema.
- database - banco de dados de destino.
- retry_on_failure - configurações que determinam se batches com falha devem ser tentados novamente.
- batch - um batch processor garante que os eventos sejam enviados em batches. Recomendamos um valor de pelo menos 10.000 com um timeout de 5s (valores de até 100.000 podem ser usados se a memória permitir). O que for atingido primeiro iniciará um batch a ser enviado ao exporter. Reduzir esses valores resultará em uma pipeline de menor latência, com dados disponíveis para consulta mais cedo, ao custo de mais connections e batches enviados ao ClickHouse. Isso não é recomendado se você não estiver usando inserts assíncronos, pois pode causar problemas de partes em excesso no ClickHouse. Por outro lado, se você estiver usando inserts assíncronos, a disponibilidade desses dados para consulta também dependerá das configurações de insert assíncrono — embora os dados ainda sejam enviados do connector mais cedo. Consulte Batching para mais detalhes.
- sending_queue - controla o tamanho da fila de envio. Cada item na fila contém um batch. Se essa fila for excedida, por exemplo, porque o ClickHouse está inacessível, mas os eventos continuam chegando, os batches serão descartados.
telemetrygen:
Schema padrão
create_schema. Além disso, os nomes das tabelas de logs e traces podem ser alterados em relação aos padrões otel_logs e otel_traces por meio das configurações indicadas acima.
Nos schemas abaixo, assumimos que o TTL está habilitado para 72h.
otelcol-contrib v0.102.1):
- Por padrão, a tabela é particionada por data via
PARTITION BY toDate(Timestamp). Isso torna eficiente remover dados expirados. - O TTL é definido via
TTL toDateTime(Timestamp) + toIntervalDay(3)e corresponde ao valor definido na configuração do collector.ttl_only_drop_parts=1significa que apenas partes inteiras são removidas quando todas as linhas que elas contêm tiverem expirado. Isso é mais eficiente do que remover linhas dentro das partes, o que implica uma operação de delete custosa. Recomendamos que isso esteja sempre definido. Consulte Gerenciamento de dados com TTL para mais detalhes. - A tabela usa o motor clássico
MergeTree. Isso é recomendado para logs e traces e não deve precisar ser alterado. - A tabela é ordenada por
ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId). Isso significa que as consultas serão otimizadas para filtros emServiceName,SeverityText,TimestampeTraceId— colunas mais no início da lista serão filtradas mais rapidamente do que as posteriores; por exemplo, filtrar porServiceNameserá significativamente mais rápido do que filtrar porTraceId. Você deve modificar essa ordenação de acordo com os padrões de acesso esperados — consulte Escolhendo uma chave primária. - O esquema acima aplica
ZSTD(1)às colunas. Isso oferece a melhor compressão para logs. Você pode aumentar o nível de compressão do ZSTD (acima do padrão de 1) para obter uma compressão melhor, embora isso raramente seja benéfico. Aumentar esse valor acarretará maior sobrecarga de CPU no momento do insert (durante a compressão), embora a descompressão (e, portanto, as consultas) deva permanecer comparável. Consulte aqui para mais detalhes. A codificação delta adicional também é aplicada ao Timestamp com o objetivo de reduzir seu tamanho em disco. - Observe como
ResourceAttributes,LogAttributeseScopeAttributessão map. É importante entender as diferenças entre eles. Consulte “Usando map” para saber como acessar esses map e otimizar o acesso às chaves dentro deles. - A maioria dos outros tipos aqui, por exemplo
ServiceNamecomo LowCardinality, está otimizada. Observe queBody, que é JSON em nossos logs de exemplo, é armazenado como String. - Filtros de Bloom são aplicados às chaves e aos valores dos map, bem como à coluna
Body. Eles têm como objetivo melhorar os tempos de consulta para consultas que acessam essas colunas, mas normalmente não são necessários. Consulte Índices secundários/data skipping indices.
Otimizando inserções
Processamento em lotes
- (1) Se o nó que recebe os dados apresentar problemas, a consulta de inserção atingirá o tempo limite (ou retornará um erro mais específico) e não receberá uma confirmação.
- (2) Se os dados forem gravados pelo nó, mas a confirmação não puder ser devolvida ao remetente da consulta devido a interrupções de rede, o remetente receberá um timeout ou um erro de rede.
timeout do batch processor seja atingido, garantindo que a latência de ponta a ponta do pipeline permaneça baixa e que os lotes tenham tamanho consistente.
Use inserções assíncronas
timeout do batch processor expira. Isso pode causar problemas, e é aí que as inserções assíncronas se tornam necessárias. Esse cenário geralmente ocorre quando collectors na função de agent são configurados para enviar dados diretamente ao ClickHouse. Gateways, por atuarem como agregadores, podem amenizar esse problema — veja Escalabilidade com Gateways.
Se não for possível garantir batches grandes, você pode delegar o batching ao ClickHouse usando Asynchronous Inserts. Com inserções assíncronas, os dados são inseridos primeiro em um buffer e depois gravados no armazenamento do banco de dados posteriormente, ou seja, de forma assíncrona.
Com inserções assíncronas habilitadas, quando o ClickHouse ① recebe uma consulta de insert, os dados da consulta são ② gravados imediatamente em um buffer na memória. Quando ③ ocorre o próximo flush do buffer, os dados do buffer são ordenados e gravados como uma parte no armazenamento do banco de dados. Observe que os dados não podem ser consultados antes de serem gravados no armazenamento do banco de dados; o flush do buffer é configurável.
Para habilitar inserções assíncronas no collector, adicione async_insert=1 à connection string. Recomendamos que os usuários usem wait_for_async_insert=1 (o padrão) para ter garantias de entrega — veja aqui para mais detalhes.
Os dados de uma inserção assíncrona são inseridos assim que o buffer do ClickHouse é descarregado. Isso acontece quando async_insert_max_data_size é excedido ou após async_insert_busy_timeout_ms milissegundos desde a primeira consulta INSERT. Se async_insert_stale_timeout_ms estiver definido com um valor diferente de zero, os dados serão inseridos após async_insert_stale_timeout_ms milliseconds desde a última consulta. Você pode ajustar essas configurações para controlar a latência ponta a ponta do pipeline. Outras configurações que podem ser usadas para ajustar o flush do buffer estão documentadas aqui. Em geral, os valores padrão são adequados.
Considere inserções assíncronas adaptativasNos casos em que há poucos agents em uso, com baixo throughput, mas com requisitos rígidos de latência ponta a ponta, adaptive asynchronous inserts podem ser úteis. Em geral, elas não se aplicam a casos de uso de observabilidade com alto throughput, como os vistos com ClickHouse.
async_insert_deduplicate.
Os detalhes completos sobre como configurar esse recurso podem ser encontrados aqui, com uma análise mais aprofundada aqui.
Arquiteturas de implantação
Somente agents
- Escalabilidade das conexões - Cada agent estabelecerá uma conexão com o ClickHouse. Embora o ClickHouse seja capaz de manter centenas (senão milhares) de conexões de inserção concorrentes, isso acabará se tornando um fator limitante e tornará as inserções menos eficientes — ou seja, o ClickHouse usará mais recursos para manter essas conexões. O uso de gateways minimiza o número de conexões e torna as inserções mais eficientes.
- Processamento na borda - Quaisquer transformações ou processamentos de eventos precisam ser executados na borda ou no ClickHouse nessa arquitetura. Além de ser restritivo, isso pode significar visões materializadas complexas no ClickHouse ou deslocar uma carga computacional significativa para a borda — onde serviços críticos podem ser impactados e os recursos podem ser escassos.
- Lotes pequenos e latências - Os collectors como agents podem, individualmente, coletar pouquíssimos eventos. Isso normalmente significa que eles precisam ser configurados para fazer flush em um intervalo definido a fim de atender aos SLAs de entrega. Isso pode fazer com que o collector envie pequenos lotes ao ClickHouse. Embora seja uma desvantagem, isso pode ser mitigado com inserções assíncronas — consulte Otimizando inserções.