Introdução
Conceitos do ClickHouse
| Tipo de dado | Tabela |
|---|---|
| Logs | otel_logs |
| Traces | otel_traces |
| Métricas (gauges) | otel_metrics_gauge |
| Métricas (somas) | otel_metrics_sum |
| Métricas (histograma) | otel_metrics_histogram |
| Métricas (histogramas exponenciais) | otel_metrics_exponentialhistogram |
| Métricas (resumo) | otel_metrics_summary |
| Sessões | hyperdx_sessions |
default é usado — isso pode ser alterado no OpenTelemetry Collector.
No mínimo, você deve entender os seguintes fundamentos do ClickHouse:
| Conceito | Descrição |
|---|---|
| Tabelas | Como as fontes de dados no ClickStack correspondem às tabelas subjacentes do ClickHouse. As tabelas no ClickHouse usam principalmente o motor MergeTree. |
| Partes | Como os dados são gravados em partes imutáveis e mesclados ao longo do tempo. |
| Partições | As partições agrupam as partes de dados de uma tabela em unidades lógicas organizadas. Essas unidades são mais fáceis de gerenciar, consultar e otimizar. |
| Mesclagens | O processo interno que mescla partes para reduzir o número de partes a serem consultadas. Essencial para manter o desempenho das consultas. |
| Grânulos | A menor unidade de dados que o ClickHouse lê e descarta durante a execução da consulta. |
| Chaves primárias (de ordenação) | Como a chave ORDER BY determina o layout dos dados em disco, a compressão e a eliminação de dados durante a consulta. |
- Criando tabelas no ClickHouse - Uma introdução simples às tabelas.
- partes
- partições
- mesclagens
- Primary keys/indexes
- Como o ClickHouse armazena dados: parts e grânulos - Guia mais avançado sobre como os dados são estruturados e consultados no ClickHouse, cobrindo grânulos e chaves primárias em detalhes.
- MergeTree- Guia avançado de referência do MergeTree útil para comandos e detalhes internos.
Otimização 1. Materialize os atributos consultados com frequência
LogAttributes, ScopeAttributes e ResourceAttributes e promovê-los a colunas de nível superior por meio de colunas materializadas.
Só essa otimização muitas vezes já é suficiente para escalar implantações do ClickStack para dezenas de terabytes por dia e deve ser aplicada antes de considerar técnicas de ajuste mais avançadas.
Por que materializar atributos
Map(String, String). Embora isso ofereça flexibilidade, consultar subchaves de um Map tem uma implicação importante de desempenho.
Ao consultar uma única chave de uma coluna Map, o ClickHouse precisa ler a coluna Map inteira do disco. Se o Map contiver muitas chaves, isso resulta em I/O desnecessária e consultas mais lentas em comparação com a leitura de uma coluna dedicada.
Materializar atributos acessados com frequência evita essa sobrecarga ao extrair o valor no momento da inserção e armazená-lo como uma coluna propriamente dita.
Colunas materializadas:
- São calculadas automaticamente durante as inserções
- Não podem ser definidas explicitamente em instruções INSERT
- Oferecem suporte a qualquer expressão do ClickHouse
- Permitem conversão de tipo de String para tipos numéricos ou de data mais eficientes
- Permitem o uso de skip indexes e chave primária
- Reduzem leituras do disco ao evitar o acesso completo ao Map
O ClickStack detecta automaticamente colunas materializadas extraídas de Maps e as usa de forma transparente durante a execução da consulta, mesmo quando os usuários continuam consultando o caminho original do atributo.
Exemplo
ResourceAttributes:
ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c":
Isso gera um predicado SQL semelhante a:
Map, o ClickHouse precisa ler a coluna ResourceAttributes inteira para cada linha correspondente — o que pode ser muito grande se o Map contiver muitas chaves.
Se esse atributo for consultado com frequência, ele deverá ser materializado como uma coluna de nível superior.
Para extrair o nome do pod do Kubernetes no momento da inserção, adicione uma coluna materializada:
PodName.
Agora, os usuários podem consultar nomes de pod com eficiência usando a sintaxe Lucene, por exemplo: PodName:"checkout-675775c4cc-f2p9c"
Para dados inseridos recentemente, isso elimina totalmente o acesso ao map e reduz significativamente a E/S.
No entanto, mesmo que os usuários continuem consultando o caminho original do atributo, por exemplo, ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c", o ClickStack reescreverá automaticamente a consulta internamente para usar a coluna materializada PodName, ou seja, usando o predicado:
Por padrão, as colunas materializadas são excluídas de
consultas SELECT *. Isso preserva o invariante de que os resultados da consulta sempre podem ser reinseridos na tabela.Materialização de dados históricos
system.mutations, por exemplo.
is_done = 1 para a mutação correspondente.
Otimização 2. Adicionando skip indexes
- Filtragem de strings com alta cardinalidade, como TraceId, identificadores de sessão, chaves de atributos ou valores
- Filtragem de subchaves de map acelerada por text indexes nas colunas
*AttributeItems - Filtragem por faixa numérica, como a duração de um span
text(tokenizer = 'array') em vez de filtros de Bloom em toda a tabela, e adiciona um índice text(tokenizer = 'splitByNonAlpha') em lower(Body) para busca de texto completo. Consulte “Tables and schemas used by ClickStack” para ver o DDL completo.
Filtros de Bloom
PodName:"checkout-675775c4cc-f2p9c".
Os filtros de Bloom são mais eficazes quando a distribuição dos valores faz com que um determinado valor apareça em um número relativamente pequeno de partes. Isso costuma ocorrer naturalmente em cargas de trabalho de observabilidade, em que metadados como nomes de pods do Kubernetes, IDs de trace ou identificadores de sessão estão correlacionados com o tempo e, portanto, agrupados pela chave de ordenação da tabela.
Como acontece com todos os skip indexes, os filtros de Bloom devem ser adicionados de forma seletiva e validados com base em padrões reais de consulta para garantir que ofereçam um benefício mensurável - consulte “Avaliando a eficácia do skip index.”
Índices de texto
WHERE. Índices de texto são índices invertidos que mapeiam tokens para offsets exatos dentro de uma part. Como avaliam offsets em vez de grânulos e não geram falsos positivos, normalmente conseguem responder à condição WHERE sem carregar a coluna subjacente. Essa é uma otimização conhecida como leitura direta. Como o carregamento dos dados costuma ser o principal fator no tempo de execução da consulta, a leitura direta pode reduzir significativamente a latência da consulta.
Além disso, os próprios índices de texto podem ser consultados, viabilizando autocompletar e outros recursos de introspecção no ClickStack.
Dois tokenizadores cobrem a maioria dos padrões do ClickStack:
| Tokenizador | Usado para | Coluna típica |
|---|---|---|
array | Indexação de elementos Array(String) como tokens inteiros | mapKeys(...), *AttributeItems |
splitByNonAlpha | Busca de texto completo em nível de palavra em strings de texto corrido | Body, lower(Body), SpanName |
Tokenizador array para Map e colunas de array
mapKeys e os arrays de itens materializados com
o tokenizador array:
splitByNonAlpha para corpos de logs
Body se beneficia de um índice de texto splitByNonAlpha.
O ClickStack define esse índice em lower(Body) para que buscas do Lucene que não diferenciam maiúsculas de minúsculas
possam usá-lo:
text(tokenizer = 'splitByNonAlpha') em
lower(Body), ele reescreve consultas Lucene com coluna implícita, como error ou
"connection refused", para hasAllTokens(lower(Body), lower(...)), que o
índice consegue resolver sem ler toda a coluna Body. Para a maioria dos
workloads de logs de observabilidade, este é o maior ganho de desempenho de
filtragem disponível.
Índices de texto vs
tokenbf_v1O tipo de índice tokenbf_v1 mais antigo (ainda usado no esquema padrão de traces para
lower(SpanName)) é funcionalmente semelhante, mas foi descontinuado no ClickHouse 26.2
e versões posteriores. Novos índices de busca de texto completo devem usar text(tokenizer = ...).Índices de texto no esquema padrão de logs
otel_logs, sincronizado do upstream, já vem com todos os índices de texto discutidos acima: text(tokenizer = 'array') em TraceId, em cada array mapKeys(...) e *AttributeItems, e text(tokenizer = 'splitByNonAlpha') em lower(Body) para busca de texto completo. Para ver a DDL canônica, consulte “Tabelas e esquemas usados pelo ClickStack”; o mesmo esquema é reproduzido abaixo.
Índices min-max
SpanAttributes:
Materializar skip index
Materialização de skip indexesMaterializar um skip index geralmente é uma operação leve e segura, especialmente no caso de índices minmax. Para índices de filtro de Bloom em datasets grandes, os usuários podem preferir materializar por partição para ter mais controle sobre o uso de recursos, por exemplo:
is_done = 1 para a mutação correspondente.
Quando estiver concluída, confirme que os dados do índice foram criados:
0.01 para 0.05 produz um índice menor, que é avaliado mais rapidamente, ao custo de uma poda menos agressiva. Embora menos grânulos possam ser ignorados, a latência geral da consulta pode melhorar devido à avaliação mais rápida do índice.
Ajustar os parâmetros do filtro de Bloom é, portanto, uma otimização que depende da carga de trabalho e deve ser validada com padrões reais de consulta e volumes de dados semelhantes aos de produção.
Para mais detalhes sobre skip indexes, consulte o guia “Entendendo os data skipping indexes do ClickHouse.”
Avaliando a eficácia dos skip indexes
EXPLAIN indexes = 1, que mostra quantas partes e grânulos são eliminados em cada etapa do planejamento da consulta. Na maioria dos casos, o ideal é ver uma grande redução no número de grânulos na etapa Skip, de preferência depois que a chave primária já tiver reduzido o espaço de busca. Os skip indexes são avaliados após o pruning de partições e o pruning pela chave primária, portanto seu impacto é medido melhor em relação às partes e aos grânulos restantes.
EXPLAIN confirma se o pruning ocorre, mas não garante, por si só, um ganho líquido de desempenho. Os skip indexes têm um custo de avaliação, especialmente se o índice for grande. Sempre faça benchmark das consultas antes e depois de adicionar e materializar um índice para confirmar melhorias reais de desempenho.
Por exemplo, considere o skip index padrão com filtro de Bloom para TraceId incluído no esquema padrão de Traces:
EXPLAIN indexes = 1 para ver a eficácia dele em uma consulta seletiva:
FORMAT Null para evitar a sobrecarga da serialização dos resultados e desative o cache de condições de consulta para manter as execuções repetíveis:
use_query_condition_cache garante que os resultados não sejam afetados por decisões de filtragem armazenadas em cache, e definir use_skip_indexes = 0 fornece uma referência limpa para comparação. Se a poda for eficaz e o custo de avaliação do índice for baixo, a consulta indexada deverá ser significativamente mais rápida, como no exemplo acima.
Quando adicionar skip indexes
IN que os filtros de Bloom, e além disso habilitam predicados baseados em tokens (hasToken, hasAllTokens, has) usados pela busca de texto completo e pela otimização leitura direta de Map. Em clusters mais antigos que ainda não oferecem suporte a índice de texto, os filtros de Bloom continuam sendo uma escolha sólida.
Os filtros de Bloom são mais eficazes em colunas de string com alta cardinalidade, nas quais cada valor aparece com frequência relativamente baixa, ou seja, a maioria das partes e dos grânulos não contém o valor pesquisado. Como regra prática, os filtros de Bloom tendem a ser mais promissores quando a coluna tem pelo menos 10.000 valores distintos e, muitas vezes, apresentam o melhor desempenho com mais de 100.000 valores distintos. Eles também são mais eficazes quando os valores correspondentes estão agrupados em um pequeno número de partes sequenciais, o que normalmente acontece quando a coluna está correlacionada com a chave de ordenação. Novamente, os resultados podem variar — nada substitui testes em condições reais.
Otimização 3. Leitura direta de Map
LogAttributes['k8s.pod.name'] = 'checkout', o ClickHouse precisa ler do disco toda a coluna Map LogAttributes e
desempacotar cada linha para avaliar o predicado. Materializar atributos consultados com frequência
resolve isso para chaves que você conhece de antemão, mas não escala para
atributos arbitrários pelos quais os usuários filtram ad hoc.
Mesmo que um esquema tenha índices em mapKeys e mapValues, esses índices podem informar se uma linha tem uma determinada chave e se ela tem um determinado valor, mas não se a chave e o valor pertencem à mesma entrada. Em outras palavras, mapKeys responde a mapContainsKey(ResourceAttributes, 'foo') e mapValues responde a mapContainsValue(ResourceAttributes, 'bar'), mas nenhum dos dois responde a ResourceAttributes['foo'] = 'bar'.
Ao concatenar as chaves e os valores em uma única coluna Array(String), a
otimização de leitura direta de Map permite que ResourceAttributes['foo'] = 'bar' seja
avaliado sem carregar o map subjacente. Maps costumam ser grandes e aumentam
de tamanho à medida que o volume cresce. Combinados com uma reescrita de consulta
no nível da aplicação, os filtros de igualdade em qualquer subchave de Map passam a ser uma única chamada has(...)
suportada por esse índice, sem desserialização de Map no momento da consulta. Além disso,
o único custo de armazenamento é o do índice de texto, já que a coluna subjacente é
uma coluna ALIAS e não é armazenada.
Essa otimização é automática. O ClickStack inclui as colunas e os
índices necessários nas tabelas padrão de logs e traces, e reescreve em tempo de
execução os filtros de subscrito de Map quando o servidor ClickHouse conectado oferece suporte à primitiva
subjacente. Se o seu esquema não contiver essas colunas, ou se você tiver
colunas Map adicionais que queira acelerar além das padrão, continue lendo para
ativá-las.
Esquema
Map que você deseja acelerar, o ClickStack define uma
coluna Array(String) ALIAS que combina cada chave e valor com =:
text(tokenizer = 'array') na
coluna ALIAS armazena um token por par key=value, que o ClickHouse usa
para descartar grânulos sem acessar o Map de origem:
| Tabela | colunas ALIAS | Índices de texto |
|---|---|---|
otel_logs | ResourceAttributeItems, ScopeAttributeItems, LogAttributeItems | idx_res_attr_items, idx_scope_attr_items, idx_log_attr_items |
otel_traces | ResourceAttributeItems, SpanAttributeItems | idx_res_attr_items, idx_span_attr_items |
Reescrita de consulta
LogAttributeItems, elimina
linhas inteiras que não contêm o token key=value e nunca desserializa o
map de origem LogAttributes para linhas sem correspondência. Para workloads de alta cardinalidade de
observabilidade, isso normalmente proporciona uma redução de uma ordem de magnitude
na E/S em comparação com o acesso ao Map por subscrito.
A reescrita acontece automaticamente — consultas salvas, dashboards e alertas que
fazem referência a LogAttributes['key'] se beneficiam do ganho de desempenho sem nenhuma alteração.
Requisitos de versão do ClickHouse
SELECT version(), armazenada em cache por conexão) e só
emite a forma reescrita quando o servidor está nesse limite ou acima dele. Servidores
mais antigos voltam automaticamente para a forma original com subscrito de Map.
| Branch do ClickHouse | Versão mínima |
|---|---|
| 26.2 | 26.2.19.43 |
| 26.3 | 26.3.12.3 |
| 26.4 | 26.4.3.37 |
| 26.5+ | Todas as versões |
Por que ALIAS, e não MATERIALIZEDO array
items é uma visão dos dados que já estão na coluna Map.
Armazená-lo duas vezes — uma no Map e outra no array — dobraria a E/S de gravação
sem viabilizar novos padrões de consulta. O índice de texto na coluna ALIAS é
criado no momento da inserção a partir dos mesmos dados de origem, portanto a
otimização adiciona ao disco apenas o espaço ocupado pelo índice.Otimização 4. Modificando a chave primária
Uma observação sobre a terminologiaAo longo deste documento, o termo “chave de ordenação” é usado de forma intercambiável com “chave primária”. Em termos estritos, eles diferem no ClickHouse, mas, no ClickStack, normalmente se referem às mesmas colunas especificadas na cláusula
ORDER BY da tabela. Para mais detalhes, consulte a documentação do ClickHouse sobre como escolher uma chave primária diferente da chave de ordenação.- Logs (
otel_logs) -(toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp) - Traces (
otel_traces) -(ServiceName, SpanName, toDateTime(Timestamp))
Escolhendo uma chave primária
Modificando a chave primária padrãoAs chaves primárias padrão são suficientes na maioria dos casos. As alterações devem ser feitas com cautela e somente com uma compreensão clara dos padrões de consulta. Modificar uma chave primária pode degradar o desempenho de outros fluxos de trabalho, portanto, é essencial testar.
- Selecione colunas que estejam alinhadas com seus filtros e padrões de acesso mais comuns. Se você normalmente inicia investigações de observabilidade filtrando por uma coluna específica, por exemplo, o nome do pod, essa coluna será usada com frequência em cláusulas
WHERE. Priorize incluí-las na sua chave em vez daquelas usadas com menos frequência. - Prefira colunas que ajudem a excluir uma grande porcentagem do total de linhas quando filtradas, reduzindo assim a quantidade de dados que precisa ser lida. Nomes de serviços e códigos de status costumam ser bons candidatos — neste último caso, apenas se você filtrar por valores que excluam a maioria das linhas; por exemplo, filtrar por códigos 200, na maioria dos sistemas, corresponderá à maior parte das linhas, em comparação com erros 500, que corresponderão a um pequeno subconjunto.
- Prefira colunas com alta correlação com outras colunas da tabela. Isso ajuda a garantir que esses valores também sejam armazenados de forma contígua, melhorando a compressão.
- Operações
GROUP BY(agregações para gráficos) eORDER BY(ordenação) em colunas da chave de ordenação podem ser mais eficientes em termos de memória.
Alterando a chave primária
SeverityText antes de ServiceName.
Criar nova tabela
Chave de ordenação vs chave primáriaObserve que, no exemplo acima, é necessário especificar
PRIMARY KEY e ORDER BY.
No ClickStack, elas quase sempre são iguais.
O ORDER BY controla o layout físico dos dados, enquanto a PRIMARY KEY define o índice esparso.
Em casos raros de workloads muito grandes, elas podem ser diferentes, mas a maioria dos usuários deve mantê-las alinhadas.Fazer EXCHANGE e excluir a tabela
A instruçãoEXCHANGE é usada para trocar os nomes das tabelas de forma atômica. A tabela temporária (agora a antiga tabela padrão) pode então ser excluída.Fazer backfill dos dados existentes em uma nova tabela raramente vale a pena em escala. O custo de compute e de E/S geralmente é alto e não justifica os ganhos de desempenho. Em vez disso, deixe os dados mais antigos expirarem via TTL, enquanto os dados mais novos se beneficiam da chave aprimorada.
SeverityText como a primeira coluna da chave primária é usado abaixo. Nesse caso, uma tabela é criada para novos dados, mantendo a tabela antiga para análise histórica.
Criar nova tabela
Crie a nova tabela com a chave primária desejada. Observe o sufixo_23_01_2025 — adapte-o para a data atual. Por exemplo:Criar uma tabela Merge
O motor Merge (não confundir com MergeTree) não armazena dados por conta própria, mas permite ler de qualquer quantidade de outras tabelas simultaneamente.currentDatabase() pressupõe que o comando seja executado no banco de dados correto. Caso contrário, especifique explicitamente o nome do banco de dados.otel_logs.Atualizar a ClickStack UI para ler da tabela merge
Configure a ClickStack UI para usarotel_logs_merge como tabela da fonte de dados de logs.Neste ponto, as gravações continuam em otel_logs com a chave primária original, enquanto as leituras usam a tabela merge. Não há nenhuma mudança visível para os usuários nem impacto na ingestão.Trocar as tabelas
Agora, uma instruçãoEXCHANGE é usada para trocar atomicamente os nomes das tabelas otel_logs e otel_logs_23_01_2025.otel_logs, com a chave primária atualizada. Os dados existentes permanecem em otel_logs_23_01_2025 e ainda estão acessíveis por meio da tabela merge. O sufixo indica a data em que a alteração foi aplicada e representa o timestamp mais recente contido nessa tabela.Esse processo permite alterar a chave primária sem interrupção da ingestão e sem impacto visível para o usuário.SeverityNumber deve fazer parte da chave primária, em vez de SeverityText. O processo a seguir pode ser adaptado quantas vezes forem necessárias alterações na chave primária.
Criar nova tabela
Crie a nova tabela com a chave primária desejada. No exemplo abaixo,30_01_2025 é usado como sufixo para indicar a data da tabela. Por exemplo:Trocar as tabelas
Agora, uma instruçãoEXCHANGE é usada para trocar atomicamente os nomes das tabelas otel_logs e otel_logs_30_01_2025.otel_logs, com a chave primária atualizada. Os dados antigos permanecem em otel_logs_30_01_2025, acessíveis por meio da tabela merge.Tabelas redundantesSe houver políticas de TTL em vigor, o que é recomendado, as tabelas com chaves primárias antigas que não estiverem mais recebendo gravações serão esvaziadas gradualmente à medida que os dados expirarem. Elas devem ser monitoradas e limpas periodicamente quando não contiverem mais dados. No momento, esse processo de limpeza é manual.
Aceleração da busca de linhas com colunas de bloco
(_block_number, _block_offset) que a identifica de forma única dentro de uma
part. Quando você clica em uma linha de log na ClickStack UI para abrir o painel de detalhes, o ClickStack
executa uma consulta complementar para buscar essa linha específica. Sem colunas de bloco, a
cláusula WHERE da linha precisa incluir colunas suficientes — normalmente a chave primária
mais Body e SeverityText — para distinguir a linha. Com colunas de bloco,
a chave primária mais _block_number mais _block_offset é suficiente. Colunas
grandes como Body nunca são lidas nessa busca, o que acelera a consulta.
O ClickStack detecta essa configuração a partir da instrução CREATE da tabela e gera
automaticamente a cláusula WHERE mais enxuta quando ambas as colunas estão habilitadas. Nenhuma
alteração na configuração da aplicação é necessária.
Para ativar a otimização em uma tabela existente de logs ou traces:
ALTER. As partes existentes continuam
a usar a consulta antiga por linha até serem reescritas por uma operação de merge.
Otimização 5. Aproveitando visões materializadas
Otimização 6. Explorando projeções
ORDER BY da tabela base, permitindo que o ClickHouse descarte dados com mais eficiência para padrões de acesso que não se alinham com a ordenação original.
As visões materializadas podem alcançar um efeito semelhante ao gravar explicitamente linhas em uma tabela de destino separada com uma chave de ordenação diferente. A principal diferença é que as projeções são mantidas automática e transparentemente pelo ClickHouse, enquanto as visões materializadas são tabelas explícitas que precisam ser registradas e selecionadas intencionalmente pelo ClickStack.
Quando uma consulta tem como destino a tabela base, o ClickHouse avalia o layout base e todas as projeções disponíveis, inspeciona seus índices primários e seleciona o layout capaz de produzir o resultado correto lendo o menor número de grânulos. Essa decisão é tomada automaticamente pelo analisador de consultas.
No ClickStack, portanto, as projeções são mais adequadas para reordenação pura de dados, em que:
- Os padrões de acesso são fundamentalmente diferentes da chave primária padrão
- É impraticável cobrir todos os fluxos de trabalho com uma única chave de ordenação
- Você quer que o ClickHouse escolha de forma transparente o layout físico ideal
Exemplo de projeções
Use caracteres curingaNo exemplo de projeção acima, é usado um caractere curinga (
SELECT *). Embora selecionar um subconjunto de colunas possa reduzir a sobrecarga de gravação, isso também limita quando a projeção pode ser usada, já que apenas consultas que possam ser totalmente atendidas por essas colunas são elegíveis. No ClickStack, isso costuma restringir o uso de projeções a casos bem específicos. Por esse motivo, em geral, recomenda-se usar um caractere curinga para maximizar a aplicabilidade.Materializar uma projeção pode levar bastante tempo e consumir muitos recursos. Como os dados de observabilidade normalmente expiram por meio de TTL, isso só deve ser feito quando for absolutamente necessário. Na maioria dos casos, basta deixar que a projeção se aplique apenas aos dados recém-ingestados, permitindo otimizar os intervalos de tempo consultados com mais frequência, como as últimas 24 horas.
SELECT *) e os filtros da consulta estão bem alinhados com o ORDER BY da projeção.
Consultas que filtram por TraceId (especialmente com igualdade) e incluem um intervalo de tempo se beneficiariam da projeção acima. Por exemplo:
TraceId, ou que filtram principalmente por outras dimensões que não vêm primeiro na chave de ordenação da projeção, normalmente não se beneficiam disso (e podem acabar lendo pelo layout base).
As projeções também podem armazenar agregações (de forma semelhante às visões materializadas). No ClickStack, agregações baseadas em projeções geralmente não são recomendadas, porque a seleção depende do analisador do ClickHouse, e o uso pode ser mais difícil de controlar e compreender. Em vez disso, prefira visões materializadas explícitas, que o ClickStack possa registrar e selecionar intencionalmente na camada de aplicação.
TraceId específico).
Custos e orientações
- Sobrecarga de inserção: Uma projeção
SELECT *com uma chave de ordenação diferente efetivamente grava os dados duas vezes, o que aumenta a E/S de gravação e pode exigir CPU adicional e maior taxa de transferência em disco para sustentar a ingestão. - Use com parcimônia: As projeções são mais indicadas para padrões de acesso realmente diversos, em que uma segunda ordenação física proporciona uma poda significativa para uma grande parcela das consultas, por exemplo, quando duas equipes consultam o mesmo conjunto de dados de maneiras fundamentalmente diferentes.
- Valide com benchmarks: Como em qualquer ajuste, compare a latência real das consultas e o uso de recursos antes e depois de adicionar e materializar uma projeção.
Projeções leves com _part_offset
As projeções leves estão em Beta para o ClickStackProjeções leves baseadas em
_part_offset não são recomendadas para workloads do ClickStack. Embora reduzam o armazenamento e a E/S de gravação, elas podem introduzir mais acessos aleatórios no momento da consulta, e seu comportamento em produção na escala da observabilidade ainda está sendo avaliado. Essa recomendação pode mudar à medida que o recurso amadurece e coletamos mais dados operacionais._part_offset para a tabela base, em vez de duplicar linhas completas. Isso pode reduzir bastante a sobrecarga de armazenamento, e melhorias recentes permitem poda no nível de grânulo, fazendo com que elas se comportem mais como índices secundários de fato. Veja:
Alternativas
- Configurar o OpenTelemetry Collector para gravar em duas tabelas com chaves
ORDER BYdiferentes e criar fontes separadas no ClickStack para cada tabela. - Criar uma visão materializada como um pipeline de cópia, ou seja, anexar uma visão materializada à tabela principal que selecione linhas brutas para uma tabela secundária com uma chave de ordenação diferente (um padrão de desnormalização ou roteamento). Crie uma fonte para essa tabela de destino. Exemplos podem ser encontrados aqui.