AdTechSep 10, 202610 min de leitura

A medição de anúncios perde eventos no pico: costuras, chaves duplicadas e o join com o bidder

Pipelines de eventosMedição de anúnciosAtribuiçãoIntegridade de dados
Erro ao carregar a imagem

O seu pipeline de medição perde eventos no pico de tráfego e os números de atribuição nunca batem com os logs do bidder. São duas falhas por trás de um sintoma: eventos que nunca chegaram, em uma costura que ninguém contou, e eventos que chegaram e foram contados sob uma regra diferente. É assim que as costuras, as chaves, a janela de atraso e a ponte de reconciliação são construídas.

Um pipeline de eventos que perde eventos no pico de tráfego e nunca reconcilia com os logs do bidder pode ser duas falhas sob um sintoma. Uma é de transporte: eventos criados e que nunca chegaram, em uma costura que ninguém contou. A outra é definicional: eventos que chegaram e foram contados sob uma regra diferente da do registro do leilão.

O enquadramento habitual - o pipeline descarta eventos, então reconstrua o pipeline - conserta no máximo um deles. O que vem a seguir separa os dois e diz o que uma reconciliação produz, que não é igualdade. Os padrões citados abaixo são os do Kafka no lado do transporte e os do ClickHouse no lado do armazenamento.

A resposta curta é estrutural: um identificador por leilão carregado de ponta a ponta, um contador dos dois lados de cada salto, e uma reconciliação sobre uma janela que já fechou. O que a amBrain pode sustentar publicamente é o trabalho em AdTech: desenvolvimento de DSP, plataformas de real-time bidding e engenharia de ad exchange. Em qualquer stack de RTB, esse é o lado de onde vêm os registros de lance, vitória e impressão. O pipeline abaixo é descrito a partir da mecânica do problema, não de um caso nosso.

Uma consulta distingue um evento perdido de um atrasado

A primeira falha é a perda: o evento foi criado e nunca chegou, em um salto específico, por um motivo específico. Um beacon que nunca saiu da página, um edge reiniciado no meio do deploy, um buffer de producer que encheu, um consumer que fez commit do offset antes de processar.

A segunda não é uma falha, de forma alguma. O lado da medição conta um evento iniciado no cliente; o bidder registra um resultado de leilão do lado do servidor. Um é uma adjudicação, o outro é uma observação do que aconteceu com ela. As diretrizes de medição do MRC tratam pre-fetch, pre-render e auto-refresh como coisas separadas a detectar e divulgar: uma regra de contagem, não uma falha de transporte. Distinguir os dois custa uma consulta e alguma paciência.

  • Reexecute a mesma janela de event time uma hora, seis horas e um dia inteiro depois do período que ela cobre
  • Um déficit que encolhe a cada execução significa que os eventos estavam atrasados, não perdidos, e o transporte está em ordem
  • Conte chaves de deduplicação distintas, não linhas: o transporte at-least-once garante reentregas, e uma curva baseada em linhas esconde uma contagem a mais
  • Um déficit que não se move significa que os eventos sumiram, e a pergunta é qual costura
  • O teste precisa de um timestamp de event time, de um identificador que sobreviva às retentativas e de retenção longa o bastante para reexecutar a janela
  • Publique a curva de estabilização como um gráfico, por tipo de evento, ao lado do número que ela explica

Enquanto essa curva não existir, os dois lados da discussão são opiniões. Depois, o formato dela decide qual metade deste artigo se aplica, e as metades não são exclusivas.

Eventos somem em costuras nomeadas, e uma costura não contada não pode ser culpada

Há sete lugares onde um evento de anúncio é criado e depois deixa de existir silenciosamente, mais um ajuste que parece uma garantia e não é.

  • Coleta no cliente: o beacon dispara mas o documento é descarregado antes, ou o criativo foi cacheado, sofreu pre-fetch ou auto-refresh e conta o que o registro do leilão não conta
  • Ingestão no edge: limites de conexão, esgotamento de keep-alive, restarts durante deploys e a variante perigosa - sucesso devolvido antes de o evento ser durável
  • Buffer do producer: o cliente bloqueia por um tempo limitado e depois lança erro, e o código que captura esse erro e não conta nada é onde o dado morre
  • Durabilidade no broker: sem exigir confirmação, nada garante que o registro chegou; só com o leader, ele se perde se esse leader cair antes de os followers replicarem
  • A confirmação de todas as réplicas não é durabilidade: ela espera pelo conjunto in-sync atual, cujo tamanho mínimo é um por padrão, então um pico que deixa os followers para trás faz commit apenas no leader
  • Consumer: fazer commit do offset antes de processar é at-most-once, e isso é um padrão, não uma decisão - o cliente faz commit por timer a menos que isso tenha sido desligado
  • Estouro de retenção: um consumer que fica para trás além da janela de retenção encontra seu próximo offset apagado, e a política de reset padrão o joga para a ponta do log
  • Carga no banco colunar: inserts fire-and-forget confirmam assim que bufferizados, e views materializadas dependentes deduplicam por um ajuste separado - onde a tabela bruta e o relatório se separam

A regra é uma regra de relatório, não de engenharia: uma perda é atribuída a uma costura nomeada, ou não é atribuída de forma alguma. Dois alarmes mantêm as costuras silenciosas visíveis - o lag do consumer medido em tempo contra a retenção, e uma política de reset que falha em vez de pular.

A deduplicação precisa de uma chave que exista antes da primeira retentativa

Uma chave de deduplicação não é uma chave primária conveniente escolhida no destino. Ela é atribuída a montante de qualquer retentativa, no momento do leilão ou quando o evento é criado, e o receptor nunca inventa uma. Timestamps de chegada ficam de fora: uma retentativa carrega um novo horário de chegada e uma nova chave.

A chave tem que ser idêntica em toda tentativa, o que decide as partes dela: a exchange ou o seat, o identificador de leilão, o identificador de impressão e o tipo de evento. Particione o tópico por essa chave, para que as retentativas caiam juntas e a ordenação por chave sobreviva a elas. Essa instrução vale só para o transporte: a mesma palavra aplicada ao banco colunar dá uma partição por evento, e o insert morre no limite por bloco. Particione o armazenamento por tempo, ordene pela chave.

  • O producer idempotente remove duplicatas vindas de retentativas do producer dentro de uma sessão, e o Kafka o habilita por padrão desde a 3.0 junto com a confirmação de todas as réplicas
  • Esse padrão é condicional: um ajuste conflitante vindo de uma configuração antiga desliga a idempotência silenciosamente, então pergunte ao processo em execução o que ele tem
  • Ela não enxerga uma duplicata em nível de aplicação: um processo que caiu e reenviou, um beacon disparado duas vezes, um operador reexecutando um job de ingestão
  • A deduplicação de insert do ClickHouse faz hash do conteúdo do bloco, então um consumer que refaz os lotes depois de um rebalance envia as mesmas linhas em um formato novo e o hash não bate
  • A janela é limitada em blocos e em tempo, e em tabelas não replicadas o padrão é zero, ou seja, desligada; um token de insert remove essa dependência
  • Exactly-once dentro do log cobre consume-transform-produce, e o salto para um banco analítico fica fora dessa fronteira, prometa o transporte o que prometer

Então o formato que funciona é transporte at-least-once com chaves idempotentes. A deduplicação na escrita mantém a conta de armazenamento sã; a deduplicação na leitura é o que deixa o número correto. Merges nunca combinam parts de partições diferentes, então uma duplicata que cai na partição seguinte só é resolvida quando uma consulta pergunta.

Back pressure decide se uma perda é um número ou um boato

Sob sobrecarga um sistema tem três opções: desacelerar o producer, descartar com um contador, ou perder em silêncio. Só a terceira é inaceitável, e ela é o comportamento padrão de um código a quem nunca se fez a pergunta. Perda no pico é uma fila que cresceu até a memória acabar, ou uma confirmação emitida antes da durabilidade.

  • Filas limitadas em cada salto, com rejeição explícita em vez de crescimento. Uma fila ilimitada desloca a perda para pressão de memória e um restart
  • O tempo de bloqueio do producer e o tamanho do buffer são decisões de capacidade: dimensione-os pelo pico que você mediu, e coloque alarme no tempo passado bloqueado
  • Descarte por classe, não aleatoriamente: eventos de impressão e faturáveis sobrevivem, os de diagnóstico vão primeiro, e todo evento descartado incrementa um contador rotulado
  • O lag do consumer é back pressure tornado visível. Coloque alarme na idade do evento não processado mais antigo e na velocidade com que o lag muda

Um descarte com um contador rotulado é uma quantidade conhecida que pode ser reconciliada depois. Um descarte sem contador não é dado perdido, é um número perdido.

A chegada atrasada é estrutural, e metade da divergência é um calendário

A orientação de implementação do OpenRTB diz isso diretamente: a sequência que vai da requisição de anúncio, passando pelo leilão, até a renderização e a cobrança é fundamentalmente não transacional. Há partes demais entre as duas contagens.

O atraso é esperado, não excepcional. A bid request pode carregar uma expiração de impressão e o lance, o atraso que o bidder tolera, e a mesma orientação dá regras de bolso que vão da ordem de um minuto para web a muito mais para formatos in-app cacheados e vídeo com stitching.

Nenhum dos dois campos é um contrato. A orientação diz claramente que uma notificação de cobrança que chega depois da expiração declarada pelo bidder ainda pode ser faturável - uma discussão de política entre bidder e exchange, e não algo que o protocolo imponha.

  • Existem três timestamps por evento e exatamente um governa a janela: o relógio do dispositivo, não confiável; o horário de recebimento no edge, atrasado; o horário do leilão, autoritativo
  • Existe um quarto timestamp onde a exchange o fornece, a macro que carrega o momento em que a impressão foi entregue; onde ela falta, a especificação assume que a notificação veio segundos depois
  • Um watermark declara que o event time chegou a um ponto e que nenhum elemento anterior é esperado, então o atraso permitido é um parâmetro que você escolhe
  • Publique a janela de atraso por tipo de evento junto com a política de reapresentação: os números se movem enquanto ela está aberta, depois congelam, e os movimentos são registrados
  • Conte o evento atrasado e marque-o como atrasado, porque descartá-lo é descartar gasto que foi cobrado de você

Um evento que chega depois da janela não é um defeito do pipeline, é uma propriedade do meio. A única escolha real é se o número se move em público enquanto a janela está aberta, ou se move em privado depois.

A reconciliação faz join pelos identificadores que o protocolo já carrega

Os identificadores que a exchange substitui nas URLs de notificação e de tracking são o identificador de leilão vindo da bid request, o identificador de impressão e, quando o bidder emitiu um, o identificador de lance. Nenhum dos três é uma chave sozinho.

A especificação chama o identificador de leilão de único por exchange, não globalmente único: duas exchanges podem te entregar a mesma string no mesmo dia. O identificador de impressão é único apenas dentro da própria bid request, muitas vezes literalmente 1. O identificador de lance é opcional.

A chave que se sustenta é a composta: a exchange ou o seat pelo qual você transacionou, mais o identificador de leilão, mais o identificador de impressão. Gere-a do lado do bidder no momento do leilão, e trate qualquer coisa mais curta como prefixo, não como chave.

Se essas macros estiverem ausentes dos beacons, a reconciliação em nível de evento é impossível, e o que resta é casar por horário, placement e criativo. O que dá para construir no lugar é uma ponte de seis contagens, cada uma nomeando sua razão para diferir da etapa acima.

  • Vitórias em leilão, vindas do log do bidder - a única contagem que é inteiramente sua
  • Notificações de vitória recebidas pela exchange - a diferença é a perda de notificações e os timeouts, e, pela especificação, uma notificação de vitória não implica necessariamente entrega
  • Beacons recebidos no seu edge - a diferença é a coleta no cliente e cada costura acima dela
  • Eventos após a deduplicação - a diferença são as retentativas, e ela deve ser estável de semana para semana
  • Eventos após a filtragem de tráfego inválido - a diferença é uma taxa de filtragem que você publica, em vez de descobrir
  • Eventos faturáveis - a diferença é a regra de cobrança, e a notificação pertence ao lado do servidor, onde a exchange contabiliza receita

Uma razão estável entre as etapas é o objetivo e um movimento inexplicado é o alarme. Dentro de um mesmo sistema, as razões entre saltos devem ficar em um, e qualquer desvio é o sinal. Na fronteira entre leilão e medição, um é a leitura suspeita.

Os mesmos contadores, lidos como razões, transformam a pergunta em aritmética: aceitos sobre enviados, produzidos sobre aceitos, consumidos sobre produzidos, inseridos sobre consumidos. Quatro razões em um gráfico dizem para onde os eventos foram antes de alguém abrir um log. Acrescente um número de sequência do lado do producer por fonte e por partição, e um buraco vira evidência em vez de suspeita.

O que um pipeline reconstruído não te dá

Uma pergunta que a ponte não responde e um contrato responde: sobre qual desses números você paga. O vendedor contabiliza receita pelo seu próprio evento faturável, o comprador faz o pacing pelo dele, e a orientação trata uma diferença persistente como uma conversa de suporte entre as partes.

Decida com antecedência qual contagem é o sistema de registro do gasto, e a partir de que diferença uma nota no relatório vira um chamado com a exchange. O trabalho acima compra atribuição da perda, duplicatas honestas e uma reconciliação explicada linha por linha. Ele não compra o que vem a seguir.

  • Não torna as duas contagens iguais: os dois lados contam eventos diferentes de propósito, e a diferença é explicada, nunca removida
  • Não recupera eventos descartados antes de a instrumentação existir - a curva de estabilização começa no dia em que os contadores começam
  • Não elimina a reapresentação: ontem se move enquanto a janela de atraso está aberta, e um negócio que não tolera isso precisa de um fechamento mais tarde
  • Não sobrevive a macros ausentes: sem os identificadores de leilão nos beacons, nenhum design de armazenamento produz um join em nível de evento
  • Não torna dados amostrados passíveis de join depois, porque a amostragem decide quais perguntas continuam respondíveis antes de a linha ser escrita
  • Não substitui a lista de divulgações que uma auditoria no estilo MRC espera: ponto de captura, frequência de registro, estimativas de latência, regras para inconsistências

Um pipeline que consegue responder a essas perguntas de divulgação tem uma história de integridade. Um que não consegue tem uma opinião, e é sobre opinião que se discute no fim de um trimestre.

A falha contra a qual vale projetar não é a hora faltante que abre uma investigação. É a versão silenciosa: uma costura que descarta sem contador, um insert confirmado antes de ser durável, e uma reconciliação sobre uma janela ainda aberta.

O que a amBrain pode sustentar publicamente: a amBrain é uma empresa de engenharia de software de Yerevan, Armênia, que constrói plataformas de trading de baixa latência, matching engines e sistemas de real-time bidding em Rust. A amBrain constrói software desde 2019. Reconstruir um pipeline de medição não é um trabalho descrito aqui. Se os números param de reconciliar do lado do bidder e da exchange - os próprios registros de lance, vitória e impressão - essa é a conversa que vale ter, e ela começa pela curva de estabilização, não por uma reconstrução.

Tem um projeto assim na mesa?

Traga sua arquitetura atual e o modo de falha que preocupa você, e vamos analisá-lo juntos em meia hora.

Artigos relacionados

Erro ao carregar a imagem
AdTech
Sep 10, 202610 min de leitura

Pausas de GC do Go em um bidder RTB: mark assist, deadlines e a decisão sobre Rust

Ler post
Erro ao carregar a imagem
AdTech
Mar 5, 20267 min de leitura

Como a IA está remodelando a publicidade programática em 2026

Ler post
Erro ao carregar a imagem
AdTech
Feb 14, 20266 min de leitura

Segmentação com privacidade em primeiro lugar: construindo ad tech sem cookies de terceiros

Ler post