DEV Community

Cover image for Como aprendi Apache Spark — Parte 17: Airflow, CI/CD e DataOps
renanpyd
renanpyd

Posted on

Como aprendi Apache Spark — Parte 17: Airflow, CI/CD e DataOps

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:

  1. Extrair dados do PostgreSQL.
  2. Validar os arquivos recebidos.
  3. Executar transformações Spark.
  4. Atualizar tabelas analíticas.
  5. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Esse exemplo pressupõe:

  • Airflow instalado.
  • Provider Spark compatível.
  • Conexão spark_default configurada.
  • spark-submit disponí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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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():
    ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
    )
Enter fullscreen mode Exit fullscreen mode

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,
    }
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Essa fixture pode ser colocada em:

tests/conftest.py
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
)
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Essas informações precisam ser correlacionadas.


29. Logs estruturados

Logs são fundamentais para investigação.

Mas mensagens genéricas como:

Process failed
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
DATA RECOVERY
      |
      v
Restore / Rebuild Correct Dataset
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)