Pedir diagnóstico

BI e engenharia de dados

O que acontece entre a API do anunciante ter o dado e o gráfico estar na tela

A rodada tinha horário de início e não tinha horário de término. Nenhum log, nenhuma exceção, nenhum alerta — e o painel do dia seguinte mostrou um número menor que era perfeitamente plausível.

Bruno Chipelo · Mídia, medição e engenharia de dados 12 de agosto de 2026 11 min de leitura · 2.121 palavras Parte 2 de 2 · Engenharia de dados aplicada a marketing

01 O sintoma

A rodada que morreu sem deixar rastro

Na tabela de estado do pipeline, a linha daquela fonte tinha o horário de início preenchido e o de término nulo. Só isso. Nenhuma linha de log, nenhuma exceção capturada, nenhum alerta disparado. Do ponto de vista de qualquer ferramenta de monitoração, não havia acontecido nada de errado — não havia acontecido nada.

A rodada tinha sido interrompida por limite de recurso no meio da carga. Interrupção assim não gera exceção aplicacional: o processo é encerrado por quem está por fora dele, e o código nem chega a saber que vai morrer. O que sobra é a assimetria na tabela de estado — começou, não terminou.

O painel do dia seguinte mostrou um número menor. E o número menor era plausível: dia de semana, verba estável, nada gritante. Falha que grita é barata, porque alguém vê. Cara é a que continua verossímil — e quase tudo que se projeta num pipeline de dados existe para transformar a segunda na primeira.

Este texto descreve o desenho: as quatro etapas do pipeline e o que decide cada fronteira, cinco defeitos que pagamos para aprender, quando o desenho não deve ser esse — e como se prova, com testemunha externa, que uma refatoração não mudou nada.

02 O conceito

Extrair, normalizar, carregar, servir — e o que decide cada fronteira

Um pipeline de dados de marketing tem quatro etapas, e o desenho inteiro é decidido pelo que se coloca em cada fronteira entre elas. Reis e Housley chamam isso de ciclo de vida da engenharia de dados; o vocabulário importa menos que a disciplina de não misturar as etapas.

Extrair é falar com a fonte e trazer o que ela tem, no formato dela, sem opinião. Normalizar é onde mora quase todo o trabalho: converter tipo e fuso, decidir o que é chave, traduzir o nome que cada plataforma dá ao mesmo canal, descartar a linha duplicada e marcar de onde cada campo veio. Carregar é escrever de um jeito que possa ser repetido sem estrago. Servir é a agregação da leitura — e é etapa separada de propósito, porque o dia em que a pergunta mudar não pode ser o dia em que o histórico precisa ser recarregado.

Onde Python entra, e onde termina

Python faz a extração e a normalização. É onde estão os SDK oficiais das plataformas, os clientes HTTP com política de repetição decente e as ferramentas de manipulação de dados. Ele termina quando o dado está conformado e escrito. A agregação acontece em SQL, na leitura, dentro do banco — trazer uma tabela para a memória do processo só para devolvê-la somada é trabalho e risco a mais, e piora com o tempo à medida que a base cresce.

Explicitar esse limite vale mais que defender a linguagem. A escolha é por trecho, não por gosto: extração e conformação em Python porque a variedade das APIs é o problema; agregação em SQL porque o volume é o problema; a rotina agendada rodando na borda porque a disponibilidade é o problema. Uma linguagem que faz tudo bem em toda etapa não existe, e o pipeline que finge que existe acaba com regra de negócio espalhada em três lugares.

Idempotência como requisito, não como virtude

Rodar de novo tem de ser barato e seguro. Se rodar duas vezes muda o resultado, o pipeline não é confiável — é sortudo. Isso se paga na primeira falha real: com idempotência, a resposta a "a carga de ontem quebrou no meio" é rodar de novo e ir dormir; sem ela, é abrir o banco e reconciliar à mão, às onze da noite, num estado que ninguém conhece.

Na prática isso significa gravar de forma que a repetição seja inofensiva: uma chave que identifique a linha sem ambiguidade e uma escrita que substitua a versão anterior daquela chave em vez de acrescentar outra. Kleppmann é claro sobre o motivo de isso ser requisito e não elegância: entrega exatamente uma vez é ilusão em sistemas distribuídos, e o que existe de verdade é entrega pelo menos uma vez somada a uma operação que tolera repetição.

Janela móvel e marca d’água

Reprocessar toda a série todo dia é caro e desnecessário; reprocessar só o dia de ontem é barato e errado. Dado de marketing amadurece: conversão é reprocessada, atribuição é retroativa, pedido muda de status depois de faturado, ferramenta de busca só publica o dia depois de alguns dias. A janela precisa caber na latência da fonte — reprocessa-se os últimos N dias, com N escolhido pelo tempo que aquela fonte leva para parar de mudar de ideia.

A marca d’água é o registro de até onde o dado é considerado final. Ela existe para que a leitura saiba o que ainda pode mudar, e é o que permite marcar o período corrente como parcial numa tela em vez de deixá-lo cair no gráfico como se fosse queda de desempenho. Quase toda "queda no último dia" de painel é ausência de marca d’água.

Qual é o recurso escasso

Otimizar antes de saber o que é caro é adivinhação. Nem sempre o escasso é o tempo. Quando o caro é a escrita — ambiente com cota de linhas gravadas, banco de borda, API com limite de chamadas —, o desenho inteiro muda: em vez de reescrever a fatia toda, lê-se o que já está lá, compara-se com o que chegou e grava-se apenas o que mudou. O mesmo pipeline, com o mesmo resultado final, passa a custar uma fração.

Identificar o recurso escasso antes de escrever a primeira linha é metade do projeto. A outra metade é aceitar que ele muda: o que era limitado por chamada de API vira limitado por memória quando a base dobra, e o desenho precisa ser refeito na fronteira certa.

Fonte isolada

Um pipeline com quatro fontes não pode deixar a falha de uma derrubar as outras três. Cada fonte registra o próprio estado — início, término, contagem de linhas, erro — e o orquestrador segue para a seguinte independentemente do que aconteceu na anterior. O ganho não é só disponibilidade: é diagnóstico. Com estado por fonte, "o painel está desatualizado" vira "a fonte tal parou de responder às 3h", que é uma frase acionável.

Pipeline com quatro fontes isoladas Quatro fontes — duas plataformas de anúncio, o analytics do site e o e-commerce — são extraídas em paralelo, cada uma gravando o próprio estado. Uma delas falhou e registrou o erro sem impedir as outras. As que passaram seguem para normalização, carga idempotente e banco; a leitura agrega em SQL e respeita a marca d’água, que separa o período final do parcial. EXTRAIR NORMALIZAR · CARREGAR SERVIR anúncio · A janela de 7 dias anúncio · B 503 · erro registrado analytics do site janela de 3 dias e-commerce pedido muda de status × normalizar tipo · fuso · chave de-para de canal procedência do campo carregar chave estável repetir é inofensivo banco fato no grão fino dimensões conformadas agregação em SQL na leitura, não na carga estado por fonte: início · término · linhas · erro início sem término = rodada morta por recurso marca d’água até aqui o dado é final
Quatro fontes, quatro estados independentes. A que falha registra o erro e não impede as outras de carregar — e a marca d’água diz até onde o dado pode ser lido como final.

03 Como aplicamos

Cinco defeitos que custaram caro

O upsert que apaga o que não veio

Uma escrita do tipo "insere ou substitui" grava a linha inteira. A coluna que não estava no payload não fica como estava: volta vazia. O comando acerta exatamente o que você mandou e apaga o resto, sem erro e sem aviso. A mesma armadilha aparece na ordem de uma migração: se o backfill de uma coluna nova começar antes de a rotina de sincronismo saber que ela existe, a rotina antiga reescreve com vazio o que o backfill acabou de preencher, e as duas ficam brigando em silêncio. Publicar as duas pontas antes de carregar resolve.

A fonte portada sem retry

Uma extração que funcionava havia meses foi portada por nós para outro ambiente. A tradução ficou fiel em tudo, menos na política de repetição, que não veio junto. A primeira rodada morreu em um segundo, no primeiro erro transitório da API — o tipo de erro que aquela fonte devolve várias vezes por semana e que a versão original absorvia sem ninguém notar. O retry não era detalhe de implementação: era metade da razão de a versão antiga parecer estável.

A morte que não loga

É o caso do começo deste texto. Interrupção por limite de recurso não gera exceção aplicacional, então não existe except capaz de registrá-la. O único sinal é o registro de execução com início preenchido e término nulo. Isso muda o que se monitora: em vez de vasculhar log atrás de erro, verifica-se a tabela de estado atrás de rodada que começou e não terminou — e é essa checagem, não o log, que vira alerta.

Segredo nunca em query string

Token passado na URL vaza no traceback: a biblioteca HTTP imprime a URL inteira quando levanta o erro, e o log de execução passa a guardar a credencial em texto puro. Detalhe pequeno, consequência desproporcional. Vale também para a paginação — alguns serviços devolvem o link da próxima página com o token embutido, de modo que reaproveitar esse link propaga o segredo para dentro de todo o resto do código. Credencial vai no cabeçalho, e o filtro de log mascara o que passar por perto.

Fusos diferentes na mesma coluna

Uma plataforma de anúncio reportava no fuso configurado na conta; o analytics do site, em outro. No agregado mensal isso é invisível. No grão diário, as duas séries desencontram em uma hora — e o desencontro se concentra nas transações do fim da noite, que mudam de dia. A comparação lado a lado fica errada sem que nada acuse, porque as duas séries continuam internamente consistentes. Fuso é atributo da fonte, não do banco: ou se normaliza na entrada, ou se guarda o fuso junto e se converte na leitura. O que não dá é misturar os dois na mesma coluna.

04 Trade-offs

Quando o pipeline não deve ser assim

Quando não há API

Nem toda conta tem credencial de API — algumas plataformas exigem aprovação que leva meses, outras não liberam para quem não é parceiro. Nesses casos a extração pode viver como rotina agendada dentro do próprio painel de anúncios, gravando numa planilha que o pipeline lê depois. É menos elegante, tem menos garantia de execução e resolve. Tem inclusive uma vantagem que a solução elegante não tem: nenhum segredo nosso passa a morar dentro da conta do cliente.

Quando não é Python

Se o dado já está no banco, a transformação é SQL. A tentação de trazer para um dataframe é grande porque a manipulação é confortável; o custo aparece depois, quando a regra de negócio passa a existir em dois dialetos e a correção só chega a um deles. A fronteira útil é geográfica, não estética: enquanto o dado está vindo de fora e ainda não tem forma, é Python; depois que ele está gravado e tipado, é do banco. Cruzar essa linha nos dois sentidos é o que produz pipeline em que ninguém sabe mais onde uma regra é aplicada.

Pipeline próprio ou ferramenta gerenciada

Não há veredito aqui, há critério. Quantas fontes existem e quantas delas a ferramenta já cobre de fábrica; quão previsíveis são essas APIs, porque conector genérico envelhece mal em plataforma que muda de contrato; quem mantém o que quebrar às sete da manhã; e quanto custa uma hora de indisponibilidade do painel. Com poucas fontes bem cobertas e ninguém disponível para manter, a ferramenta gerenciada ganha com folga. Com fontes que exigem regra própria de conformação, ela vira uma camada a mais para depurar.

05 Como provamos

Como se afirma que uma mudança não mudou nada

A pergunta mais difícil de uma refatoração de pipeline não é "funciona?", é "o resultado continua idêntico?". Feathers chama de teste de caracterização: não se escreve o teste a partir da especificação, e sim a partir do comportamento atual, inclusive das esquisitices dele.

  1. Capturar a versão antiga verbatim, por cópia de arquivo — nunca redigitando. Redigitar já é reinterpretar, e a diferença aparece justamente no detalhe que a memória não guarda.
  2. Rodar as duas versões ponta a ponta contra o artefato real de produção, em modo somente leitura, e comparar a saída inteira, não uma amostra.
  3. Publicar o número da comparação junto com o tamanho da base, para que quem lê saiba o peso do "nada mudou".

Numa refatoração recente de um módulo de calendário, o resultado foi zero linha alterada numa base de cerca de dez mil. É um número pequeno e por isso mesmo dito com o denominador junto: taxa publicada sem tamanho de amostra é exatamente o defeito que este par de textos denuncia.

É a mesma lógica que sustenta o outro lado desta série: um número só é confiável quando existe uma segunda fonte capaz de contradizê-lo. No pipeline, essa fonte é o artefato antigo; no modelo, é a dimensão que precisa bater com o código que já estava em produção.

Perguntas frequentes

As respostas curtas, para quem chegou pela pergunta.

Python ou SQL para transformar dado de marketing?
Os dois, em trechos diferentes. Python faz extração e normalização, porque o problema ali é a variedade das APIs. A agregação é SQL, dentro do banco, porque ali o problema é volume. A fronteira é geográfica: enquanto o dado vem de fora e ainda não tem forma, é Python; depois de gravado e tipado, é do banco.
O que é idempotência num pipeline, na prática?
É poder rodar de novo sem estrago. Uma chave que identifica a linha sem ambiguidade e uma escrita que substitui a versão anterior daquela chave, em vez de acrescentar outra. Sem isso, a resposta a uma carga que quebrou no meio deixa de ser "rode de novo" e passa a ser reconciliação manual num estado que ninguém conhece.
Quantos dias reprocessar na janela móvel?
O tanto que a fonte leva para parar de mudar de ideia. Conversão reprocessada, atribuição retroativa e pedido que muda de status depois de faturado definem esse prazo, e ele é diferente por fonte. Reprocessar só ontem é barato e errado; reprocessar tudo todo dia é caro e desnecessário.
Como descobrir que uma rodada falhou se não há erro no log?
Interrupção por limite de recurso não gera exceção aplicacional, então não existe captura que a registre. O sinal fica na tabela de estado: início preenchido e término nulo. O alerta tem de olhar para essa assimetria, não para o log.
Vale mais um pipeline próprio ou uma ferramenta gerenciada?
Depende de quantas fontes existem e quantas a ferramenta cobre de fábrica, de quão previsíveis são essas APIs, de quem mantém o que quebrar às sete da manhã e de quanto custa uma hora de painel indisponível. Com poucas fontes bem cobertas e ninguém para manter, a ferramenta ganha. Com fontes que exigem regra própria de conformação, ela vira mais uma camada para depurar.

Bibliografia

De onde vem o que não inventamos.

  • Martin Kleppmann, Designing Data-Intensive Applications idempotência, entrega pelo menos uma vez e por que exactly-once é uma propriedade do efeito, não da entrega.
  • Maxime Beauchemin, "Functional Data Engineering: a modern paradigm for batch data processing" (2018) imutabilidade e sobrescrita de partição como base da repetição segura.
  • Joe Reis e Matt Housley, Fundamentals of Data Engineering (O’Reilly, 2022) o ciclo de vida da engenharia de dados, que dá a espinha deste artigo.
  • Michael Feathers, Working Effectively with Legacy Code testes de caracterização — escrever o teste a partir do comportamento existente.