OpenTelemetry Collector v0.163.0: hash que colide, queue que trava e CPU que não desce de 100%
Em 14 de outubro de 2026 o time do OpenTelemetry Collector lançou a v1.69.0/v0.163.0. Não tem CVE, não tem anúncio de maturidade, não é o tipo de release que chama atenção num changelog corrido. Mas junta quatro correções que, isoladas, já justificariam um post — e juntas descrevem bem o tipo de bug que só aparece em produção, sob carga real, meses depois de alguém configurar block_on_overflow: true ou ativar um gate de semântica nova.
Se você roda Collector como gateway central de métricas, traces ou logs, vale ler as quatro antes de decidir quando atualizar.
O bug mais sério: hash que colide e mistura séries diferentes
O pacote pkg/xpdata/xhash serializa um pcommon.Map em bytes antes de gerar um hash — usado por componentes que precisam identificar “esse mapa de atributos é o mesmo de antes” sem comparar campo a campo. O problema, reportado em agosto: a serialização escrevia um prefixo de tipo antes de cada valor, mas sem comprimento ou terminador. Isso deixa a fronteira entre um item e o próximo ambígua.
O relator mostrou dois mapas diferentes que produzem exatamente os mesmos bytes — e portanto o mesmo hash:
{a: bytes[F4 62 F6 BB]}e{a: bytes[], b: bytes[BB]}{a: "x\xf4b\xf7y"}e{a: "x", b: "y"}
O segundo caso usa UTF-8 inválido, que o pmetric.ProtoUnmarshaler aceita sem reclamar — ou seja, essa string pode chegar via OTLP de qualquer client descuidado, não é só um exercício de laboratório.
Por que isso importa: a issue lista explicitamente quem depende do xhash para identidade ou agrupamento — cumulativetodeltaprocessor, groupbyattrsprocessor, logdedupprocessor, metricstarttimeprocessor, spanmetricsconnector, countconnector, sumconnector, signaltometricsconnector e o kafkaexporter. Se dois conjuntos de atributos colidem no mesmo hash, o cumulativetodeltaprocessor pode tratar duas séries distintas como a mesma série cumulativa, o logdedupprocessor pode deduplicar logs que não eram duplicados, e o kafkaexporter pode mandar dados pra partição errada. Nada disso gera erro — gera número errado no dashboard, sem alerta.
A correção (#16050, fechada em 30/09) prefixa cada chave, string e valor de bytes com o próprio comprimento antes de gerar o hash. Resolve a ambiguidade — mas como consequência direta, todo hash que o xhash produz muda de valor depois do upgrade. O time confirma que os hashes são só em memória, mas qualquer chave externa derivada deles (o exemplo citado é chave de partição no Kafka) muda uma vez, no momento do upgrade, e depois se estabiliza. Não há como “traduzir” hash antigo pra novo — trate como migração de esquema de chave, plana e sem janela de transição. Se você usa kafkaexporter com particionamento por atributos, espere um reshuffle de partição no deploy do upgrade.
Deadlock na sending queue com block_on_overflow: true
O segundo bug é mais direto: a fila de envio do pkg/exporterhelper podia travar inteiramente. A causa, segundo o relato original: uma variável de condição sensível a contexto guarda um contador de esperas separado de um canal de notificação de tamanho 1. Quando dois sinais de conclusão chegam próximos — o caminho multiDone.OnDone da fila persistente é o gatilho comum — o segundo Signal tenta escrever num canal já cheio enquanto ainda segura o mutex da fila. Resultado: ninguém mais consegue ler, escrever ou perguntar o tamanho da fila.
O caso relatado é concreto: uma fila persistente de métricas travou em 5.995.714 de 6.000.000 itens — a cinco mil itens do fim — e ficou lá. O endpoint de health continuava respondendo normalmente, o que torna esse tipo de travamento particularmente traiçoeiro: nenhum healthcheck convencional pega isso. Só reiniciar o processo destravava, e o problema voltava com a mesma configuração. Desligar block_on_overflow evitava o deadlock, mas trocava o problema por falhas de enqueue quando a fila enchia.
A correção é PR #16169, mesclada em 13/10, dias antes do release. Se você usa sending_queue.block_on_overflow: true em qualquer exporter — principalmente com fila persistente em disco — esse patch resolve um travamento real que não aparece em healthcheck nenhum. Monitore otelcol_exporter_queue_size versus otelcol_exporter_queue_capacity: uma fila parada perto da capacidade máxima, sem crescer nem esvaziar, era exatamente o sintoma.
Perda de dados ao dividir lotes grandes — ainda incompleto
Quando um item individual (um span, uma amostra de profile) é maior que sending_queue.batch.max_size, a função de split do exporterhelper retornava cedo — e descartava não só o item grande, mas tudo que estava na fila atrás dele. O contador de itens descartados também não refletia o tamanho real da perda.
A correção já existia para logs desde antes deste release; o v0.163.0 estende o mesmo comportamento pra traces (#16088): agora só o span que excede o limite é descartado, o resto do lote segue normalmente. Para profiles, a correção ainda não saiu — elementos de profile referenciam um ProfilesDictionary compartilhado e não são autocontidos, então a abordagem usada em logs não se aplica direto, e há sugestão de remover o suporte a split pra profiles em vez de replicar a lógica. Para métricas, o comportamento antigo (perda de lote inteiro) continua, sem PR vinculado até este release.
Se você configura batch.max_size explicitamente, hoje a proteção contra perda em cascata cobre só logs e traces. Enquanto isso não muda nas outras duas, a mitigação é dimensionar max_size com folga generosa acima do maior item esperado, em vez de confiar que um item fora do limite custa só ele mesmo.
O(n²) que derruba o processo com receiver.postgresql.useOTelSemconv
O quarto bug é o mais fácil de reproduzir e o mais brutal no sintoma. O código gerado pelo cmd/mdatagen para recordDataPoint comparava cada ponto novo contra todos os pontos já existentes na mesma métrica antes de decidir se deveria mesclar ou anexar — um scan O(n) por chamada, ou seja, O(n²) pra registrar n pontos num scrape só.
O caso que expôs o problema: receiver/postgresql com o feature gate receiver.postgresql.useOTelSemconv ativado e 30 bancos configurados. Esse modo coloca o servidor inteiro num único resource, então uma métrica acumulava cerca de 28.410 data points num scrape só. O relator mediu um core de CPU travado em 100% em cerca de 90 segundos, o segundo scrape nunca terminando, e o processo ignorando SIGTERM até o systemd matá-lo na força. O profile de CPU mostrou o tempo todo gasto em pcommon.Map.Equal dentro do loop gerado — bookkeeping de métrica, não query lenta no Postgres.
A correção troca o scan linear por um lookup O(1) em mapa, indexado por atributos e timestamps de início/fim. Nos testes do relator, CPU caiu de 100% fixo para uma faixa estável de 8–11%, com scrapes completando nos 30 segundos esperados.
Se você usa receiver/postgresql com useOTelSemconv habilitado e múltiplos bancos no mesmo servidor, isso é motivo suficiente pra atualizar sem esperar o ciclo normal — é o tipo de bug que só aparece quando o número de bancos monitorados cresce, não com um banco só em teste. De forma geral, qualquer receiver ou processor gerado por mdatagen que acumula muitos data points sob um único resource se beneficia do mesmo fix, mesmo fora do Postgres.
Resumo pra decidir quando atualizar
| Bug | Afeta quem | Urgência |
|---|---|---|
Colisão de hash no xhash |
kafkaexporter, dedup/agregação por atributos |
Alta — dados podem estar sendo misturados silenciosamente agora |
| Deadlock na sending queue | Qualquer exporter com block_on_overflow: true |
Alta — travamento sem sinal claro no health |
| Split de lote perde dados extras | Exporters com batch.max_size configurado, em traces |
Média — mitigado parcialmente nesta versão |
O(n²) no mdatagen |
Receivers com muitos data points por resource (ex.: postgresql + useOTelSemconv) | Alta se o cenário se aplica, irrelevante se não |
Nenhum desses quatro tem CVE porque nenhum é vulnerabilidade de segurança — são bugs de correção de dados e de disponibilidade, categoria que costuma passar batida em changelog porque não aciona scanner nenhum. Vale testar a v0.163.0 em staging com atenção particular a qualquer pipeline que derive chave externa de hash, e subir pra produção sem tratar isso como “patch de rotina que pode esperar”.