Machine learning models are only as good as the data they train on — and only as reliable as the pipelines that deliver that data. Yet data pipelines are often the least engineered part of an ML system: written as one-off scripts, lacking tests, without retry logic, and impossible to debug when they fail silently at 3am.Modelos de machine learning são tão bons quanto os dados com que treinam — e tão confiáveis quanto os pipelines que entregam esses dados. No entanto, pipelines de dados são frequentemente a parte menos engenheirada de um sistema de ML: escritos como scripts avulsos, sem testes, sem lógica de retry e impossíveis de depurar quando falham silenciosamente às 3h da manhã.
This article applies software engineering principles to data pipelines: idempotency, testability, schema contracts, observability, and incremental processing. The goal is a pipeline you can trust at scale — one that fails loudly, recovers automatically, and is as easy to reason about as any well-structured service.Este artigo aplica princípios de engenharia de software a pipelines de dados: idempotência, testabilidade, contratos de schema, observabilidade e processamento incremental. O objetivo é um pipeline em que você pode confiar em escala — que falha barulhentamente, se recupera automaticamente e é tão fácil de raciocinar quanto qualquer serviço bem estruturado.
The Core Properties of a Reliable PipelineAs Propriedades Fundamentais de um Pipeline Confiavel
A pipeline is reliable when it satisfies four properties:Um pipeline é confiável quando satisfaz quatro propriedades:
- Correctness: The output is accurate and complete. Data quality checks run at every stage boundary.Corretude: O output é preciso e completo. Verificações de qualidade de dados rodam em cada fronteira de etapa.
- Idempotency: Running the pipeline twice for the same time window produces the same result. Reruns do not create duplicates or leave partial state.Idempotência: Executar o pipeline duas vezes para a mesma janela de tempo produz o mesmo resultado. Reruns não criam duplicatas nem deixam estado parcial.
- Observability: You know when it ran, how long each step took, how many records were processed, and what failed — without digging through logs.Observabilidade: Você sabe quando rodou, quanto tempo cada etapa levou, quantos registros foram processados e o que falhou — sem vasculhar logs.
- Recoverability: When a step fails, you can rerun just that step, for a specific time window, without cascading side effects.Recuperabilidade: Quando uma etapa falha, você pode reroda-la individualmente, para uma janela de tempo específica, sem efeitos colaterais em cascata.
The most common failure mode: A pipeline that is not idempotent. A failed run leaves partial state. The rerun appends to the partial state instead of replacing it. Two weeks later, your training dataset has double-counted records and you do not know it.O modo de falha mais comum: Um pipeline que não é idempotente. Uma execução com falha deixa estado parcial. O rerun acrescenta ao estado parcial em vez de substituí-lo. Duas semanas depois, seu dataset de treinamento tem registros contados em duplicata e você não sabe.
The Medallion ArchitectureA Arquitetura Medallion
The most widely adopted pattern for organizing a data lakehouse is the Medallion Architecture: three layers with progressively higher data quality.O padrão mais amplamente adotado para organizar um data lakehouse é a Arquitetura Medallion: três camadas com qualidade de dados progressivamente maior.
raw ingestion
cleaned & validated
feature-ready
model inputs
- Bronze: Raw data exactly as it arrived from the source — no transformations, no filtering. Stored immutably. If a source system sends bad data, you can reprocess from bronze after the fix.Bronze: Dados brutos exatamente como chegaram da fonte — sem transformações, sem filtragem. Armazenados imutavelmente. Se um sistema de origem envia dados ruins, você pode reprocessar a partir do bronze após a correção.
- Silver: Cleaned, deduplicated, type-cast, and schema-validated data. Failed records are quarantined in a dead letter table, not silently dropped.Silver: Dados limpos, deduplicados, com tipos corretos e validados por schema. Registros com falha são quarentenados em uma tabela de dead letter, não descartados silenciosamente.
- Gold: Business-ready aggregations and feature tables. Optimized for query patterns — the input layer for model training and batch inference.Gold: Agregações e tabelas de features prontas para o negócio. Otimizadas para padrões de consulta — a camada de entrada para treinamento de modelos e inferência em batch.
Idempotency by DesignIdempotencia por Design
Every pipeline step should be designed to be safely rerun. The two most reliable techniques:Cada etapa do pipeline deve ser projetada para ser segura de reruns. As duas técnicas mais confiáveis:
Overwrite, Don't AppendSobrescreva, Nao Anexe
For batch pipelines, write results to a partition and overwrite that partition on rerun. With Delta Lake or Apache Iceberg, partition overwrite is atomic — the reader sees either the old data or the new data, never a partial state mid-write.Para pipelines batch, escreva os resultados em uma partição e sobrescreva essa partição no rerun. Com Delta Lake ou Apache Iceberg, a sobrescrita de partição é atômica — o leitor vê os dados antigos ou os novos, nunca um estado parcial durante a escrita.
# PySpark — idempotent partition overwrite with Delta Lake
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
spark.conf.set("spark.sql.sources.partitionOverwriteMode", "dynamic")
df_silver = (
spark.read.format("delta").load("s3://lake/bronze/transactions")
.filter("event_date = '2025-02-15'")
.dropDuplicates(["transaction_id"])
.withColumn("amount_usd", col("amount_cents") / 100)
)
# Overwrite only the 2025-02-15 partition — safe to rerun
(df_silver
.write
.format("delta")
.mode("overwrite")
.partitionBy("event_date")
.save("s3://lake/silver/transactions")
)Upsert with MergeUpsert com Merge
For slowly-changing data (user profiles, product catalog), use a MERGE statement. If the record already exists, update it. If it does not, insert it. Running twice produces the same final state.Para dados que mudam lentamente (perfis de usuário, catálogo de produtos), use uma instrução MERGE. Se o registro já existe, atualize-o. Se não existe, insira-o. Executar duas vezes produz o mesmo estado final.
-- Delta Lake MERGE — idempotent upsert
MERGE INTO silver.users AS target
USING bronze.users_staging AS source
ON target.user_id = source.user_id
WHEN MATCHED AND source.updated_at > target.updated_at THEN
UPDATE SET *
WHEN NOT MATCHED THEN
INSERT *;Schema Contracts with dbtContratos de Schema com dbt
dbt (data build tool) brings software engineering practices to SQL transformations: version control, testing, documentation, and lineage tracking. Every transformation is a model; every model has tests.O dbt (data build tool) traz práticas de engenharia de software para transformações SQL: controle de versão, testes, documentação e rastreamento de linhagem. Cada transformação é um modelo; cada modelo tem testes.
# models/silver/transactions.yml — schema contract
version: 2
models:
- name: silver_transactions
description: Cleaned and validated transaction records
columns:
- name: transaction_id
description: Unique transaction identifier
tests:
- unique
- not_null
- name: amount_usd
tests:
- not_null
- dbt_utils.expression_is_true:
expression: "amount_usd > 0"
- name: customer_id
tests:
- not_null
- relationships:
to: ref('silver_customers')
field: customer_idWhen dbt test fails, the pipeline halts. No silent corruption propagates to the gold layer or into training data. The failure is logged, alerted, and the run is marked as requiring manual review.Quando o dbt test falha, o pipeline para. Nenhuma corrupção silenciosa se propaga para a camada gold ou para os dados de treinamento. A falha é registrada, alertada e o run é marcado como necessitando de revisão manual.
Orchestration with Apache AirflowOrquestracao com Apache Airflow
Airflow orchestrates the sequence of pipeline tasks, handles retries, and provides a UI for monitoring run history. Key practices for production Airflow DAGs:O Airflow orquestra a sequência de tarefas do pipeline, gerencia retries e fornece uma UI para monitoramento do histórico de execuções. Práticas-chave para DAGs Airflow em produção:
from airflow.decorators import dag, task
from airflow.utils.dates import days_ago
from datetime import timedelta
@dag(
schedule_interval="@daily",
start_date=days_ago(1),
catchup=False, # Don't backfill missed runs on startup
max_active_runs=1, # Prevent overlapping runs for the same DAG
default_args={
"retries": 3,
"retry_delay": timedelta(minutes=5),
"retry_exponential_backoff": True,
"execution_timeout": timedelta(hours=2),
"on_failure_callback": alert_slack,
}
)
def ml_feature_pipeline():
@task
def ingest_bronze(ds: str):
"""Idempotent ingestion for logical_date=ds"""
run_ingestion(date=ds)
@task
def transform_silver(ds: str):
run_dbt_model("silver_transactions", date=ds)
@task
def build_gold_features(ds: str):
run_dbt_model("gold_churn_features", date=ds)
@task
def validate_features(ds: str):
run_great_expectations_suite("churn_features", date=ds)
bronze = ingest_bronze()
silver = transform_silver()
gold = build_gold_features()
check = validate_features()
bronze >> silver >> gold >> check
dag = ml_feature_pipeline()Key pattern: Pass the ds (logical date) explicitly to every task. This makes every task a pure function of its inputs and enables safe backfilling: you can rerun any historical date window independently without side effects on other dates.Padrão chave: Passe o ds (data lógica) explicitamente para cada tarefa. Isso torna cada tarefa uma função pura de seus inputs e habilita backfilling seguro: você pode reroda qualquer janela de data histórica independentemente sem efeitos colaterais em outras datas.
Handling Late-Arriving DataTratando Dados com Atraso
In real systems, data arrives late. A transaction that happened on Monday may only reach the data lake on Wednesday due to batch ETL delays, mobile app sync, or source system lag. Two strategies:Em sistemas reais, dados chegam atrasados. Uma transação que ocorreu na segunda-feira pode chegar ao data lake somente na quarta-feira devido a atrasos de ETL batch, sincronização de app mobile ou lag do sistema de origem. Duas estratégias:
Watermark-based Processing (Streaming)Processamento por Watermark (Streaming)
In streaming pipelines (Spark Structured Streaming, Flink), a watermark defines how late an event can arrive and still be included in a window computation. Events arriving after the watermark are either included in a late-data bucket or discarded — but never silently corrupt a closed window.Em pipelines de streaming (Spark Structured Streaming, Flink), um watermark define o quão tarde um evento pode chegar e ainda ser incluído no cálculo de uma janela. Eventos que chegam após o watermark são incluídos em um bucket de dados tardios ou descartados — mas nunca corrompem silenciosamente uma janela fechada.
Reprocessing Windows (Batch)Janelas de Reprocessamento (Batch)
For batch pipelines, always reprocess the last N days of data to catch late arrivals. On each daily run, overwrite not just today's partition but also the previous 3 days. This is safe because the write is idempotent — and it ensures late-arriving records are reflected in features used for training.Para pipelines batch, sempre reprocesse os últimos N dias de dados para capturar chegadas tardias. Em cada execução diária, sobrescreva não apenas a partição de hoje, mas também os 3 dias anteriores. Isso é seguro porque a escrita é idempotente — e garante que registros tardios sejam refletidos nas features usadas para treinamento.
# Reprocess last 3 days to absorb late-arriving records
from datetime import date, timedelta
def get_reprocessing_dates(logical_date: date, lookback_days: int = 3):
return [logical_date - timedelta(days=i) for i in range(lookback_days + 1)]
# Run transformation for each date window
for process_date in get_reprocessing_dates(today):
run_silver_transform(date=process_date)Pipeline ObservabilityObservabilidade do Pipeline
Every production pipeline needs metrics surfaced to a dashboard. The minimum viable set:Todo pipeline de produção precisa de métricas expostas em um dashboard. O conjunto mínimo viável:
- Record counts per layer: Bronze rows ingested, Silver rows after dedup/validation, Gold rows output. Sudden drops indicate upstream failures or schema changes.Contagem de registros por camada: Linhas ingeridas no Bronze, linhas no Silver após dedup/validação, linhas de saída no Gold. Quedas repentinas indicam falhas upstream ou mudanças de schema.
- Null rates per critical column: If
customer_idgoes from 0.1% null to 15% null overnight, a join broke upstream.Taxas de nulo por coluna crítica: Se ocustomer_idvai de 0,1% nulo para 15% nulo da noite para o dia, um join quebrou upstream. - Pipeline duration by task: A task that normally takes 8 minutes taking 45 minutes signals a data volume spike or a performance regression.Duração do pipeline por tarefa: Uma tarefa que normalmente leva 8 minutos levando 45 minutos sinaliza um pico de volume de dados ou uma regressão de performance.
- Dead letter record counts: Records quarantined due to validation failures. A rising dead letter count is an early warning of schema drift at the source.Contagem de registros dead letter: Registros quarentenados por falhas de validação. Uma contagem crescente de dead letters é um aviso antecipado de schema drift na fonte.
Key TakeawaysPontos Principais
- Design every pipeline step to be idempotent — use partition overwrite or MERGE, never append-only writes for reprocessable data.Projete cada etapa do pipeline para ser idempotente — use sobrescrita de partição ou MERGE, nunca escritas somente-append para dados reprocessáveis.
- Adopt the Medallion Architecture to isolate raw ingestion from clean data and from feature-ready output.Adote a Arquitetura Medallion para isolar a ingestão bruta dos dados limpos e do output pronto para features.
- Use dbt schema tests as quality gates — a failing test should halt the pipeline, not produce corrupted downstream data.Use testes de schema do dbt como quality gates — um teste falhando deve parar o pipeline, não produzir dados corrompidos downstream.
- Pass logical dates explicitly through orchestration tasks to enable safe, isolated backfills.Passe datas lógicas explicitamente pelas tarefas de orquestração para habilitar backfills seguros e isolados.
- Reprocess a lookback window on every run to absorb late-arriving records without special-case logic.Reprocesse uma janela de lookback em cada execução para absorver registros tardios sem lógica de caso especial.
- Monitor record counts, null rates, and dead letter volumes — these catch data problems days before model degradation becomes visible.Monitore contagens de registros, taxas de nulo e volumes de dead letter — eles capturam problemas de dados dias antes que a degradação do modelo fique visível.