Como aprendi Apache Spark — Parte 17: Orquestração, Airflow, CI/CD e DataOps
Nota histórica: esta série revisita minha trajetória em Engenharia de Dados iniciada em 2012 e conecta seus fundamentos às práticas modernas. Apache Airflow, DataOps e as ferramentas atuais de CI/CD passaram por evoluções importantes desde então.
Nos primeiros capítulos desta série, concentramos nossa atenção em como o Apache Spark executa processamento distribuído.
Estudamos Driver, Executors, Jobs, Stages, Tasks, partições, shuffle, memória, formatos de armazenamento, Lakehouse e Structured Streaming.
Na Parte 16, exploramos Kafka, Debezium e CDC para construir pipelines incrementais.
Mas existe uma diferença enorme entre executar um Job Spark manualmente e manter uma plataforma de dados funcionando todos os dias.
Imagine que temos cinco processos:
- Extrair dados do PostgreSQL.
- Validar os arquivos recebidos.
- Executar transformações Spark.
- Atualizar tabelas analíticas.
- Publicar métricas para consumidores.
Executar cada processo individualmente pode funcionar durante o desenvolvimento.
Mas o que acontece quando a extração falha?
E se o Spark terminar, mas a qualidade dos dados estiver abaixo do esperado?
Como reprocessar somente um dia?
Como impedir que duas execuções concorrentes publiquem resultados inconsistentes?
Como garantir que uma alteração no código não quebre os pipelines existentes?
Essas perguntas nos levam à orquestração, CI/CD e DataOps.
1. Processamento não é orquestração
O Apache Spark é um mecanismo de processamento distribuído.
Ele executa transformações sobre dados.
O Apache Airflow é uma plataforma de orquestração de workflows.
Ele organiza dependências, agendamentos, execuções e estados de tarefas.
Essa diferença é fundamental.
APACHE AIRFLOW
|
Orchestrates Tasks
|
+----------+----------+
| | |
v v v
Ingestion Spark Job Quality Check
|
v
Distributed Data
Processing
O Airflow não substitui o Spark.
E o Spark não precisa assumir sozinho todas as responsabilidades de coordenação de workflows.
Em uma arquitetura corporativa, as duas tecnologias podem trabalhar juntas.
2. O problema dos scripts isolados
Imagine um pipeline executado por cron:
0 2 * * * python processar_vendas.py
Essa configuração pode ser suficiente para um processo simples.
Mas considere uma arquitetura com dependências:
Extract Customers
|
v
Extract Orders
|
v
Transform Orders
|
v
Validate Quality
|
v
Publish Analytics
Se uma tarefa falhar, precisamos controlar quais etapas podem continuar.
Também precisamos registrar:
- Qual execução falhou.
- Qual tarefa falhou.
- Quando ocorreu.
- Quantas tentativas foram realizadas.
- Quais dados foram processados.
- Qual versão do código estava em execução.
Um orquestrador ajuda a organizar essas responsabilidades.
3. O que é Apache Airflow?
Apache Airflow é uma plataforma para desenvolver, agendar e monitorar workflows.
Seu modelo é baseado em DAGs — Directed Acyclic Graphs.
Uma DAG representa tarefas e dependências sem ciclos direcionados.
Exemplo:
START
|
v
EXTRACT
|
v
TRANSFORM
|
v
VALIDATE
|
v
PUBLISH
|
v
END
O Airflow controla a execução dessas tarefas conforme a definição do workflow.
É importante lembrar que a API e a arquitetura interna do Airflow evoluíram entre versões.
Neste artigo, os exemplos utilizam a API moderna do Airflow 3, com imports do pacote airflow.sdk, e devem ser executados em um ambiente compatível.
4. DAG, Task e DAG Run
Três conceitos precisam ficar claros.
DAG
Representa o workflow e suas dependências.
Task
Representa uma unidade de trabalho dentro da DAG.
DAG Run
Representa uma instância de execução do workflow.
Conceitualmente:
DAG: daily_sales_pipeline
|
+-- DAG Run: 2026-11-04
| |
| +-- extract
| +-- transform
| +-- validate
|
+-- DAG Run: 2026-11-05
|
+-- extract
+-- transform
+-- validate
Cada execução possui seu próprio contexto e estado.
Isso permite acompanhar histórico, falhas e reprocessamentos.
5. Uma DAG simples
Vamos construir uma DAG utilizando TaskFlow API.
from datetime import datetime
from airflow.sdk import dag, task
@dag(
dag_id="spark_pipeline_example",
schedule="@daily",
start_date=datetime(2026, 11, 1),
catchup=False,
tags=["spark", "dataops"],
)
def spark_pipeline_example():
@task
def extract():
print("Extraindo dados")
@task
def transform():
print("Transformando dados")
@task
def validate():
print("Validando dados")
@task
def publish():
print("Publicando resultados")
extraction = extract()
transformation = transform()
validation = validate()
publication = publish()
extraction >> transformation
transformation >> validation
validation >> publication
spark_pipeline_example()
Esse exemplo demonstra dependências.
As funções apenas imprimem mensagens; ainda não executam Spark.
Em produção, cada tarefa deve possuir uma responsabilidade clara e observável.
6. Como o Airflow executa tarefas?
Uma instalação Airflow pode conter componentes responsáveis por:
- Gerenciamento das DAGs.
- Agendamento.
- Execução das tarefas.
- Persistência de metadados.
- Interface de acompanhamento.
A arquitetura específica depende do executor e da versão.
Conceitualmente:
AIRFLOW DAG
|
v
SCHEDULER
|
v
TASK EXECUTION
|
+----------+----------+
| | |
v v v
Task A Task B Task C
|
v
SPARK CLUSTER
O Airflow decide quando e sob quais dependências uma tarefa deve executar.
O Spark continua responsável pelo processamento distribuído.
Essa separação evita confundir orquestração com computação.
7. Integrando Airflow e Spark
Existem diferentes maneiras de executar aplicações Spark a partir do Airflow.
Uma opção é utilizar SparkSubmitOperator, disponibilizado pelo provider Apache Spark.
Exemplo ilustrativo:
from datetime import datetime
from airflow.sdk import dag
from airflow.providers.apache.spark.operators.spark_submit import (
SparkSubmitOperator
)
@dag(
dag_id="daily_sales_spark",
schedule="@daily",
start_date=datetime(2026, 11, 1),
catchup=False,
tags=["spark"],
)
def daily_sales_spark():
process_sales = SparkSubmitOperator(
task_id="process_sales",
application="/opt/spark/jobs/process_sales.py",
conn_id="spark_default",
application_args=[
"--processing-date",
"{{ ds }}"
],
verbose=False,
)
daily_sales_spark()
Esse exemplo pressupõe:
- Airflow instalado.
- Provider Spark compatível.
- Conexão
spark_defaultconfigurada. -
spark-submitdisponível no ambiente apropriado. - Aplicação acessível ao processo executor.
A configuração real depende do deployment.
Em Kubernetes, por exemplo, podemos utilizar estratégias diferentes de submissão e gerenciamento de aplicações.
8. O papel do spark-submit
O spark-submit é uma interface utilizada para submeter aplicações Spark.
Exemplo:
spark-submit \
--master local[*] \
--name sales-processing \
process_sales.py \
--processing-date 2026-11-05
Em ambientes distribuídos, a configuração pode incluir:
- Cluster manager.
- Deploy mode.
- Recursos do Driver.
- Recursos dos Executors.
- Dependências.
- Configurações de rede.
- Segurança.
O Airflow pode coordenar a submissão.
Mas o dimensionamento do Spark continua sendo uma responsabilidade de engenharia.
Um DAG bem configurado não corrige automaticamente um Job com shuffle excessivo ou particionamento inadequado.
9. Data Interval e Logical Date
Um erro comum é utilizar a data atual do servidor para determinar quais dados processar.
Imagine uma DAG diária que deveria processar os dados de 5 de novembro.
Por algum motivo, ela só foi executada em 7 de novembro.
Se o código utilizar:
from datetime import date
processing_date = date.today()
poderá processar o dia errado.
O Airflow fornece informações de contexto sobre a execução e seu intervalo de dados.
Para workflows agendados por intervalo, devemos definir claramente qual período cada execução representa.
Em vez de depender do relógio da máquina, podemos passar parâmetros associados ao intervalo da DAG.
Por exemplo, em uma tarefa com templates compatíveis:
{{ data_interval_start }}
{{ data_interval_end }}
A semântica exata depende do timetable utilizado.
O princípio é simples:
A data lógica de processamento deve ser definida pelo contrato do pipeline, não pelo momento em que o código foi iniciado.
10. Retries
Falhas temporárias acontecem.
Uma API pode ficar indisponível.
Uma conexão pode ser interrompida.
Um serviço pode retornar erro temporário.
O Airflow permite configurar tentativas adicionais.
from datetime import timedelta
from airflow.sdk import task
@task(
retries=3,
retry_delay=timedelta(minutes=5),
)
def extract_data():
print("Executando extração")
Mas retries precisam ser utilizados com cuidado.
Repetir uma tarefa não garante que sua execução seja segura.
Se a tarefa inserir dados novamente sem controle, poderá gerar duplicidades.
Por isso, retries e idempotência devem ser projetados em conjunto.
11. Idempotência
Uma operação idempotente pode ser repetida sem produzir efeitos adicionais indevidos após a primeira aplicação bem-sucedida.
Imagine uma tarefa que grava dados em uma partição diária.
Uma abordagem potencialmente segura é substituir integralmente a partição correspondente à data lógica, desde que a operação de publicação seja implementada corretamente.
Outra estratégia é utilizar uma tabela transacional com MERGE e chaves de negócio.
Conceitualmente:
Execution 1
|
v
Publish Partition 2026-11-05
Execution 2 (Retry)
|
v
Publish Same Logical Result
O objetivo é que o retry não duplique registros.
Entretanto, sobrescrever diretórios Parquet diretamente não garante atomicidade de publicação.
Para produção, devemos considerar os recursos transacionais do destino.
12. Backfill
Um backfill é o processamento ou reprocessamento de períodos históricos.
Imagine que um pipeline foi criado hoje, mas precisamos processar os últimos 90 dias.
Ou que descobrimos um bug que afetou determinadas datas.
2026-11-01 → Reprocess
2026-11-02 → Reprocess
2026-11-03 → Reprocess
2026-11-04 → Reprocess
2026-11-05 → Reprocess
O Airflow oferece mecanismos de backfill e gerenciamento de execuções históricas, com comportamento dependente da versão e do timetable.
Mas o pipeline precisa estar preparado.
Um backfill confiável exige:
- Parâmetros temporais explícitos.
- Idempotência.
- Controle de concorrência.
- Capacidade de processamento.
- Validação.
- Estratégia de publicação.
Não basta disparar várias execuções antigas.
13. Concorrência e dependências
Imagine que dois DAG Runs tentam atualizar a mesma tabela.
DAG Run A ──> MERGE Orders
|
X
|
DAG Run B ──> MERGE Orders
Dependendo do destino e da estratégia de escrita, podem ocorrer conflitos.
O Airflow oferece controles como:
- Limites de DAG Runs ativos.
- Pools.
- Limites de tarefas.
- Dependências entre tarefas.
Exemplo:
@dag(
dag_id="controlled_pipeline",
schedule="@daily",
start_date=datetime(2026, 11, 1),
catchup=False,
max_active_runs=1,
)
def controlled_pipeline():
...
Esse limite reduz a concorrência entre execuções da mesma DAG.
Mas não protege automaticamente contra escritores externos ou outras DAGs.
A consistência do destino continua exigindo mecanismos adequados.
14. Orquestração vs. Event-Driven Processing
Nem todo pipeline precisa de um agendamento diário.
Existem diferentes padrões.
Time-based
Executar em intervalos definidos.
Dependency-based
Executar após a conclusão de outra tarefa.
Data-aware
Executar quando determinadas condições ou atualizações de dados estiverem disponíveis.
Event-driven
Responder a eventos externos.
O Airflow oferece recursos para workflows orientados a dados, incluindo Assets nas versões modernas.
Mas devemos distinguir orquestração de workflows e processamento contínuo de eventos.
Uma aplicação Spark Structured Streaming pode permanecer ativa continuamente.
Nesse cenário, o Airflow pode coordenar seu deployment, verificações ou ciclo operacional, sem necessariamente iniciar um novo Job a cada evento.
15. O que é DataOps?
DataOps é uma abordagem que aplica princípios de automação, colaboração, testes, observabilidade e melhoria contínua ao ciclo de vida dos dados.
Seu objetivo é aumentar a confiabilidade e a velocidade de entrega de produtos de dados.
Conceitualmente:
DATAOPS LIFECYCLE
Plan
|
v
Develop
|
v
Test
|
v
Deploy
|
v
Operate
|
v
Monitor
|
v
Improve
|
+----------> Plan
DataOps não é uma ferramenta específica.
É uma combinação de práticas, processos e responsabilidades.
16. Versionamento com Git
Um pipeline corporativo precisa de versionamento.
Uma estrutura possível:
spark-data-platform/
|
+-- dags/
| +-- daily_sales.py
|
+-- src/
| +-- pipelines/
| | +-- sales.py
| |
| +-- transformations/
| | +-- orders.py
| |
| +-- quality/
| +-- validations.py
|
+-- tests/
| +-- test_orders.py
| +-- test_quality.py
|
+-- config/
| +-- dev.yaml
| +-- prod.yaml
|
+-- pyproject.toml
+-- README.md
Essa separação evita concentrar toda a lógica dentro dos arquivos DAG.
O ideal é manter a DAG como camada de orquestração.
A lógica de negócio e as transformações devem ser implementadas em módulos testáveis.
17. Evitando lógica pesada no parse da DAG
O Airflow precisa carregar e interpretar os arquivos das DAGs.
Por isso, não devemos executar operações pesadas durante a importação desses arquivos.
Evite:
# Exemplo do que NÃO fazer no parse da DAG
df = spark.read.parquet("/data/orders")
total = df.count()
Isso pode provocar processamento inesperado sempre que o arquivo for interpretado.
O correto é definir tarefas que executem o trabalho no momento apropriado.
DAG Definition
|
v
Task Scheduling
|
v
Task Execution
|
v
Spark Processing
Essa separação melhora previsibilidade e manutenção.
18. Testes unitários em PySpark
Vamos criar uma transformação simples.
from pyspark.sql import functions as F
def calcular_valor_liquido(df):
return df.withColumn(
"valor_liquido",
F.col("valor_bruto") - F.col("desconto")
)
Agora podemos testá-la.
def test_calcular_valor_liquido(spark):
entrada = spark.createDataFrame(
[
(1, 100.0, 10.0),
(2, 200.0, 20.0),
],
[
"pedido_id",
"valor_bruto",
"desconto",
],
)
resultado = calcular_valor_liquido(
entrada
)
valores = {
row["pedido_id"]: row["valor_liquido"]
for row in resultado.collect()
}
assert valores == {
1: 90.0,
2: 180.0,
}
Esse teste pressupõe uma fixture spark configurada no pytest.
A vantagem é validar a transformação sem executar toda a arquitetura.
19. Fixture Spark para pytest
Podemos criar uma fixture reutilizável.
import pytest
from pyspark.sql import SparkSession
@pytest.fixture(scope="session")
def spark():
session = (
SparkSession.builder
.master("local[2]")
.appName("PySparkTests")
.config(
"spark.ui.enabled",
"false",
)
.getOrCreate()
)
yield session
session.stop()
Essa fixture pode ser colocada em:
tests/conftest.py
Assim, diferentes testes podem compartilhar uma SparkSession durante a sessão de testes.
Em ambientes de CI, precisamos garantir compatibilidade entre Python, Java e Spark.
20. Testes de integração
Testes unitários verificam funções isoladas.
Mas precisamos também testar a integração entre componentes.
Por exemplo:
Read Input
|
v
Transform
|
v
Validate
|
v
Write Output
|
v
Read Output
|
v
Assert Results
Um teste de integração pode utilizar datasets pequenos e diretórios temporários.
O objetivo é validar o comportamento do pipeline completo em um ambiente controlado.
Para destinos transacionais, também devemos testar:
- MERGE.
- Reprocessamento.
- Idempotência.
- Evolução de schema.
- Falhas.
- Recuperação.
21. Data Quality no CI/CD
Na Parte 14, estudamos Data Quality e Data Contracts.
Agora podemos integrar esses conceitos ao processo de entrega.
Imagine um Pull Request que modifica uma transformação.
O pipeline de CI pode executar:
Pull Request
|
v
Code Formatting
|
v
Static Analysis
|
v
Unit Tests
|
v
Data Contract Tests
|
v
Integration Tests
|
v
Build Validation
Isso ajuda a identificar problemas antes do deploy.
Mas testes com dados sintéticos não substituem verificações de qualidade em produção.
Precisamos de validação antes e depois da implantação.
22. CI/CD para pipelines Spark
CI/CD significa:
Continuous Integration
Integração frequente de alterações com validações automatizadas.
Continuous Delivery / Deployment
Automação da preparação e, quando apropriado, da publicação de novas versões.
Uma arquitetura conceitual:
Developer
|
v
Git Repository
|
v
Pull Request
|
v
CI Pipeline
|
+-- Lint
+-- Tests
+-- Build
|
v
Artifact
|
v
Deployment
|
v
Airflow / Spark
O objetivo é tornar o processo repetível e auditável.
23. Exemplo de GitHub Actions
Podemos utilizar GitHub Actions para executar testes PySpark.
Exemplo simplificado:
name: Spark CI
on:
push:
branches:
- main
pull_request:
jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pyspark==3.5.5 pytest
- name: Run tests
run: |
pytest -q
Esse exemplo utiliza uma combinação de versões para fins didáticos.
Em um projeto real, devemos fixar e validar a matriz de compatibilidade, preferencialmente por arquivos de dependências e imagens versionadas.
Também é importante revisar periodicamente as versões das Actions e as políticas de segurança da organização.
24. Build once, deploy many
Uma boa prática é construir um artefato versionado e promovê-lo entre ambientes.
Source Code
|
v
Build Artifact
|
+----> Development
|
+----> Staging
|
+----> Production
Isso reduz diferenças entre ambientes.
O mesmo código pode receber configurações específicas para:
- Credenciais.
- Endpoints.
- Buckets.
- Catálogos.
- Recursos.
- Logs.
A configuração deve ser externa ao artefato sempre que apropriado.
Não devemos incorporar senhas ou tokens diretamente no código.
25. Ambientes Dev, Staging e Prod
Uma plataforma madura normalmente separa ambientes.
Development
Utilizado para desenvolvimento e experimentação.
Staging
Utilizado para validações de integração e testes próximos da produção.
Production
Utilizado para cargas reais e consumidores finais.
Conceitualmente:
DEV
|
v
TEST
|
v
STAGING
|
v
PRODUCTION
A quantidade de ambientes depende da organização.
Mas precisamos controlar diferenças de:
- Configuração.
- Dados.
- Permissões.
- Capacidade.
- Dependências.
- Versões.
Um pipeline que funciona localmente não está automaticamente pronto para produção.
26. Configuração por ambiente
Evite utilizar caminhos fixos no código.
Exemplo inadequado:
df.write.parquet(
"/home/user/projeto/output"
)
Uma alternativa é receber parâmetros.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--processing-date",
required=True,
)
parser.add_argument(
"--output-path",
required=True,
)
args = parser.parse_args()
Agora o pipeline pode ser executado com diferentes configurações.
spark-submit \
process_sales.py \
--processing-date 2026-11-05 \
--output-path /data/curated/sales
Em produção, os parâmetros devem ser validados antes do processamento.
27. Secrets Management
Credenciais não devem ficar armazenadas em arquivos versionados.
Evite:
password = "minha_senha"
Prefira mecanismos apropriados ao ambiente, como:
- Secret managers.
- Backends de secrets do Airflow.
- Identidades de workload.
- Credenciais temporárias.
- Variáveis de ambiente controladas, quando apropriado.
O objetivo é reduzir exposição e facilitar rotação.
Também devemos evitar registrar secrets em logs, exceções ou parâmetros de linha de comando quando isso puder expô-los.
28. Observabilidade de pipelines
Uma DAG verde não significa necessariamente que os dados estão corretos.
Precisamos acompanhar três dimensões.
Orchestration Health
- Estados das tarefas.
- Retries.
- Falhas.
- Duração.
- Atrasos de agendamento.
Processing Health
- Duração do Spark Job.
- Shuffle.
- Spill.
- Memória.
- CPU.
- Falhas de Executors.
Data Health
- Freshness.
- Volume.
- Completude.
- Duplicidade.
- Contratos.
- Quality Gates.
Conceitualmente:
OBSERVABILITY
|
+-----------+-----------+
| | |
v v v
AIRFLOW SPARK DATA
HEALTH HEALTH QUALITY
Essas informações precisam ser correlacionadas.
29. Logs estruturados
Logs são fundamentais para investigação.
Mas mensagens genéricas como:
Process failed
oferecem pouco contexto.
Uma mensagem estruturada pode incluir:
{
"pipeline": "daily_sales",
"run_id": "scheduled__example",
"processing_date": "2026-11-05",
"stage": "transform",
"status": "failed",
"error_type": "DataQualityError"
}
Em produção, os valores devem ser obtidos do contexto real da execução.
Logs estruturados facilitam busca, correlação e análise de incidentes.
Também devemos evitar registrar dados sensíveis desnecessariamente.
30. Alertas e incidentes
Imagine que um pipeline crítico falhe às 2h da manhã.
Precisamos definir:
- Quem recebe o alerta.
- Qual é a criticidade.
- Qual é o SLA.
- Como investigar.
- Como recuperar.
- Quando escalar.
- Como comunicar consumidores.
Uma estratégia pode classificar incidentes.
WARNING
|
v
Non-critical Degradation
ERROR
|
v
Pipeline Failure
CRITICAL
|
v
Business Data Unavailable
A classificação deve refletir o impacto real.
Nem toda falha técnica exige o mesmo nível de resposta.
31. SLO e SLA
Dois conceitos aparecem frequentemente em operação.
SLO — Service Level Objective
Objetivo interno de desempenho ou confiabilidade.
SLA — Service Level Agreement
Compromisso formal de serviço, quando aplicável.
Exemplo:
Dataset: daily_sales
SLO:
99% das publicações concluídas
até 07:00.
Podemos acompanhar:
- Disponibilidade.
- Freshness.
- Tempo de processamento.
- Taxa de falhas.
- Tempo de recuperação.
O importante é definir indicadores verificáveis.
32. Backfill seguro
Vamos imaginar que precisamos reprocessar sete dias.
Uma abordagem insegura seria disparar todas as execuções sem avaliar concorrência.
Uma abordagem controlada considera:
Validate Historical Range
|
v
Estimate Processing Cost
|
v
Check Source Availability
|
v
Control Concurrency
|
v
Execute Backfill
|
v
Validate Results
|
v
Publish
Também devemos considerar que os dados de origem podem ter mudado desde a execução original.
Reprocessar uma data histórica não garante reproduzir exatamente o mesmo resultado se as fontes não forem versionadas ou preservadas.
Essa é uma razão importante para manter histórico e snapshots quando necessário.
33. Deploy e rollback
Imagine que uma nova versão do pipeline produz resultados incorretos.
Precisamos ter uma estratégia de recuperação.
Isso pode envolver:
- Reverter o código.
- Reimplantar o artefato anterior.
- Recuperar uma versão de tabela.
- Reprocessar períodos afetados.
- Validar o resultado.
- Comunicar consumidores.
Mas rollback de código e rollback de dados são operações diferentes.
CODE ROLLBACK
|
v
Restore Previous Application Version
DATA RECOVERY
|
v
Restore / Rebuild Correct Dataset
Uma plataforma confiável precisa planejar ambos.
34. Orquestração de pipelines streaming
Nem todo pipeline Spark termina após processar um intervalo.
Uma aplicação Structured Streaming pode permanecer ativa por longos períodos.
Nesse cenário, precisamos definir:
- Como iniciar a aplicação.
- Como detectar falhas.
- Como reiniciar.
- Como preservar checkpoints.
- Como atualizar versões.
- Como realizar shutdown controlado.
- Como monitorar lag e estado.
Airflow pode participar dessa arquitetura, mas não precisa ser o supervisor principal de um serviço streaming de longa duração.
Em determinados ambientes, Kubernetes ou outro mecanismo de gerenciamento de aplicações pode assumir essa responsabilidade.
A decisão depende do modelo operacional.
35. Arquitetura corporativa de referência
Vamos conectar tudo o que estudamos.
DATA SOURCES
|
v
INGESTION
|
v
RAW STORAGE
|
v
SPARK PROCESSING
|
v
DATA QUALITY
|
v
LAKEHOUSE TABLES
|
v
ANALYTICS
ORCHESTRATION LAYER
|
v
AIRFLOW
DELIVERY LAYER
|
v
GIT + CI/CD
GOVERNANCE LAYER
|
v
CONTRACTS + LINEAGE
OBSERVABILITY LAYER
|
v
LOGS + METRICS + ALERTS
As camadas de orquestração, entrega, governança e observabilidade atuam transversalmente sobre os pipelines.
Essa arquitetura separa responsabilidades e facilita manutenção.
36. Checklist para produção
Antes de colocar um pipeline Spark em produção, eu verificaria:
| Área | Verificação |
|---|---|
| Código | Versionado e revisado |
| Testes | Unitários e integração |
| Contratos | Schemas e regras definidos |
| Qualidade | Quality Gates configurados |
| Orquestração | Dependências explícitas |
| Retries | Seguros e controlados |
| Idempotência | Reprocessamento validado |
| Backfill | Estratégia documentada |
| Concorrência | Limites definidos |
| Segurança | Secrets protegidos |
| Deploy | Artefatos versionados |
| Observabilidade | Logs, métricas e alertas |
| Recuperação | Procedimento testado |
| Custos | Recursos e execução monitorados |
| Ownership | Responsáveis definidos |
Esse checklist não substitui uma avaliação de arquitetura.
Mas ajuda a identificar lacunas antes do deployment.
37. Erros comuns
Alguns erros aparecem repetidamente.
Colocar toda a lógica dentro da DAG.
Isso dificulta testes e manutenção.
Executar Spark durante o parse da DAG.
Pode gerar processamento inesperado.
Utilizar date.today() como referência de processamento.
Pode quebrar backfills e retries.
Configurar retries sem idempotência.
Pode produzir duplicidades.
Não controlar concorrência.
Pode causar conflitos no destino.
Não versionar dependências.
Pode produzir comportamentos diferentes entre ambientes.
Não separar código de configuração.
Dificulta deploy e manutenção.
Tratar DAG verde como garantia de qualidade.
Dados incorretos podem ser publicados.
Não planejar rollback de dados.
Reverter código pode não corrigir resultados já gravados.
Não monitorar custos.
Pipelines podem continuar funcionando enquanto se tornam economicamente inviáveis.
38. Como eu estruturaria um projeto Spark + Airflow
Para uma plataforma corporativa, começaria com uma organização simples.
data-platform/
|
+-- airflow/
| +-- dags/
|
+-- spark/
| +-- jobs/
| +-- transformations/
| +-- quality/
|
+-- contracts/
|
+-- tests/
| +-- unit/
| +-- integration/
|
+-- configs/
| +-- dev/
| +-- staging/
| +-- prod/
|
+-- infrastructure/
|
+-- observability/
|
+-- docs/
|
+-- .github/
| +-- workflows/
|
+-- pyproject.toml
+-- README.md
Essa estrutura não é obrigatória.
Ela representa uma forma de separar responsabilidades.
O objetivo é permitir que código, infraestrutura, contratos e operação evoluam de maneira controlada.
39. A principal lição deste capítulo
Até aqui, aprendemos como o Spark processa dados.
Agora entendemos que um pipeline corporativo precisa de um ciclo de vida completo.
DEVELOP
|
v
TEST
|
v
VERSION
|
v
DEPLOY
|
v
ORCHESTRATE
|
v
MONITOR
|
v
RECOVER
|
v
IMPROVE
O princípio mais importante é:
Um Job Spark que funciona é apenas o começo. Engenharia de Dados em produção exige automação, testes, rastreabilidade, observabilidade e recuperação.
Airflow ajuda a coordenar workflows.
CI/CD ajuda a entregar alterações com segurança.
DataOps conecta essas práticas ao ciclo de vida dos dados.
40. Como penso hoje
Quando avalio um pipeline Spark, não pergunto apenas:
O processamento terminou?
Quero saber:
- Qual versão do código foi executada?
- Qual intervalo de dados foi processado?
- Quais contratos foram validados?
- O resultado passou pelos Quality Gates?
- Como o pipeline reage a falhas?
- Os retries são seguros?
- Como executamos backfills?
- Como controlamos concorrência?
- Como monitoramos performance?
- Como recuperamos uma versão incorreta?
- Quanto custa manter esse pipeline?
- Quem é responsável por ele?
Essas perguntas representam a diferença entre um script funcional e um produto de dados operável.
Próximo artigo
Agora que entendemos como orquestrar, testar e operar pipelines Spark, vamos aprofundar um dos temas mais importantes de plataformas corporativas:
Performance Tuning e otimização avançada de Apache Spark.
Vamos explorar:
- Catalyst Optimizer.
- Tungsten.
- Adaptive Query Execution.
- Query Plans.
- Broadcast Joins.
- Skew Join Optimization.
- Shuffle Partitions.
- Dynamic Partition Pruning.
- Caching.
- Memory Tuning.
- Spark UI.
- Métricas de execução.
- Benchmarking.
- Estratégias de otimização em produção.
A pergunta central será:
Como identificar gargalos reais e otimizar aplicações Spark utilizando evidências, planos de execução e métricas?
Série: Como aprendi Apache Spark
← Parte 16: Apache Kafka, CDC e pipelines incrementais
Próximo: Parte 18 — Performance Tuning avançado: Catalyst, AQE, Spark UI e otimização de Jobs
Top comments (0)