Dados brutos → produto de dados confiável

Apostila completa de Engenharia de Dados

Toda decisão orientada por dados — e todo agente de IA que presta — depende de um dado que chegou íntegro, no horário certo e no formato certo. Esta apostila constrói a disciplina que garante isso: da primeira ingestão em batch ao lakehouse transacional, da orquestração de DAGs ao streaming exactly-once, sempre com o olho no que empresas realmente pedem em vagas e entrevistas.

10 módulosSQL · Python · dbt · AirflowSpark · Kafka · FlinkIceberg · Delta LakeBoxes de entrevistaExercícios com gabarito
MÓDULO 01 · BÁSICO

O que é Engenharia de Dados e onde ela vive

Objetivo: entender o problema que a Engenharia de Dados resolve, como o papel se distingue de analista, cientista e ML engineer, e que forma tem a stack moderna.

1.1 O problema: dado existe, dado confiável não

Quase toda empresa já tem dados: num banco de produção, numa planilha, num CRM SaaS, nos logs de um servidor, num arquivo que alguém exporta toda segunda-feira. O que quase nenhuma empresa tem de graça é esse dado reunido, limpo, histórico, versionado e disponível no momento em que uma decisão precisa dele.

Engenharia de Dados é a disciplina que projeta e opera os sistemas que transformam dados dispersos e crus em produtos de dados confiáveis: tabelas, APIs e feeds que um analista, um dashboard, um modelo de ML ou um agente de IA podem consumir sem precisar saber de onde aquilo veio nem se está certo.

💡 Conceito-chave

O produto do engenheiro de dados não é código — é confiança. Um pipeline que roda mas entrega número errado é pior do que pipeline nenhum, porque alguém vai decidir com base nele. Todo o resto da apostila (idempotência, testes, contratos, observabilidade) existe para tornar essa confiança verificável.

1.2 Quem faz o quê: o time de dados

PapelPergunta que respondeFerramentas típicas
Engenheiro de Dados"Como faço esse dado chegar limpo, no horário, todo dia, sem eu precisar acordar de madrugada?"SQL, Python, dbt, Airflow/Dagster, Spark, Kafka, warehouse/lakehouse, Terraform
Analytics Engineer"Como transformo as tabelas cruas em modelos de negócio que o time de BI entende?"dbt, SQL, warehouse, ferramenta de BI
Analista de Dados / BI"O que os dados dizem sobre o negócio?"SQL, BI (Power BI, Looker, Metabase), planilha
Cientista de Dados"Consigo prever / explicar / segmentar isso?"Python, notebooks, scikit-learn, estatística
ML / AI Engineer"Como coloco esse modelo/agente em produção e o mantenho?"Python, feature store, MLOps/LLMOps, APIs
Platform / DataOps Engineer"Como o time inteiro roda isso com segurança, custo controlado e sem reinventar a roda?"Kubernetes, Terraform, CI/CD, IAM, observabilidade

Em empresas pequenas, uma pessoa faz vários desses papéis (o famoso "time de dados de 1"). Em empresas grandes, são times separados. O engenheiro de dados fica na fronteira entre os sistemas de produção e o mundo analítico/IA — é quem conhece os dois lados.

1.3 A stack de dados moderna, em uma imagem mental

          FONTES                 INGESTÃO            ARMAZENAMENTO         TRANSFORMAÇÃO           CONSUMO
  ┌───────────────────┐   ┌──────────────┐   ┌──────────────────┐   ┌───────────────┐   ┌────────────────┐
  Banco OLTP (Postgres) ─▶  CDC/Debezium  ─▶
  APIs SaaS (Stripe...) ─▶  Airbyte/ELT   ─▶   Data Lake (S3) +      dbt / Spark SQL      BI / dashboards
  Eventos de app        ─▶  Kafka         ─▶   Lakehouse (Iceberg/  ─▶ modelagem +      ─▶  ML / feature store
  Arquivos (CSV/Parquet)─▶  batch load    ─▶   Delta) ou Warehouse    testes + docs        Reverse ETL → SaaS
  Logs                  ─▶  Fluent Bit    ─▶   (BigQuery/Snowflake)                        Agentes de IA / RAG
  └───────────────────┘   └──────────────┘   └──────────────────┘   └───────────────┘   └────────────────┘
              ▲                                        ▲                       ▲
        Orquestração (Airflow / Dagster / Prefect) coordena tudo: agenda, dependências, retries, backfill
        Governança & observabilidade (catálogo, lineage, testes de qualidade, custo) cobre todas as etapas

Essa é a espinha dorsal de praticamente todo sistema analítico sério em 2026. Cada módulo desta apostila aprofunda uma dessas caixas. Note dois blocos transversais, que atravessam todas as etapas: orquestração (Módulo 6) e governança/observabilidade (Módulo 9).

1.4 As competências de base (o que assumir daqui pra frente)

💼 Mercado de trabalho

Engenharia de Dados é uma das áreas de dados com melhor relação oferta/demanda: há mais vagas do que gente com a combinação "SQL forte + Python + orquestração + um pouco de cloud". Diferente de Ciência de Dados (saturada no nível júnior), Engenharia de Dados raramente é primeira vaga — costuma vir de quem já foi analista, dev backend ou de infra. A boa notícia: quase tudo é aprendível com projeto prático, e o ferramental (dbt, Airflow, DuckDB, Spark) roda de graça na sua máquina.

✏️ Exercício 1 — De quem é a tarefa?

Classifique cada tarefa como Engenharia de Dados (ED), Analytics Engineering (AE), Análise/BI (BI) ou Ciência de Dados (DS): (a) montar um job diário que copia a base de pedidos do Postgres para o lakehouse; (b) construir o modelo dim_clientes com regras de negócio a partir das tabelas cruas; (c) descobrir por que a receita caiu 8% em março; (d) treinar um modelo de churn; (e) garantir que o job de (a) avise no Slack quando o volume de linhas cair 50%.

Gabarito: (a) ED — ingestão e pipeline; (b) AE — transformação/modelagem de negócio, tipicamente em dbt; (c) BI — análise exploratória; (d) DS — modelagem preditiva; (e) ED — observabilidade de pipeline. Note que (b) e (e) são as fronteiras onde os papéis se misturam.

MÓDULO 02 · BÁSICO

Fundamentos: batch × streaming, ETL × ELT, OLTP × OLAP e formatos de arquivo

Objetivo: fixar os quatro pares conceituais que estruturam todas as decisões de arquitetura de dados — e reconhecer os formatos de arquivo que fazem a diferença entre um pipeline barato e um caro.

2.1 Batch × Streaming

Batch (lote)Streaming (fluxo)
UnidadeUm conjunto de registros processado de uma vez (a "carga das 3h")Um registro (ou micro-lote) processado assim que chega
LatênciaMinutos a horasMilissegundos a segundos
ComplexidadeBaixa: começa, termina, tem resultadoAlta: processo sempre ligado, precisa lidar com dado atrasado, fora de ordem, reprocessamento
CustoMenor e previsívelMaior: infraestrutura 24/7
Use quandoA decisão pode esperar até a próxima carga (relatórios, ML de treino, faturamento)A decisão é agora (detecção de fraude, alerta operacional, dashboard ao vivo, feature em tempo real)
💡 A regra dos 90%

A grande maioria dos casos de negócio é resolvida por batch — muitas vezes por micro-batch de 5 a 15 minutos, que já parece "quase tempo real" para o usuário e custa uma fração do streaming verdadeiro. Só vá para streaming (Módulo 8) quando o requisito de latência for real e alguém puder articular o custo de esperar 10 minutos. "O CEO quer ver ao vivo" raramente sobrevive à pergunta "e se atualizar a cada 5 minutos?".

2.2 ETL × ELT

As três letras: Extract (tirar da fonte), Transform (limpar, juntar, aplicar regra de negócio), Load (gravar no destino analítico). O que mudou foi a ordem:

Vantagens do ELT que caem em entrevista: você preserva o dado bruto (pode reprocessar com lógica nova sem re-extrair da fonte), a transformação vira código SQL versionado e testável (dbt), e você não mantém um servidor de ETL separado.

2.3 OLTP × OLAP

OLTP (transacional)OLAP (analítico)
Para quêRodar o negócio: inserir pedido, atualizar saldoEntender o negócio: somar vendas por região por mês
ConsultasMuitas, pequenas, por chave, leem/escrevem poucas linhasPoucas, grandes, varrem milhões de linhas, agregam
ArmazenamentoOrientado a linha (row-store): a linha inteira junta no discoOrientado a coluna (columnar): cada coluna junta no disco
ExemplosPostgreSQL, MySQL, SQL ServerBigQuery, Snowflake, Redshift, ClickHouse, DuckDB, Spark
🧠 Por que columnar ganha em analítica

Uma consulta analítica típica toca 3 colunas de uma tabela de 200. Num row-store, ler essas 3 colunas obriga a carregar as 200 (a linha inteira). Num columnar store, você lê só os 3 arquivos de coluna relevantes — e como valores de uma mesma coluna são parecidos, a compressão é brutal (run-length, dictionary encoding). Menos I/O + menos bytes = consultas 10–100× mais rápidas e mais baratas. É por isso que o formato Parquet (seção 2.4) é columnar.

2.4 Formatos de arquivo: o detalhe que decide o custo

FormatoTipoBom paraObservações
CSVTexto, linhaTroca simples, inspeção humanaSem tipos, sem compressão, sem esquema, lento. Aceite na borda, converta imediatamente.
JSON / JSONLTexto, linhaAPIs, dados aninhados, logsVerboso e lento para analítica. JSONL (um objeto por linha) é o mínimo aceitável para processar em paralelo.
ParquetBinário, colunarAnalítica — o padrão de fatoCompressão forte, esquema embutido, predicate pushdown e leitura só das colunas necessárias. Use por padrão.
ORCBinário, colunarEcossistema Hive/HadoopParecido com Parquet; comum em stacks legadas da Cloudera.
AvroBinário, linhaStreaming, eventos KafkaÓtimo para escrita registro a registro e evolução de esquema; ruim para varredura analítica.

Regra prática: Avro (ou Protobuf) no transporte de eventos; Parquet no armazenamento analítico. CSV e JSON são formatos de borda — tolere na entrada, nunca deixe virar a base do seu lake.

💼 Mercado de trabalho

Perguntas reais deste módulo: "Qual a diferença entre ETL e ELT e por que ELT virou padrão?", "Por que Parquet é mais rápido que CSV para analítica?", "Quando você usaria streaming em vez de batch?" (a boa resposta menciona custo e o requisito real de latência, não "porque é mais moderno"). Saber explicar columnar vs row-store com o exemplo das 3 colunas de 200 já sinaliza que você entende o porquê, não só os nomes.

✏️ Exercício 2 — Escolhas de arquitetura

(a) Um time quer um dashboard de vendas atualizado "de manhã cedo, todo dia". Batch ou streaming? (b) Você recebe um dump diário de 4 GB em CSV de um parceiro. O que faz com ele? (c) Um sistema antifraude precisa bloquear cartão em até 2 segundos. Batch ou streaming? (d) Por que carregar o dado cru antes de transformar (ELT) ajuda quando a regra de negócio muda seis meses depois?

Gabarito: (a) Batch — uma carga noturna resolve. (b) Ingere o CSV na camada bronze, converte para Parquet particionado por data, valida esquema, e daí pra frente só trabalha com o Parquet. (c) Streaming — latência é requisito duro. (d) Porque o dado bruto histórico ainda está lá: você reescreve o SQL de transformação e reprocessa (backfill) sem precisar pedir os dados de novo à fonte, que pode nem tê-los mais.

MÓDULO 03 · INTERMEDIÁRIO

Ingestão de dados: batch, CDC, APIs e filas

Objetivo: dominar as quatro formas de tirar dado de uma fonte e trazê-lo para dentro, com as ferramentas de mercado e os padrões que evitam duplicar ou perder registros.

3.1 As quatro fontes e como se lê cada uma

FonteComo extrairFerramentas
Banco relacional de produçãoFull load (cópia total) + incremental por coluna de data, ou CDC (seção 3.3)Airbyte, Fivetran, Meltano, dlt, Debezium
API de SaaS (Stripe, HubSpot, GA4)Chamadas HTTP paginadas, respeitando rate limit, guardando o cursorAirbyte/Fivetran (conectores prontos), dlt, código próprio
Eventos de aplicaçãoApp publica em um tópico; consumidor grava no lakeKafka, Kinesis, Pub/Sub, RabbitMQ
Arquivos (SFTP, bucket, e-mail)Sensor que detecta arquivo novo e dispara a cargaAirflow sensors, Lambda/Cloud Function, rclone

3.2 Full load × incremental

# Ingestão incremental com a biblioteca dlt (Python) — padrão enxuto e testável
import dlt
from dlt.sources.sql_database import sql_database

pipeline = dlt.pipeline(
    pipeline_name="vendas",
    destination="filesystem",          # grava Parquet no lake
    dataset_name="bronze",
)

source = sql_database().with_resources("pedidos", "itens_pedido")
# incremental: dlt guarda o estado do cursor entre execuções
source.pedidos.apply_hints(incremental=dlt.sources.incremental("updated_at"))

info = pipeline.run(source, write_disposition="merge", primary_key="id")
print(info)

3.3 CDC — Change Data Capture o jeito profissional de ler um banco

Em vez de consultar a tabela periodicamente, o CDC lê o log de transações do banco (o WAL do Postgres, o binlog do MySQL) e emite um evento para cada INSERT, UPDATE e DELETE. Vantagens decisivas:

// Evento CDC do Debezium para um UPDATE (formato resumido)
{
  "op": "u",                          // c=create, u=update, d=delete, r=snapshot
  "ts_ms": 1738000000000,
  "before": { "id": 42, "status": "pendente" },
  "after":  { "id": 42, "status": "pago" },
  "source": { "table": "pedidos", "lsn": 987654321 }
}

Arquitetura clássica: Debezium (conector) lê o WAL → publica no Kafka → um consumidor (ou Kafka Connect com sink) grava no lakehouse, onde um MERGE aplica os eventos na tabela final. É assim que se mantém uma réplica analítica quase em tempo real de um banco de produção.

⚠️ O antipadrão dual-write

"Vou fazer a aplicação gravar no banco e publicar no Kafka ao mesmo tempo." Isso quebra: se a segunda escrita falha, os sistemas divergem silenciosamente e não há transação entre eles. As soluções corretas são CDC (deriva os eventos do próprio log do banco) ou o transactional outbox (a aplicação grava o evento numa tabela outbox na mesma transação do dado, e um processo separado publica dali). Citar isso numa entrevista de system design vale pontos.

3.4 Idempotência e entrega: os dois problemas que sempre voltam

💼 Mercado de trabalho

Vagas de Engenharia de Dados testam ingestão o tempo todo: "Como você replicaria o banco de produção para o warehouse?" (resposta forte: CDC com Debezium+Kafka, snapshot inicial + stream, MERGE no destino, e o cuidado com deletes). "Sua carga incremental rodou duas vezes por causa de um retry. E agora?" (resposta: se o pipeline é idempotente, nada acontece — e você explica como garantiu isso). Ferramenta que aparece muito em job description hoje: Airbyte e Fivetran para conectores, dlt ganhando espaço em times que querem código versionado.

✏️ Exercício 3 — Desenhe a ingestão

Você precisa trazer três fontes para o lake: (a) a tabela clientes (80 mil linhas, muda pouco); (b) a tabela eventos_clique (500 milhões de linhas, só cresce, nunca atualiza); (c) a API do Stripe para pagamentos. Descreva a estratégia de cada uma.

Gabarito: (a) Full load diário — é pequena, e full load nunca erra; ou CDC se precisar de baixa latência. (b) Incremental por id/created_at (append-only, sem updates nem deletes, então watermark basta), particionando por data no destino. (c) Conector (Airbyte/dlt) com paginação por cursor, respeitando rate limit, incremental por created; tratar reembolsos, que chegam como novos objetos, não como update.

MÓDULO 04 · INTERMEDIÁRIO

Armazenamento: Data Warehouse, Data Lake e Lakehouse

Objetivo: entender as três arquiteturas de armazenamento analítico, por que o lakehouse surgiu, e o que os formatos de tabela abertos (Iceberg, Delta, Hudi) resolvem.

4.1 Data Warehouse o clássico que funciona

Um banco analítico gerenciado, colunar, otimizado para SQL sobre grandes volumes: BigQuery, Snowflake, Redshift, Databricks SQL. Você joga dados estruturados dentro, escreve SQL, e a plataforma cuida de armazenamento, índice, escala e otimização.

4.2 Data Lake barato e flexível, perigoso se largado

Object storage (S3, GCS, Azure Blob) cheio de arquivos — Parquet, JSON, CSV, imagens — organizados em pastas. Barato, guarda qualquer coisa, qualquer engine lê (Spark, Trino, DuckDB).

⚠️ O "data swamp"

Um lake sem catálogo, sem esquema forçado e sem controle vira um pântano: ninguém sabe o que tem, qual arquivo está certo, se aquele vendas_final_v2_ok.parquet é confiável. Além disso, arquivos soltos não têm transações: se um job falha no meio da escrita, você fica com dados parciais e leitores enxergam lixo.

4.3 Lakehouse o padrão de 2026

A ideia: ficar com o armazenamento barato e aberto do lake (Parquet no S3) e ganhar por cima dele as garantias do warehouse — transações ACID, esquema forçado, evolução de esquema, versionamento (time travel), UPDATE/DELETE/MERGE. Isso é feito por uma camada de formato de tabela aberto que fica entre os arquivos e as engines:

Formato de tabelaOrigemNotas
Apache IcebergNetflixO que mais cresce; adotado por quase todos os fornecedores como padrão neutro. Bom em evolução de esquema e particionamento oculto (hidden partitioning).
Delta LakeDatabricksMaduro, forte no ecossistema Spark/Databricks; open source, mas historicamente com recursos de ponta primeiro no Databricks.
Apache HudiUberNasceu focado em upserts e CDC com baixa latência; nicho forte em ingestão incremental pesada.

Como funciona por baixo (vale para os três): além dos arquivos Parquet de dados, existe uma camada de metadados — arquivos de manifesto que listam quais Parquet compõem a tabela naquela versão. Um commit é trocar o ponteiro de metadados atomicamente. Daí saem: transações (leitores veem sempre uma versão consistente), time travel (basta apontar para um snapshot antigo), e DELETE sem reescrever tudo (marca arquivos/linhas como removidos).

-- Com uma tabela Iceberg, isto simplesmente funciona sobre arquivos no S3:
MERGE INTO silver.clientes t
USING bronze.clientes_cdc s ON t.id = s.id
WHEN MATCHED AND s.op = 'd' THEN DELETE
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;

-- time travel: como estava a tabela ontem?
SELECT * FROM silver.clientes FOR TIMESTAMP AS OF '2026-08-29 00:00:00';

4.4 Arquitetura Medallion (bronze / silver / gold)

A forma canônica de organizar as camadas dentro do lakehouse:

CamadaConteúdoQuem consome
🥉 Bronze (raw)Cópia fiel da fonte, sem transformação, só tipada e particionada. Append-only, imutável, com _ingested_at.Só pipelines. É o seu "backup reprocessável".
🥈 Silver (cleaned/conformed)Dado limpo, deduplicado, com tipos corretos, chaves resolvidas, joins básicos entre fontes. Uma linha = um fato/entidade real.Analytics engineers, cientistas de dados.
🥇 Gold (business/marts)Modelos agregados e prontos para consumo: fct_vendas_diarias, dim_cliente, KPIs. Modelagem dimensional (Módulo 5).BI, dashboards, relatórios executivos, feature store.

Por que essa separação importa: quando uma regra de negócio muda, você reescreve silver/gold e faz backfill a partir do bronze, sem tocar na fonte. Quando um dashboard está errado, você sabe em qual camada investigar.

4.5 Warehouse ou lakehouse? a decisão real

Prefira Warehouse (BigQuery/Snowflake) quando…Prefira Lakehouse (Iceberg/Delta + engine) quando…
Dados majoritariamente estruturados e o time vive de SQLHá muito dado semi/não estruturado (logs, JSON, texto, mídia para IA)
Time pequeno, quer zero infra e time-to-value rápidoVolume é enorme e o custo de armazenamento do warehouse dói
Governança e permissões finas são prioridade imediataQuer evitar lock-in e usar várias engines sobre o mesmo dado
Orçamento tolera o custo de compute do fornecedorCargas de ML/Spark convivem com as de SQL sobre a mesma tabela

Na prática, muitos times fazem híbrido: lake/lakehouse para bronze/silver e ingestão bruta, warehouse (ou a camada SQL do próprio lakehouse) para gold e BI. E a fronteira está se dissolvendo: Snowflake e BigQuery leem Iceberg nativamente hoje.

💼 Mercado de trabalho

Termos que precisam estar na ponta da língua: medallion (bronze/silver/gold), Iceberg vs Delta, time travel, "schema-on-read vs schema-on-write", data swamp. Pergunta comum: "O que é um lakehouse e que problema ele resolve em relação a lake e warehouse?" — resposta: traz ACID, esquema e MERGE para cima de arquivos baratos e abertos, evitando o pântano do lake sem o lock-in e o custo do warehouse. Databricks e Snowflake dominam as job descriptions; saber os dois conceitualmente cobre a maioria.

✏️ Exercício 4 — Onde mora cada coisa

Classifique em bronze, silver ou gold: (a) o JSON cru da API do Stripe, exatamente como veio; (b) uma tabela pagamentos deduplicada, com valores em centavos convertidos para reais e status normalizado; (c) fct_receita_mensal_por_plano usada no dashboard do board; (d) eventos CDC brutos do Debezium.

Gabarito: (a) bronze; (b) silver; (c) gold; (d) bronze. Dica: se um humano de negócio consome direto, é gold; se é cópia da fonte, é bronze; o meio de campo limpo é silver.

MÓDULO 05 · INTERMEDIÁRIO

Transformação com SQL, dbt e modelagem dimensional

Objetivo: transformar dado cru em modelo de negócio de forma versionada, testada e documentada — e modelar as tabelas gold do jeito que BI e mercado esperam.

5.1 dbt: transformação como engenharia de software

dbt (data build tool) é o padrão de fato da camada T do ELT. A ideia é simples e poderosa: você escreve cada transformação como um SELECT num arquivo .sql, e o dbt cuida de criar a tabela/view, resolver a ordem de execução (grafo de dependências), rodar testes e gerar documentação.

-- models/silver/stg_pedidos.sql
with fonte as (
    select * from {{ source('bronze', 'pedidos') }}
),
limpo as (
    select
        id                              as pedido_id,
        cliente_id,
        lower(trim(status))             as status,
        valor_centavos / 100.0          as valor_reais,
        cast(criado_em as timestamp)     as criado_em
    from fonte
    where id is not null
)
select * from limpo
# models/silver/stg_pedidos.yml — testes e documentação junto do modelo
models:
  - name: stg_pedidos
    description: "Pedidos limpos e tipados, um por linha."
    columns:
      - name: pedido_id
        description: "Chave primária do pedido."
        tests: [unique, not_null]
      - name: status
        tests:
          - accepted_values: { values: ['pago', 'pendente', 'cancelado'] }
      - name: cliente_id
        tests:
          - relationships: { to: ref('stg_clientes'), field: cliente_id }

O que o dbt te dá "de graça": dbt run materializa tudo na ordem certa; dbt test valida os contratos; dbt docs gera um site navegável com o lineage (de qual fonte cada coluna veio); ref() torna o grafo de dependências explícito; ambientes (dev/prod) isolados; e versionamento git com pull request e CI como qualquer código.

5.2 Materializações

MaterializaçãoO que fazUse quando
viewCria uma view (recalcula a cada consulta)Modelo leve, dado sempre fresco, staging
tableRecria a tabela inteira a cada dbt runModelo pesado consultado muitas vezes; tabela pequena/média
incrementalSó processa e insere as linhas novas/alteradasTabelas de fatos grandes onde reprocessar tudo é caro
ephemeralVira um CTE embutido, não cria objetoLógica intermediária reutilizada, sem valor de persistir

5.3 Modelagem dimensional: star schema o que BI espera

A camada gold quase sempre segue o esquema estrela (Kimball): uma tabela fato no centro, cercada de tabelas dimensão.

                    ┌─────────────┐
                    │  dim_data   │
                    └──────┬──────┘
   ┌─────────────┐   ┌─────┴───────┐   ┌──────────────┐
   │ dim_cliente ├───┤ fct_vendas  ├───┤ dim_produto  │
   └─────────────┘   │ (grão: 1    │   └──────────────┘
                     │  item de    │
                     │  pedido)    │
                     └─────┬───────┘
                    ┌──────┴──────┐
                    │  dim_loja   │
💡 O "grão" (grain) é a decisão nº 1

Antes de escrever qualquer coluna, defina em uma frase o que uma linha da tabela fato representa: "uma linha = um item de um pedido". Todo o resto decorre disso. Misturar grãos (algumas linhas por pedido, outras por item) é o erro que estraga qualquer dashboard depois.

5.4 Slowly Changing Dimensions (SCD) avançado

Um cliente muda de cidade. A dimensão deve refletir só o valor atual, ou preservar o histórico?

💼 Mercado de trabalho

dbt é quase obrigatório em vagas de Analytics/Data Engineering hoje. O que testam: escrever um modelo com CTEs legíveis, configurar um teste (unique, not_null, relationships), explicar materialização incremental e sua cláusula is_incremental(), e desenhar um star schema a partir de um enunciado ("modele as vendas de uma rede de lojas"). Saber articular grão, fato vs dimensão e SCD tipo 2 separa o pleno do júnior.

✏️ Exercício 5 — Modele e teste

(a) Defina o grão de uma tabela fct_pagamentos de um app de assinatura. (b) Liste três dimensões plausíveis. (c) O plano do cliente mudou de "Pro" para "Enterprise" em maio — que tipo de SCD você usa em dim_cliente e por quê? (d) Que teste dbt garante que todo cliente_id em fct_pagamentos existe em dim_cliente?

Gabarito: (a) "uma linha = uma cobrança processada". (b) dim_cliente, dim_plano, dim_data (poderia ter dim_metodo_pagamento). (c) SCD Tipo 2 — para que a receita de abril continue atribuída ao plano "Pro" e a de junho ao "Enterprise"; um relatório de MRR por plano fica errado com Tipo 1. (d) o teste relationships apontando de fct_pagamentos.cliente_id para dim_cliente.cliente_id.

MÓDULO 06 · INTERMEDIÁRIO → AVANÇADO

Orquestração: Airflow, Dagster e Prefect

Objetivo: coordenar dezenas de tarefas interdependentes com agendamento, retries, backfill e alertas — e conhecer as três ferramentas que o mercado usa, com seus trade-offs.

6.1 Por que orquestração existe

Um pipeline real não é um script. É: "às 2h, extraia 6 fontes (algumas dependem de VPN); quando todas terminarem, rode 40 modelos dbt na ordem certa; se o modelo de receita passar nos testes, atualize o dashboard e o feed de ML; se qualquer coisa falhar, tente de novo 3 vezes com espera crescente e, se ainda falhar, chame alguém no Slack; e se eu precisar recalcular março inteiro, me deixe reprocessar dia a dia sem duplicar nada".

Um orquestrador resolve isso: você descreve o DAG (grafo acíclico dirigido) de tarefas e dependências, e ele cuida de agendamento, execução, paralelismo, retries, estado, histórico e observabilidade.

6.2 As três ferramentas

Apache AirflowDagsterPrefect
Modelo mentalTarefas e dependências ("faça isto, depois aquilo")Ativos de dados ("esta tabela é feita a partir daquelas") + tarefasFluxos Python com o mínimo de cerimônia
Maturidade / mercadoPadrão dominante; está em quase toda job descriptionCresce rápido; forte em times dbt e "data as assets"Nicho; adorado por quem quer DX leve
Ponto forteEcossistema gigante de providers, maturidade, comunidadeLineage nativo, testabilidade local, tipagem, developer experienceEscrever pipeline parece escrever Python normal; dynamic mapping fácil
Ponto fracoBoilerplate, teste local chato, scheduler historicamente pesadoBase instalada menor; curva do modelo de assetsMenos conectores prontos; comunidade menor
# Airflow 2/3 — DAG com a TaskFlow API
from airflow.decorators import dag, task
from pendulum import datetime

@dag(
    schedule="0 2 * * *",               # todo dia às 2h
    start_date=datetime(2026, 1, 1),
    catchup=False,                       # não reprocessa o passado ao ligar
    default_args={"retries": 3, "retry_delay": 300},
    tags=["vendas"],
)
def pipeline_vendas():

    @task
    def extrair(fonte: str) -> str:
        # idempotente: sobrescreve a partição da data de execução
        return carregar_bronze(fonte, particao="{{ ds }}")

    @task
    def transformar(caminhos: list): dbt_run(select="silver+ gold+")

    @task
    def publicar(): atualizar_marts_e_feature_store()

    dados = extrair.expand(fonte=["pedidos", "itens", "clientes"])
    transformar(dados) >> publicar()

pipeline_vendas()

6.3 Os conceitos que você tem que dominar

⚠️ Não coloque a lógica pesada dentro do orquestrador

O Airflow/Dagster deve coordenar, não processar. Rodar um pandas de 30 GB dentro de uma task do Airflow trava o worker. O padrão certo: a task dispara um job no Spark/warehouse/Kubernetes e monitora; o processamento acontece lá, com recursos próprios. O orquestrador segura só o fio, não o balde.

💼 Mercado de trabalho

Airflow é a aposta segura — aparece em ~70% das vagas de Engenharia de Dados e é o que você deve saber com profundidade. Dagster vale conhecer conceitualmente (o modelo de "assets" e o argumento a favor dele). Perguntas típicas: "O que é idempotência e por que ela importa num pipeline agendado?", "Explique backfill e catchup", "Sua DAG tem uma task que falha 1 vez a cada 20 execuções, sempre por timeout de rede. O que você faz?" (retry com backoff, e investigar se o timeout é ajustável — não só empilhar retries).

✏️ Exercício 6 — Conserte o pipeline

Um colega escreveu uma DAG que roda às 6h e faz INSERT INTO fct_vendas SELECT ... WHERE data = current_date - 1. Aponte dois problemas e como corrigir.

Gabarito: (1) Não é idempotente: se a DAG rodar duas vezes (retry, backfill), duplica as linhas do dia. Correção: DELETE da partição do dia antes do INSERT, ou MERGE, ou INSERT OVERWRITE da partição. (2) Usa current_date em vez da data do intervalo ({{ ds }}): um backfill de 30 dias atrás processaria "ontem" 30 vezes, nunca os dias corretos. Correção: parametrizar pela data lógica do run.

MÓDULO 07 · AVANÇADO

Processamento distribuído: Spark e as alternativas modernas

Objetivo: entender quando o dado deixa de caber em uma máquina, como o Spark divide o trabalho, o que é shuffle, e por que DuckDB/Polars/Trino roubaram uma parte grande do território do Spark.

7.1 Quando você realmente precisa de distribuído

💡 "Big data is dead" — leia com cuidado

Uma máquina na nuvem hoje tem 128+ vCPUs e 1 TB+ de RAM. DuckDB e Polars processam dezenas a centenas de GB de Parquet numa única máquina, mais rápido e mais barato que um cluster Spark, porque não pagam o custo de coordenação de rede. A pergunta profissional não é "qual framework de big data?", é "esse dado cabe numa máquina grande?" — e a resposta hoje é "sim" para a maioria dos times. Spark entra quando o volume passa de ~1 TB por job, quando você já tem um cluster, ou quando precisa de streaming/ML integrados.

7.2 Como o Spark divide o trabalho

Shuffle: o gargalo que você tem que conhecer avançado

O shuffle escreve dados em disco e os transfere pela rede entre executores para reagrupá-los por chave. É a operação mais cara do Spark e a causa nº 1 de jobs lentos. Você reduz shuffle:

# PySpark: leitura de Parquet particionado, filtro cedo, join com broadcast
from pyspark.sql import functions as F
from pyspark.sql.functions import broadcast

vendas = (spark.read.parquet("s3://lake/silver/vendas/")
          .filter(F.col("data").between("2026-01-01", "2026-06-30"))   # predicate pushdown
          .select("data", "loja_id", "valor"))

lojas = spark.read.parquet("s3://lake/silver/dim_loja/")              # pequena

resultado = (vendas.join(broadcast(lojas), "loja_id")                     # sem shuffle do lado grande
             .groupBy("regiao", "data").agg(F.sum("valor").alias("receita")))

resultado.write.mode("overwrite").partitionBy("data").parquet("s3://lake/gold/receita_regiao/")

7.3 O ecossistema de engines em 2026

EngineModeloUse quando
Apache Spark (via Databricks, EMR, Glue)Distribuído, batch + streaming + MLVolumes de TB+, pipelines pesados, você já tem cluster, precisa de MLlib/Structured Streaming
DuckDBUma máquina, colunar, SQL, embutido no processoTransformações de até centenas de GB, dev local, dentro de um container/Lambda, testes de dbt
PolarsUma máquina, DataFrame em Rust, lazySubstituir pandas em ETL Python; velocidade e uso de memória muito melhores
Trino / PrestoDistribuído, SQL federado, sem armazenamento próprioConsulta interativa cruzando lake + várias fontes; camada de query do lakehouse
ClickHouseColunar, OLAP em tempo realAnalytics de baixa latência sobre eventos, dashboards que respondem em < 1 s
💼 Mercado de trabalho

Spark ainda é o item mais pedido em vagas sênior de Engenharia de Dados — e o que mais reprova em entrevista técnica, porque muita gente "usou" sem entender. O que testam: "O que é um shuffle e como você reduz?", "O que é data skew e como trata?", "Diferença entre transformação e ação", "Quando você não usaria Spark?" (resposta madura: quando o dado cabe numa máquina — cite DuckDB/Polars). Mostrar que você conhece as alternativas leves sinaliza maturidade, não ignorância.

✏️ Exercício 7 — Diagnóstico de job lento

Um job Spark que agrega 200 GB por cliente_id demora 40 min; 39 dos 40 minutos são gastos em uma única task no fim. As outras tasks terminam em 2 min. O que está acontecendo e o que você tenta?

Gabarito: data skew — provavelmente um cliente_id nulo ou um cliente "sistema" concentra a maioria das linhas, então uma partição do shuffle fica enorme. Tentativas: filtrar/tratar o valor nulo à parte; aplicar salting na chave (prefixo aleatório para dividir a chave quente e reagregar depois); habilitar/ajustar o skew join optimization do AQE; se um lado for pequeno, usar broadcast join.

MÓDULO 08 · AVANÇADO

Streaming em tempo real: Kafka, Flink e Spark Structured Streaming

Objetivo: entender o log distribuído do Kafka, o que muda ao processar um fluxo infinito (tempo de evento, janelas, watermarks, estado) e como se alcança exactly-once na prática.

8.1 Kafka: o log distribuído, append-only

Kafka não é uma fila que apaga a mensagem depois de lida. É um log particionado e durável: produtores anexam registros ao fim de um tópico; consumidores leem em seu próprio ritmo, guardando um offset (posição). Os dados ficam por um tempo de retenção configurável (dias, semanas ou "para sempre" com compaction).

8.2 O que muda ao processar um fluxo infinito

BatchStreaming
O dataset tem começo e fimO fluxo nunca acaba; você processa "até agora"
"Contar por dia" = GROUP BY data"Contar por dia" exige definir janelas e decidir quando fechá-las
Dado atrasado: estava no arquivoDado atrasado chega depois da janela que ele pertencia — e agora?
Reprocessar = rodar de novoReprocessar = rebobinar offsets e lidar com estado acumulado

Tempo de evento × tempo de processamento

Um clique aconteceu às 20h59 (event time) mas chegou ao seu processador às 21h03 (processing time), porque o celular estava sem sinal. Se você agrega por tempo de processamento, esse clique cai na janela errada. Sistemas sérios agregam por event time — e para isso precisam de watermarks.

Watermark

Um watermark é a afirmação do sistema: "acredito que já não vou receber eventos com timestamp anterior a T". Ele avança conforme o tempo de evento observado, com uma folga configurável (ex.: "tolero 5 min de atraso"). Quando o watermark passa do fim de uma janela, a janela fecha e emite o resultado. Eventos que chegam depois disso são late data: descartados, enviados para um side output, ou aplicados como atualização, conforme a política.

Tipos de janela

8.3 Flink × Spark Structured Streaming × Kafka Streams

Apache FlinkSpark Structured StreamingKafka Streams
ModeloStreaming nativo, evento a evento; latência msMicro-batch (e um modo contínuo); latência sub-segundo a segundosBiblioteca Java/Scala embutida na sua app, Kafka-para-Kafka
EstadoGestão de estado robusta (RocksDB), grandes estados, checkpoints incrementaisEstado ok; melhor se você já vive no ecossistema SparkEstado local + changelog em tópico Kafka; sem cluster separado
Use quandoLatência real e lógica de streaming complexa (CEP, joins de streams, ML online)Já tem Spark; latência de segundos é suficiente; unificar batch e stream no mesmo códigoTransformação simples entre tópicos, sem querer operar mais um sistema
-- Flink SQL: contagem de eventos por minuto, por tipo, tolerando 30 s de atraso
SELECT
    window_start,
    tipo,
    COUNT(*) AS total
FROM TABLE(
    TUMBLE(TABLE eventos, DESCRIPTOR(ts_evento), INTERVAL '1' MINUTE)
)
GROUP BY window_start, tipo;

-- ts_evento é event time; o watermark foi definido no DDL da tabela:
--   WATERMARK FOR ts_evento AS ts_evento - INTERVAL '30' SECOND

8.4 Exactly-once na prática avançado

"Exactly-once" de verdade não significa que cada mensagem é fisicamente processada uma vez — significa que o efeito observável é como se fosse. Consegue-se combinando:

⚠️ Streaming é uma decisão de custo, não de status

Um pipeline de streaming roda 24/7, precisa de gestão de estado, replay, monitoramento de lag de consumo, e é bem mais difícil de depurar que um batch. Muitos "requisitos de tempo real" são atendidos por micro-batch de 1–5 min a uma fração do custo operacional. Vá para streaming verdadeiro quando a latência de minutos gerar perda concreta (fraude, preços, alertas de segurança, features de recomendação ao vivo).

💼 Mercado de trabalho

Kafka aparece em muitas vagas; Flink é diferencial sênior e em alta. Perguntas reais: "Explique partição, offset e consumer group no Kafka", "Qual a diferença entre event time e processing time, e o que é um watermark?", "Como você garante exactly-once de ponta a ponta?", "O que você monitora num consumidor Kafka?" (resposta: consumer lag acima de tudo, throughput, taxa de rebalanceamento, erros de desserialização). Saber dizer quando não usar streaming continua valendo pontos aqui também.

✏️ Exercício 8 — Janela e atraso

Você conta pedidos por janela tumbling de 10 minutos, por event time, com watermark de 2 minutos. Um pedido com timestamp 10:08 chega ao processador às 10:13. (a) Em qual janela ele entra? (b) E se chegasse às 10:15? (c) Por que não usar simplesmente processing time e acabar com a complicação?

Gabarito: (a) Na janela [10:00, 10:10). Às 10:13 o watermark está em ~10:11 (10:13 menos a folga de 2 min, aproximando), a janela [10:00,10:10) ainda não fechou (fecha quando o watermark passa de 10:10 + 2 min = 10:12... na prática ela fecha por volta de 10:12), então provavelmente ainda entra. (b) Às 10:15 o watermark já passou de 10:12; a janela fechou e o evento é late data — descartado ou mandado para tratamento à parte. (c) Com processing time, um lote de eventos atrasados por uma queda de rede seria todo contabilizado no minuto em que a rede voltou, distorcendo completamente a série temporal do negócio.

MÓDULO 09 · MUITO AVANÇADO

Qualidade, governança, observabilidade e DataOps

Objetivo: transformar um pipeline que "roda" num pipeline em que se pode confiar — com contratos, testes, monitoramento de saúde do dado, linhagem, catálogo, controle de custo e cultura de operação.

9.1 Data quality: as seis dimensões

DimensãoPerguntaComo se testa
CompletudeFaltam valores/linhas que deveriam existir?not_null, contagem esperada de linhas, cobertura por chave
UnicidadeHá duplicatas na chave?unique na PK, checagem de grão
ValidadeOs valores estão no domínio permitido?accepted_values, ranges, regex, tipos
ConsistênciaBate entre tabelas/sistemas?relationships (FK), reconciliação de totais com a fonte
Atualidade (freshness)O dado é recente o suficiente?idade do max(updated_at) vs SLA
AcuráciaReflete o mundo real?a mais difícil: amostragem, comparação com fonte-verdade, regras de negócio
# Great Expectations / dbt tests / Soda — a ideia é a mesma: contratos executáveis
# exemplo em dbt: teste custom de reconciliação (a soma tem que bater com a origem)
# tests/assert_receita_bate_com_origem.sql — o teste passa se retornar 0 linhas
with destino as (select sum(valor_reais) v from {{ ref('fct_vendas') }} where data = '{{ var("run_date") }}'),
     origem  as (select sum(valor_centavos)/100.0 v from {{ source('bronze', 'pedidos') }} where data = '{{ var("run_date") }}')
select destino.v, origem.v
from destino, origem
where abs(destino.v - origem.v) > 0.01

9.2 Data contracts avançado

O maior gerador de incidentes de dados: um time de produto renomeia uma coluna, muda um tipo ou o significado de um enum, sem avisar — e três pipelines quebram silenciosamente rio abaixo. Um data contract é um acordo versionado e validado em CI entre quem produz e quem consome o dado: esquema, tipos, semântica, garantias de qualidade, SLA de atualização, e política de mudança (o que exige major version). Na prática: um arquivo YAML/JSON Schema no repositório do produtor, checado no pipeline de deploy dele — a mudança incompatível falha o build do time de origem, não o seu dashboard às 8h.

9.3 Observabilidade de dados

Monitoramento de infra (CPU, "o job terminou?") não detecta o pior tipo de falha: o pipeline rodou com sucesso e entregou dado errado. Observabilidade de dados acrescenta cinco sinais (os "pilares", popularizados por ferramentas como Monte Carlo, e hoje também open source com Elementary, re_data, Soda):

9.4 Catálogo, lineage e governança

9.5 FinOps de dados: o custo é um requisito

9.6 DataOps: rodar isso como time

💼 Mercado de trabalho

Este módulo é o que distingue um sênior. Perguntas: "Um dashboard mostrou receita 30% menor hoje. Como você investiga?" (freshness da fonte → volume da bronze → mudança de esquema → teste de reconciliação → lineage para ver o que mudou), "O que é um data contract e que problema resolve?", "Como você garante qualidade num pipeline?" (testes em CI + observabilidade em prod + contratos na origem — as três camadas). Ferramentas atuais para citar: dbt tests, Great Expectations/Soda, Elementary, DataHub/OpenMetadata.

✏️ Exercício 9 — Incidente de produção

Às 8h05, o time de finanças diz que o relatório de faturamento do dia anterior está com metade do valor esperado. O pipeline rodou "com sucesso" às 3h. Descreva sua sequência de diagnóstico.

Gabarito (uma boa resposta): (1) Freshness: a fonte (banco de pedidos) tinha os dados completos às 3h, ou a carga da fonte atrasou? (2) Volume na camada bronze do dia: nº de linhas vs média — se caiu, o problema é ingestão. (3) Schema: alguma coluna de valor mudou de tipo/nome (talvez centavos↔reais)? (4) Rodar o teste de reconciliação bronze × gold para localizar em que camada o valor diverge. (5) Lineage: ver se algum modelo dbt intermediário mudou no último deploy. (6) Causa provável a investigar: janela de ingestão incremental que pegou só parte do dia por causa de fuso/marca d'água, ou um JOIN que virou inner e derrubou linhas. Comunicar ETA de correção ao time de finanças enquanto investiga.

MÓDULO 10 · CARREIRA

Mercado de trabalho: roadmap, entrevistas e portfólio

Objetivo: converter o conteúdo dos módulos anteriores em aprovação — roadmap de estudo, banco de perguntas reais com respostas e projetos que geram entrevista.

10.1 Roadmap de estudo (12 semanas, para quem já sabe SQL e Python básico)

SemanasFocoEntregável prático
1–2Módulos 1–2 + SQL analítico a fundo (window functions, CTEs) + Parquet/DuckDB localConverter um dataset CSV público em Parquet particionado e consultar com DuckDB
3–4Módulo 3 (ingestão) + Módulo 4 (lakehouse) com Iceberg ou Delta localPipeline de ingestão (dlt ou Airbyte) de uma API pública para a camada bronze em Iceberg
5–6Módulo 5 — dbt do zero: staging, marts, testes, docs, star schemaProjeto dbt com bronze→silver→gold, ≥ 10 testes, star schema documentado
7–8Módulo 6 — Airflow: DAGs, idempotência, backfill, sensores, alertasOrquestrar o pipeline inteiro (ingestão + dbt + validação) numa DAG agendada
9Módulo 7 — Spark (ou aprofundar DuckDB/Polars); shuffle, skew, broadcastReescrever a transformação mais pesada em PySpark e comparar tempos
10Módulo 8 — Kafka + um processador de streaming (Flink SQL ou Spark SS)Produzir eventos sintéticos no Kafka e agregar por janela de tempo
11Módulo 9 — testes em CI, observabilidade (Elementary), catálogo, custoAdicionar CI com dbt build + relatório de qualidade ao projeto
12Cloud (1 provedor) + IaC básico (Terraform) + revisão de entrevistasSubir o projeto na nuvem com Terraform; escrever o README de portfólio

Escolha uma nuvem para aprofundar (AWS é a mais pedida; GCP tem o BigQuery, muito comum; Azure aparece em corporações). Os conceitos transferem; o que muda são nomes de serviços.

10.2 Banco de perguntas de entrevista (com a resposta que aprova)

Júnior/pleno — "Qual a diferença entre ETL e ELT?"

Explique a ordem (transformar antes ou depois de carregar) e por que ELT venceu: armazenamento barato + engines rápidas permitem guardar o cru e transformar com SQL versionado (dbt) dentro do warehouse/lakehouse, preservando a capacidade de reprocessar quando a regra muda. Feche dizendo que ETL ainda faz sentido quando há restrição de compliance para não carregar o dado bruto (ex.: mascarar PII antes de persistir).

Pleno — "O que é idempotência e por que importa num pipeline agendado?"

Idempotente = rodar N vezes tem o mesmo efeito de rodar 1 vez. Importa porque retries, backfills e reprocessamentos são inevitáveis; sem idempotência, cada um deles duplica ou corrompe dados. Consegue-se com MERGE/upsert por chave, ou INSERT OVERWRITE da partição da data lógica do run (nunca current_date). Dê o exemplo do DELETE da partição antes do INSERT.

Pleno — "Modele um star schema para uma rede de lojas"

Primeiro declare o grão: "uma linha = um item de um pedido". Fato fct_vendas com chaves (data_id, loja_id, produto_id, cliente_id) e métricas aditivas (quantidade, valor_bruto, desconto, valor_liquido). Dimensões dim_data, dim_loja, dim_produto, dim_cliente. Mencione SCD Tipo 2 em dim_produto (preço/categoria mudam e o histórico importa) e por que o desconto fica no fato, não na dimensão.

Pleno/sênior — "Como você replicaria o banco de produção para o ambiente analítico?"

CDC: Debezium lê o WAL/binlog → Kafka → sink no lakehouse; snapshot inicial + stream contínuo; MERGE no destino tratando op = 'd' para deletes. Justifique contra o polling incremental: CDC captura updates/deletes, não pesa na fonte e tem baixa latência. Cite o antipadrão dual-write e a alternativa outbox. Mencione monitorar o replication lag.

Sênior — "O que é um shuffle no Spark e como você reduz?"

É a redistribuição de dados entre executores por chave, necessária em operações wide (groupBy, join, distinct); escreve em disco e trafega pela rede — o maior custo do job. Reduz-se filtrando e projetando cedo, usando broadcast join quando um lado é pequeno, tratando data skew (salting, AQE skew handling) e escolhendo um nº de partições de shuffle adequado ao volume. Bônus: dizer quando não usar Spark (dado cabe numa máquina → DuckDB/Polars).

Sênior — "Event time vs processing time; o que é watermark?"

Event time = quando o fato ocorreu; processing time = quando seu sistema o viu. Agregações de negócio devem usar event time para não distorcer séries temporais quando há atraso de rede. Watermark é a estimativa "não espero mais eventos anteriores a T"; quando ele ultrapassa o fim de uma janela, a janela fecha e emite; eventos posteriores são late data (descarte, side output ou update). Cite a folga configurável (trade-off latência × completude).

Sênior — "Como você garante qualidade de dados num pipeline?"

Três camadas: (1) testes em CIunique, not_null, relationships, accepted_values, reconciliação, rodando em dbt build num ambiente efêmero antes do merge; (2) observabilidade em produção — freshness, volume, schema, distribuição e lineage com alerta acionável; (3) data contracts na origem — mudança incompatível quebra o build do time produtor, não o seu relatório. Mencione política de "circuit breaker": se um teste crítico falha, o pipeline não publica dado ruim a jusante.

Armadilha — "Vamos fazer tudo em streaming, é mais moderno"

Resposta madura: streaming é uma decisão de custo e latência, não de status. Roda 24/7, exige gestão de estado, replay e monitoramento de lag, e é mais difícil de depurar. A maioria dos "requisitos de tempo real" é atendida por micro-batch de 1–5 min a uma fração do custo operacional. Streaming verdadeiro se justifica quando esperar minutos gera perda concreta: fraude, precificação, alertas de segurança, features de recomendação ao vivo.

10.3 Projetos de portfólio que geram entrevista

  1. Lakehouse ponta a ponta (o projeto-âncora): ingestão de uma API pública (ou dataset como o de táxis de NYC) → bronze em Iceberg/Delta → dbt para silver/gold com star schema → Airflow orquestrando tudo → testes em CI → um dashboard simples (Metabase/Streamlit). README explicando cada escolha. Este projeto sozinho já cobre 80% de uma entrevista.
  2. CDC em tempo quase real: Postgres + Debezium + Kafka + consumidor que faz MERGE num Iceberg; painel mostrando o lag. Diferencial: demonstrar deletes propagando corretamente.
  3. Comparativo de engines: a mesma transformação de ~50 GB em pandas, Polars, DuckDB e Spark, com tabela de tempo/memória/custo e uma conclusão honesta sobre quando cada uma vale a pena.
  4. Pipeline de streaming: gerador de eventos → Kafka → Flink SQL com janela por event time e watermark → sink agregado; documentar o comportamento com dados atrasados.

Em todos: o README é o que o recrutador técnico lê. Explique o porquê de cada ferramenta, os trade-offs aceitos, e mostre um diagrama da arquitetura. Código limpo com decisões mal justificadas perde para arquitetura simples bem explicada.

10.4 Certificações e leituras que pesam

🏁 Síntese final da apostila

Se você levar quatro ideias, esta apostila cumpriu o papel: (1) o produto da Engenharia de Dados é confiança, e ela se constrói com testes, contratos e observabilidade, não com boa vontade; (2) idempotência é o que torna todo pipeline recuperável — projete para reprocessar; (3) a stack moderna é ELT sobre lakehouse (bronze/silver/gold), com orquestração coordenando e não processando; (4) escolha de ferramenta é sempre trade-off — batch antes de streaming, uma máquina antes de um cluster, o simples que você entende antes do sofisticado que você copia. O ferramental muda de nome a cada dois anos; esses princípios não.