Como validar checksums em ELT para detetar regressões
Este tutorial mostra como implementar e validar checksums (hashes) em ELT para detetar regressões e corrupções nos dados durante cargas. Validar checksums ajuda a garantir integridade, facilita reprocessamento idempotente e reduz surpresas em produção. Vou explicar o porquê de cada passo e dar exemplos concretos: por exemplo, como gerir um conjunto de 100k–1M de linhas e que métricas monitorizar (percentagem de alteração, tempo de processamento).
Pré-requisitos
- Ambiente com SQL (ex.: Azure Synapse, Databricks SQL, PostgreSQL) ou Spark/Databricks.
- Uma tabela de origem com colunas simples (strings/números) ou ficheiros Parquet/CSV. Idealmente começa com uma tabela pequena (~10k linhas) para validar o processo antes de escalar para 100k–1M.
- Permissão para criar tabelas temporárias e executar funções de hash (MD5/SHA1/SHA256). SHA256 é recomendado por ser resistente a colisões (256 bits).
- Conhecimentos básicos de SQL ou PySpark. No mínimo saber executar SELECT, JOIN e criar tabelas simples.
Passo 1: Escolher a estratégia de checksum
Decidir se o checksum será por linha ou por partição/ficheiro. O mais comum é gerar um hash por linha baseado nas colunas que representam a chave de negócio e os atributos a proteger. Para tabelas com 100k–1M linhas, o cálculo por linha é fácil de armazenar (uma coluna adicional) e permite detetar quais registos mudaram.
Evita incluir colunas com timestamps de ingestão se quiseres idempotência: se incluíres um campo como ingestion_time cada execução produzirá checksums diferentes mesmo sem alterações lógicas. Alternativa: gerar dois checksums — row_checksum (atributos de negócio) e ingest_checksum (inclui metadata) — para diagnóstico.
Passo 2: Gerar checksum por linha em SQL
Exemplo em SQL usando SHA256 concatenando colunas em ordem determinística. Inclui COALESCE para valores NULL e separador consistente. Nota: SHA256 produz 256 bits; a probabilidade de colisão é negligível para volumes habituais (<10^9 linhas).
-- Exemplo SQL (adaptar nome de tabela/colunas)
SELECT
id,
col1,
col2,
col3,
LOWER(CONVERT(VARCHAR(64), HASHBYTES('SHA2_256',
CONCAT(COALESCE(col1,''|'') , '||' , COALESCE(CAST(col2 AS VARCHAR),''), '||' , COALESCE(col3,''))
), 2)) AS row_checksum
FROM source_table;
Em sistemas onde HASHBYTES não suporta grandes strings, serializa apenas os campos críticos (ex.: 5–10 colunas) ou usa funções nativas como sha2 em Databricks. Testa com uma amostra de 1k–10k linhas para medir tempo: em clusters modestos gerar 100k checksums SHA256 costuma demorar entre 10–60s.
Passo 3: Gerar checksum por linha em PySpark
Exemplo em PySpark/Databricks usando SHA2. Mantém a ordem das colunas e trata NULLs explicitamente. PySpark escala bem: um cluster com 4 cores pode processar 1M linhas em 1–3 minutos dependendo da complexidade das transformações.
# Exemplo PySpark
from pyspark.sql.functions import sha2, concat_ws, coalesce, lit, col
df = spark.table('source_table')
df_with_checksum = df.withColumn('row_checksum',
sha2(concat_ws('||', coalesce(col('col1'), lit('')), coalesce(col('col2').cast('string'), lit('')), coalesce(col('col3'), lit(''))), 256)
)
df_with_checksum.createOrReplaceTempView('source_with_checksum')
Passo 4: Armazenar checksums e metadados
Crias uma tabela de auditoria que guarda chave, checksum, source_run_id e source_timestamp. Guarda também o número de linhas e um hash global por partição/ficheiro para diagnosticar corrupções de ficheiros grandes. Por exemplo, para 1M linhas, uma tabela audit_checksums com 1M registos é trivial de armazenar em formato columnar (Parquet/Delta).
CREATE TABLE audit_checksums (
id STRING,
row_checksum STRING,
source_run_id STRING,
source_timestamp TIMESTAMP
);
INSERT INTO audit_checksums
SELECT id, row_checksum, 'run_20261006_01', CURRENT_TIMESTAMP FROM source_with_checksum;
Exemplo de hash global por partição: agregas os row_checksums ordenados e calculas um hash final. Isto deteta diferenças ao nível do ficheiro mesmo que o número de linhas seja igual.
Passo 5: Comparar checksums entre execuções
Para detetar regressões fazes um JOIN entre a tabela actual e a tabela de auditoria anterior: checksum diferente => alteração; falta na actual => eliminação; ausência na anterior => nova linha. Analisa percentagens: por exemplo, se >0.1% das linhas aparecerem como CHANGED num processo diário pode ser aceitável em alguns cenários, mas se for >1% merece investigação.
-- Exemplo SQL de comparação
WITH prev AS (SELECT id, row_checksum AS prev_checksum FROM audit_checksums WHERE source_run_id = 'run_prev'),
curr AS (SELECT id, row_checksum AS curr_checksum FROM source_with_checksum)
SELECT
COALESCE(curr.id, prev.id) AS id,
CASE
WHEN prev.id IS NULL THEN 'NEW'
WHEN curr.id IS NULL THEN 'DELETED'
WHEN prev.prev_checksum != curr.curr_checksum THEN 'CHANGED'
ELSE 'UNCHANGED'
END AS status
FROM prev FULL OUTER JOIN curr ON prev.id = curr.id;
Passo 6: Lidar com falsos positivos e normalização
Erros comuns: diferenças por ordem de colunas, espaços em branco, tipos diferentes ou colunas irrelevantes. Normaliza strings (trim, lower), ordena colecções antes de serializar e converte tipos para evitar falsos positivos. Por exemplo, usa LOWER(TRIM(col_name)) e converte decimais para formato com precisão fixa antes de hash.
-- Normalização simples em SQL
LOWER(TRIM(col_name))
-- Em PySpark usa trim(lower(col('col')))
Também define um limiar para alertas automáticos: por exemplo, se NEW + DELETED + CHANGED > 0.5% das linhas diárias, cria um alerta e lança um job de validação aprofundada.
Verificar o resultado
Valida executando a comparação entre duas corridas de exemplo: introduz uma alteração intencional numa linha e verifica se o status aparece como CHANGED; remove uma linha e verifica DELETED; adiciona uma linha e verifica NEW. Confirma também que rows com só o timestamp de ingestão diferente aparecem como UNCHANGED se esse campo não for incluído no checksum. Regista métricas: tempo de cálculo, número de alterações e percentagem relativa para construir um histórico (p.ex. média diária de 0.03% alterações).
Conclusão
Implementar checksums em ELT é uma técnica simples e poderosa para detetar regressões, validar integridade e facilitar reprocessamentos idempotentes. Próximos passos práticos: automatizar o carregamento dos audit logs, criar alertas quando a percentagem de mudanças exceder um limiar e gerar checksums por partição para validar ficheiros grandes. Dica: começa por proteger um conjunto pequeno de tabelas críticas (3–5 tabelas) e itera conforme encontras falsos positivos; em 2–4 semanas terás um mecanismo robusto que reduz investigações manuais e aumenta confiança nas cargas.