Pipeline de dados com Spark e Delta Lake

Um teste prático de ingestão, processamento e orquestração com ferramentas open-source.

6 min de leitura
Pipeline de dados com Spark e Delta Lake

Objetivo

Há um tempo venho trabalhando com o ecossistema do Google Cloud, o que me afastou de algumas ferramentas comuns na engenharia de dados. Resolvi então desenferrujar o conhecimento em Apache Spark, Delta Lake e GitHub Actions.

Usei o Claude Code como guia, fazendo do projeto uma espécie de aula prática que agilizou o desenvolvimento e reforçou alguns conceitos. Com o avanço da IA, criar soluções ficou cada vez mais simples. Ainda assim, conhecer as tecnologias por trás segue tendo valor para debater, questionar e direcionar o que se entrega.

Defini três itens para o teste: um dataset público, um pipeline capaz de rodar de forma automática e um resultado final que agregasse o mínimo de valor. Por trabalhar com logística e custos de transporte, escolhi os preços de combustível no mercado americano.

O que ela faz

Toda terça à tarde, um pipeline obtém os preços do WTI (West Texas Intermediate, a principal referência de petróleo bruto americano) e os preços do diesel na EIA (US Energy Information Administration).

Os dados passam por uma arquitetura medallion em Apache Spark e Delta Lake. Bronze armazena o dado bruto, silver entrega o dado limpo e gold consolida o que vai para consumo. No fim, um snapshot JSON é publicado, gerando dados para este dashboard, com mais de 20 anos de dados históricos (sendo atualizado semanalmente).

O dashboard mostra como o diesel varia ao longo do tempo, sua relação com o WTI e os principais choques de preço nos últimos anos. As duas séries são públicas: WTI via yfinance e diesel via EIA Open Data API, gratuita mediante API key. O código do pipeline está disponível no GitHub.

Como funciona

A definição da stack levou em conta o que cada ferramenta agrega ao projeto. O PySpark contribui com schemas tipados e window functions, recursos que auxiliam no crescimento do dataset sem necessidade de reescrita. O Delta Lake oferece o overwrite idempotente, eliminando a necessidade de uma camada externa de coordenação. O GitHub Actions executa a orquestração, registrando os logs como trilha de auditoria.

CamadaFerramentaVersão
ComputeApache Spark (PySpark)4.1.1
StorageDelta Lake4.2.0
OrquestraçãoGitHub Actionsn/a
Testespytest9.x
LinguagemPython3.11

Apache Spark

O Apache Spark é um motor de processamento para grandes volumes de dados. No contexto do projeto, mesmo com volume modesto, ele agrega valor por dois motivos: schemas tipados, que protegem o pipeline contra mudanças silenciosas nas fontes (origens dos dados), e window functions, que permitem alinhar séries de granularidades distintas no mesmo eixo temporal e calcular operações como lags e correlações de forma direta.

A primeira aplicação ocorre no writer da camada bronze, que materializa um schema tipado antes de qualquer escrita no Delta Lake. Caso o yfinance ou a EIA alterem uma coluna ou um tipo de dado sem aviso, a execução seguinte falha imediatamente, evitando a gravação de histórico corrompido.

WTI_BRONZE_SCHEMA = StructType([
    StructField("date", DateType(), True),
    StructField("close", DoubleType(), True),
    StructField("volume", LongType(), True),
    StructField("ingestion_timestamp", TimestampType(), True),
    StructField("ingestion_date", DateType(), True),
    # ...
])

spark_df = spark.createDataFrame(pandas_df, schema=WTI_BRONZE_SCHEMA)

O segundo ponto é a combinação das séries em um mesmo eixo temporal. O WTI é diário, enquanto o diesel é semanal. Para que ambos sejam comparáveis, o valor do diesel precisa ser propagado para todos os dias úteis do WTI. Uma window function com last(..., ignorenulls=True) resolve a operação de forma direta:

window = Window.orderBy("date").rowsBetween(Window.unboundedPreceding, Window.currentRow)
filled = joined.withColumn(
    "diesel_usd_gal",
    last("price_usd_gal", ignorenulls=True).over(window),
)

Delta Lake

O Delta Lake é uma camada de armazenamento sobre arquivos Parquet que adiciona transações ACID, versionamento e upserts. ACID quer dizer Atomic (tudo-ou-nada), Consistent (dados sempre válidos), Isolated (sem interferir em leituras paralelas) e Durable (sobrevive a falhas).

No projeto, dois recursos importam: o replaceWhere, que torna a escrita do bronze idempotente, e o time travel, que versiona cada ingestão:

today = datetime.now(timezone.utc).date()

(
    spark_df.write
    .format("delta")
    .mode("overwrite")
    .option("replaceWhere", f"ingestion_date = '{today}'")
    .partitionBy("ingestion_date")
    .save(WTI_BRONZE_PATH)
)

Se a execução falhar ou for disparada novamente no mesmo dia, a tabela permanece íntegra, sem acumular duplicatas. A mesma garantia em um arquivo Parquet puro exigiria path temporário, rename de diretórios e algum mecanismo de lock.

As camadas silver e gold usam full overwrite, ou seja, são reescritas por inteiro a cada execução. Existe a alternativa incremental, que recalcula apenas o que mudou, mas envolve rastrear o estado entre as execuções e gerenciar checkpoints. Como neste projeto o payload acaba ficando de 10 MB, esse esforço extra não compensa: reescrever tudo é simples, rápido e elimina qualquer chance de inconsistência entre as execuções.

De quebra, ganhamos o recurso de time travel gratuitamente: a cada escrita, o Delta Lake registra uma nova versão da tabela e mantém as anteriores acessíveis por número ou timestamp. Na camada bronze, isso significa que qualquer execução passada pode ser consultada como se fosse o estado atual, útil para auditoria caso seja preciso voltar a um ponto específico.

GitHub Actions

O GitHub Actions é o serviço de automação do GitHub, com ambientes de execução gratuitos para repositórios públicos. No projeto, utilizamos um único arquivo de workflow capaz de gerenciar o agendamento, testes e deploy.

O cron dispara toda terça às 15:00 UTC:

on:
    schedule:
        - cron: '0 15 * * 2'
    workflow_dispatch:

O Spark depende de bibliotecas Java para integrar com o Delta Lake, e essas dependências são baixadas pelo Ivy (gerenciador de dependências do ecossistema Java/Scala) na primeira execução. Como o processo leva alguns minutos, vale cachear o diretório ~/.ivy2 entre execuções para reduzir o tempo a segundos:

- name: Cache Ivy dependencies
  uses: actions/cache@v4
  with:
      path: ~/.ivy2
      key: ivy2-${{ runner.os }}-${{ hashFiles('pyproject.toml') }}

Uma decisão importante foi posicionar os testes antes do pipeline, com fail-fast. Dessa forma, se algo regrediu na lógica do gold, a execução é interrompida antes de qualquer escrita no Delta Lake e nenhum dado corrompido chega ao dashboard:

- name: Run tests
  run: pytest tests/ -v

- name: Run pipeline (bronze -> silver -> gold -> export)
  env:
      EIA_API_KEY: ${{ secrets.EIA_API_KEY }}
  run: |
      python -m pipeline.bronze
      python -m pipeline.silver
      python -m pipeline.gold
      python -m pipeline.export

Considerações finais

Ao final do exercício, a stack entregou o que se esperava dela. Spark e Delta Lake cuidaram do dado com tipos e idempotência, e o GitHub Actions concentrou o ciclo de execução em um único workflow. O conjunto roda de forma autônoma, sem custo de infraestrutura.

A intenção era desenferrujar conceitos, e o resultado foi mais útil do que o esperado: a IA acelera bastante a parte mecânica, mas continua dependendo de decisões humanas para guiar arquitetura, escolhas técnicas e ajustes finos. Conhecer as ferramentas segue sendo o que diferencia o que é entregue.

O código do pipeline está disponível no GitHub e o dashboard está acessível para consulta.

Obrigado pela leitura.


Engenharia de dadosApache SparkDelta LakeGitHub ActionsPython
CompartilharLinkedInX