Funcionava, mas ninguém confiava
Todo dia, um fluxo automático levava os dados de produção para o data lake do BI. Primeiro, restaurava o dump mais recente do banco num PostgreSQL dedicado. Depois, o Airbyte replicava esse banco por CDC. Por fim, tudo era carregado no BigQuery.
Os jobs rodavam, às vezes falhavam e eram repetidos. Pelo painel, o pipeline funcionava. Mas o BI reclamava que os dados estavam sempre atrasados, os syncs levavam de 2 a 11 horas, e alguns dias terminavam sem ingerir nada.
Um dia atrasado, sempre
A primeira coisa que chamou atenção foi que o BigQuery mostrava uma foto do banco das 21h do dia anterior, mesmo com o sync rodando de madrugada. Cruzei os horários dos CronJobs e a explicação era simples: o restore pegava o dump mais recente disponível, mas rodava antes de o backup da madrugada chegar ao bucket. Ele sempre pegava o das 21h. Movi o restore para depois do backup, no cluster e no Git, e o atraso dos dados para o BI caiu em cerca de seis horas.
No mesmo lugar estava o motivo de cada sync varrer as 153 tabelas por inteiro: recriar o banco a cada restore destruía o slot de replicação do CDC.
Falhas que não faziam barulho
Depois vieram as falhas silenciosas, uma por uma. Um passo de correção de schema nunca tinha funcionado: faltava uma biblioteca na imagem, e o erro era engolido por três blocos except e um exit 0. O passo "passava" em quatro segundos sem fazer nada. Reconstruí a imagem com o script corrigido, com simulação por padrão e falha explícita no log.
Uma tabela tinha uma coluna com o mesmo nome dela, e o BigQuery interpretava o identificador como a linha inteira, e não como a coluna. Varri os 157 streams para confirmar que era o único caso. Esse problema custava uma passagem completa de uns 50 GB por dia.
O servidor do Airbyte também caía sozinho: uma rotina interna tentava cancelar um job com id inválido a cada minuto e estourava os 6 GB de heap. E o alerta do sync mentia, com um timeout de seis horas que acusava falha em syncs que terminavam com sucesso pouco depois. Foram quatro alarmes falsos em 50 dias.
Depois dessas correções, o sync seguinte fechou na primeira tentativa.
O erro 500 de todo dia
O erro mais frequente era um 500 do BigQuery no upload. A API do Airbyte estourava a memória do servidor quando eu pedia o histórico, então li as falhas tentativa por tentativa direto no banco interno dele. O conector subia cerca de 2,5 GB num único upload, sem área intermediária. Um 500 no meio matava a tentativa inteira, que recomeçava do zero. Medi um desperdício de 7,7 vezes num job e cerca de 45 horas de tentativas perdidas em 27 dias.
Escrevi o pós-mortem com a linha do tempo e os dados desses 27 dias. Testei a atualização do conector, mas como hipótese, não como correção, e comparei o resultado com a linha de base. A diferença estava dentro do ruído: não tinha resolvido. A documentação do BigQuery recomenda, para arquivos grandes, carregar a partir do Cloud Storage, onde repetir com backoff exponencial é suficiente. Abri esse estudo como próximo passo, com custo, permissões, validação e rollback.
GitOps dizendo que estava tudo bem
Em outro produto, o Airbyte ficou 24 horas fora do ar enquanto o ArgoCD mostrava a aplicação sincronizada e saudável. O chart declarava a ServiceAccount como hook do Helm, e o ArgoCD não inclui hooks no estado desejado. Quando ela foi apagada, nada a recriou. Restaurei a conta, versionei os recursos como objetos normais e documentei um segundo desvio: uma política de sincronização ativa no cluster que não existia no Git.
Também ajudei a preparar o backup que alimenta o pipeline para ser cifrado com KMS, com a separação de privilégios testada na prática: a credencial do backup consegue gerar uma chave de dados, mas recebe acesso negado ao tentar decifrar.
O que mudou
Os dados chegaram ao BI com cerca de seis horas a menos de atraso. As quatro falhas silenciosas foram eliminadas, e o sync passou a fechar na primeira tentativa. A causa do erro mais frequente ficou determinada num pós-mortem, com a próxima solução baseada na recomendação oficial do fornecedor.
O que eu levo disso
Hipótese testada não é problema corrigido. Atualizar o conector parecia resolver, e teria sido fácil declarar vitória. Comparar com a linha de base mostrou que não, e isso evitou que o time parasse de procurar a solução real.
E pipeline de dados também precisa de SRE. Um passo que termina com exit 0 sem fazer nada é pior do que um que falha, porque ninguém vai procurar.