DE Trilhas

M08 β€” Pipeline fim a fim β€” case completo

DuraΓ§Γ£o: 4h PrΓ©-requisitos: M00-M07 ok. Tudo o que aprendeu junto. Objetivo: entregar um pipeline de produΓ§Γ£o completo, do Azure SQL ao dashboard Power BI, versionado em Git, com agendamento diΓ‘rio e alertas. Esse Γ© o case que vocΓͺ vai mostrar em entrevista.

O case

VocΓͺ Γ© o engenheiro de dados responsΓ‘vel por um data lakehouse de vendas. A fonte Γ© o ERP em Azure SQL (AdventureWorksLT). O time de BI precisa de um dashboard diΓ‘rio em Power BI com:

  • Vendas por mΓͺs.
  • Top 10 clientes por receita.
  • Ticket mΓ©dio por categoria.

O pipeline deve rodar todo dia Γ s 06h BRT, com atΓ© 30 min de SLA, e enviar e-mail se falhar.

Arquitetura final

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Azure SQL AdventureWorksLT β”‚
β”‚ SalesLT.Customer β”‚
β”‚ SalesLT.SalesOrderHeader β”‚
β”‚ SalesLT.SalesOrderDetail β”‚
β”‚ SalesLT.Product β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ (1) Copy diΓ‘rio (ADF)
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ADLS Gen2 β€” BRONZE (parquet, daily) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ (2) Notebook ingest
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ silver_prd.adventureworks.* β”‚
β”‚ - customer, orders, products β”‚
β”‚ (Delta, tipado, dedup, MERGE) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ (3) Notebook agregaΓ§Γ£o
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ gold_prd.adventureworks.* β”‚
β”‚ - fact_sales β”‚
β”‚ - dim_customer, dim_product, dim_date β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ (4) SQL Warehouse expΓ΅e
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Power BI Dashboard β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Tudo orquestrado por ADF + monitorado em Monitor + alertas em Action Group.

Parte 1 β€” Recursos novos para este case

VocΓͺ jΓ‘ tem do M01-M06: RG, Storage, Databricks, ADF, KV, UC, Catalogs silver_* e gold_*. Falta:

1.1 β€” SQL Warehouse no Databricks (serverless ou pro)

Power BI conecta no Databricks via SQL Warehouse. Crie um Serverless (cobra sΓ³ quando em uso):

Terminal window
$WHOUSE_JSON = @'
{
"name": "wh-prd-bi",
"cluster_size": "Small",
"min_num_clusters": 1,
"max_num_clusters": 1,
"auto_stop_mins": 10,
"enable_serverless_compute": true,
"spot_instance_policy": "COST_OPTIMIZED",
"warehouse_type": "PRO",
"tags": {
"custom_tags": [
{ "key": "projeto", "value": "de" },
{ "key": "ambiente", "value": "prd" }
]
}
}
'@
$WHOUSE_JSON | Set-Content warehouse.json
databricks warehouses create --json @warehouse.json

Pegue o endpoint:

Terminal window
databricks warehouses list -o json | ConvertFrom-Json |
Where-Object name -eq "wh-prd-bi" |
Select-Object id, jdbc_url

Parte 2 β€” Estrutura final de tabelas

Silver (3 tabelas)

-- silver_prd.adventureworks.customer
CREATE SCHEMA IF NOT EXISTS silver_prd.adventureworks;
CREATE TABLE IF NOT EXISTS silver_prd.adventureworks.customer (
customer_id INT,
first_name STRING, last_name STRING,
company_name STRING,
email STRING, phone STRING,
modified_at TIMESTAMP,
_silver_ingested_at TIMESTAMP
) USING DELTA;
-- silver_prd.adventureworks.sales_order_header
CREATE TABLE IF NOT EXISTS silver_prd.adventureworks.sales_order_header (
sales_order_id INT,
customer_id INT,
order_date DATE,
ship_date DATE,
status INT,
subtotal DOUBLE, tax_amt DOUBLE, freight DOUBLE, total_due DOUBLE,
modified_at TIMESTAMP,
_silver_ingested_at TIMESTAMP
) USING DELTA PARTITIONED BY (order_date);
-- silver_prd.adventureworks.sales_order_detail
CREATE TABLE IF NOT EXISTS silver_prd.adventureworks.sales_order_detail (
sales_order_id INT,
sales_order_detail_id INT,
product_id INT,
order_qty INT,
unit_price DOUBLE, line_total DOUBLE,
modified_at TIMESTAMP,
_silver_ingested_at TIMESTAMP
) USING DELTA;
-- silver_prd.adventureworks.product
CREATE TABLE IF NOT EXISTS silver_prd.adventureworks.product (
product_id INT,
name STRING, color STRING, size STRING,
standard_cost DOUBLE, list_price DOUBLE,
product_category STRING,
modified_at TIMESTAMP,
_silver_ingested_at TIMESTAMP
) USING DELTA;

Gold (estrela)

CREATE SCHEMA IF NOT EXISTS gold_prd.adventureworks;
-- DIM CUSTOMER
CREATE TABLE IF NOT EXISTS gold_prd.adventureworks.dim_customer (
customer_sk BIGINT GENERATED ALWAYS AS IDENTITY,
customer_id INT,
full_name STRING,
company_name STRING,
email STRING,
_scd_valid_from TIMESTAMP, _scd_valid_to TIMESTAMP, _scd_current BOOLEAN
) USING DELTA;
-- DIM PRODUCT
CREATE TABLE IF NOT EXISTS gold_prd.adventureworks.dim_product (
product_sk BIGINT GENERATED ALWAYS AS IDENTITY,
product_id INT,
name STRING, color STRING, product_category STRING,
list_price DOUBLE
) USING DELTA;
-- DIM DATE
CREATE TABLE IF NOT EXISTS gold_prd.adventureworks.dim_date (
date_key DATE,
year INT, quarter INT, month INT, month_name STRING,
day INT, weekday INT, is_weekend BOOLEAN
) USING DELTA;
-- FACT SALES
CREATE TABLE IF NOT EXISTS gold_prd.adventureworks.fact_sales (
sales_order_id INT,
sales_order_detail_id INT,
date_key DATE,
customer_sk BIGINT, product_sk BIGINT,
order_qty INT,
unit_price DOUBLE, line_total DOUBLE
) USING DELTA PARTITIONED BY (date_key);

Parte 3 β€” Notebooks

notebooks/10_ingest_bronze_to_silver.py

PadrΓ£o MERGE (upsert incremental):

# Databricks notebook source
from pyspark.sql import functions as F
from delta.tables import DeltaTable
dbutils.widgets.text("storage_account", "")
dbutils.widgets.text("catalog", "")
dbutils.widgets.text("data_processamento", "")
storage = dbutils.widgets.get("storage_account")
catalog = dbutils.widgets.get("catalog")
data_proc = dbutils.widgets.get("data_processamento") # YYYY-MM-DD
# COMMAND ----------
# Credenciais (tambΓ©m jΓ‘ config no init script do cluster em prd)
for k in ["sp-appid", "sp-secret", "sp-tenant"]:
pass # jΓ‘ carregado via cluster config; aqui Γ© sΓ³ ilustrativo
bronze_root = f"abfss://bronze@{storage}.dfs.core.windows.net"
# COMMAND ----------
def merge_bronze_to_silver(table_name: str, key_cols: list[str], select_expr: dict[str, str]):
"""LΓͺ parquet do bronze do dia, transforma com select_expr, upsert no silver."""
path = f"{bronze_root}/source=adventureworks/schema=salesLT/table={table_name}/ingestion_date={data_proc}/"
df = spark.read.parquet(path)
df = df.selectExpr(*[f"{v} AS {k}" for k, v in select_expr.items()]) \
.withColumn("_silver_ingested_at", F.current_timestamp())
target_tbl = f"{catalog}.adventureworks.{table_name}"
if not spark.catalog.tableExists(target_tbl):
df.write.format("delta").saveAsTable(target_tbl)
return df.count(), 0
target = DeltaTable.forName(spark, target_tbl)
cond = " AND ".join([f"t.{c} = s.{c}" for c in key_cols])
res = (
target.alias("t")
.merge(df.alias("s"), cond)
.whenMatchedUpdateAll()
.whenNotMatchedInsertAll()
.execute()
)
return df.count(), res
# COMMAND ----------
cust_total, _ = merge_bronze_to_silver(
"customer",
key_cols=["customer_id"],
select_expr={
"customer_id": "CAST(CustomerID AS INT)",
"first_name": "FirstName",
"last_name": "LastName",
"company_name":"CompanyName",
"email": "EmailAddress",
"phone": "Phone",
"modified_at": "CAST(ModifiedDate AS TIMESTAMP)",
}
)
print(f"Customer: {cust_total} linhas processadas.")
# COMMAND ----------
merge_bronze_to_silver(
"sales_order_header",
key_cols=["sales_order_id"],
select_expr={
"sales_order_id": "CAST(SalesOrderID AS INT)",
"customer_id": "CAST(CustomerID AS INT)",
"order_date": "CAST(OrderDate AS DATE)",
"ship_date": "CAST(ShipDate AS DATE)",
"status": "CAST(Status AS INT)",
"subtotal": "CAST(SubTotal AS DOUBLE)",
"tax_amt": "CAST(TaxAmt AS DOUBLE)",
"freight": "CAST(Freight AS DOUBLE)",
"total_due": "CAST(TotalDue AS DOUBLE)",
"modified_at": "CAST(ModifiedDate AS TIMESTAMP)",
}
)
merge_bronze_to_silver(
"sales_order_detail",
key_cols=["sales_order_id","sales_order_detail_id"],
select_expr={
"sales_order_id": "CAST(SalesOrderID AS INT)",
"sales_order_detail_id": "CAST(SalesOrderDetailID AS INT)",
"product_id": "CAST(ProductID AS INT)",
"order_qty": "CAST(OrderQty AS INT)",
"unit_price": "CAST(UnitPrice AS DOUBLE)",
"line_total": "CAST(LineTotal AS DOUBLE)",
"modified_at": "CAST(ModifiedDate AS TIMESTAMP)",
}
)
merge_bronze_to_silver(
"product",
key_cols=["product_id"],
select_expr={
"product_id": "CAST(ProductID AS INT)",
"name": "Name",
"color": "Color",
"size": "Size",
"standard_cost": "CAST(StandardCost AS DOUBLE)",
"list_price": "CAST(ListPrice AS DOUBLE)",
"product_category": "ProductCategoryID", # simplificado; em prod faz lookup
"modified_at": "CAST(ModifiedDate AS TIMESTAMP)",
}
)
# COMMAND ----------
# ValidaΓ§Γ£o bΓ‘sica
display(spark.sql(f"""
SELECT
(SELECT COUNT(*) FROM {catalog}.adventureworks.customer) customers,
(SELECT COUNT(*) FROM {catalog}.adventureworks.sales_order_header) orders,
(SELECT COUNT(*) FROM {catalog}.adventureworks.sales_order_detail) order_items,
(SELECT COUNT(*) FROM {catalog}.adventureworks.product) products
"""))

notebooks/20_silver_to_gold.py

# Databricks notebook source
from pyspark.sql import functions as F
dbutils.widgets.text("silver_catalog", "silver_prd")
dbutils.widgets.text("gold_catalog", "gold_prd")
S = dbutils.widgets.get("silver_catalog")
G = dbutils.widgets.get("gold_catalog")
# COMMAND ----------
# MAGIC %md ## dim_date β€” regenera completa
spark.sql(f"""
CREATE OR REPLACE TABLE {G}.adventureworks.dim_date AS
WITH calendar AS (
SELECT explode(sequence(to_date('2020-01-01'), to_date('2030-12-31'), interval 1 day)) AS date_key
)
SELECT
date_key,
YEAR(date_key) AS year,
QUARTER(date_key) AS quarter,
MONTH(date_key) AS month,
DATE_FORMAT(date_key, 'MMMM') AS month_name,
DAY(date_key) AS day,
DAYOFWEEK(date_key) AS weekday,
DAYOFWEEK(date_key) IN (1,7) AS is_weekend
FROM calendar
""")
# COMMAND ----------
# MAGIC %md ## dim_customer (SCD1 simplificado)
spark.sql(f"""
MERGE INTO {G}.adventureworks.dim_customer t
USING (
SELECT customer_id,
CONCAT_WS(' ', first_name, last_name) AS full_name,
company_name, email,
CURRENT_TIMESTAMP() AS _scd_valid_from,
CAST(NULL AS TIMESTAMP) AS _scd_valid_to,
TRUE AS _scd_current
FROM {S}.adventureworks.customer
) s
ON t.customer_id = s.customer_id
WHEN MATCHED THEN UPDATE SET
full_name = s.full_name, company_name = s.company_name, email = s.email
WHEN NOT MATCHED THEN INSERT
(customer_id, full_name, company_name, email, _scd_valid_from, _scd_valid_to, _scd_current)
VALUES (s.customer_id, s.full_name, s.company_name, s.email, s._scd_valid_from, s._scd_valid_to, s._scd_current)
""")
# COMMAND ----------
# MAGIC %md ## dim_product
spark.sql(f"""
MERGE INTO {G}.adventureworks.dim_product t
USING (
SELECT product_id, name, color, product_category, list_price
FROM {S}.adventureworks.product
) s
ON t.product_id = s.product_id
WHEN MATCHED THEN UPDATE SET name=s.name, color=s.color, product_category=s.product_category, list_price=s.list_price
WHEN NOT MATCHED THEN INSERT (product_id, name, color, product_category, list_price)
VALUES (s.product_id, s.name, s.color, s.product_category, s.list_price)
""")
# COMMAND ----------
# MAGIC %md ## fact_sales (overwrite estratΓ©gico por partiΓ§Γ£o "order_date")
spark.sql(f"""
CREATE OR REPLACE TABLE {G}.adventureworks.fact_sales
PARTITIONED BY (date_key)
AS
SELECT
h.sales_order_id,
d.sales_order_detail_id,
h.order_date AS date_key,
dc.customer_sk,
dp.product_sk,
d.order_qty, d.unit_price, d.line_total
FROM {S}.adventureworks.sales_order_header h
JOIN {S}.adventureworks.sales_order_detail d ON h.sales_order_id = d.sales_order_id
JOIN {G}.adventureworks.dim_customer dc ON dc.customer_id = h.customer_id AND dc._scd_current
JOIN {G}.adventureworks.dim_product dp ON dp.product_id = d.product_id
""")
# COMMAND ----------
# MAGIC %md ## OPTIMIZE + ZORDER (performance pro Power BI)
spark.sql(f"OPTIMIZE {G}.adventureworks.fact_sales ZORDER BY (customer_sk, product_sk)")
# COMMAND ----------
# MAGIC %md ## ValidaΓ§Γ£o: contagens + KPIs
display(spark.sql(f"""
SELECT
d.year, d.month,
COUNT(*) trips,
ROUND(SUM(f.line_total), 2) revenue
FROM {G}.adventureworks.fact_sales f
JOIN {G}.adventureworks.dim_date d ON f.date_key = d.date_key
GROUP BY d.year, d.month
ORDER BY 1,2
"""))

notebooks/30_data_quality.py

# Databricks notebook source
dbutils.widgets.text("catalog", "silver_prd")
C = dbutils.widgets.get("catalog")
checks = [
("customer sem email", f"SELECT COUNT(*) FROM {C}.adventureworks.customer WHERE email IS NULL", 0),
("orders com total negativo", f"SELECT COUNT(*) FROM {C}.adventureworks.sales_order_header WHERE total_due < 0", 0),
("detail sem header (orfΓ£o)", f"""
SELECT COUNT(*) FROM {C}.adventureworks.sales_order_detail d
LEFT JOIN {C}.adventureworks.sales_order_header h ON h.sales_order_id = d.sales_order_id
WHERE h.sales_order_id IS NULL
""", 0),
]
falhas = []
for name, sql, expected in checks:
actual = spark.sql(sql).collect()[0][0]
print(f"{name}: esperado <= {expected}, real = {actual}")
if actual > expected:
falhas.append((name, actual, expected))
if falhas:
msg = "Data quality fail:\n" + "\n".join([f"- {n}: {a} > {e}" for n,a,e in falhas])
dbutils.notebook.exit(msg)
print("βœ… DQ OK")

Parte 4 β€” Job DAB ΓΊnico (resources/case.job.yml)

resources:
jobs:
aw_diario_e2e:
name: aw_diario_e2e
schedule:
quartz_cron_expression: "0 0 9 * * ?" # 06h BRT
timezone_id: UTC
pause_status: ${var.bundle_target == "prd" ? "UNPAUSED" : "PAUSED"}
email_notifications:
on_failure: [ team.old.schoolll@gmail.com ]
no_alert_for_skipped_runs: true
max_concurrent_runs: 1
timeout_seconds: 1800 # 30 min SLA
tasks:
- task_key: ingest_bronze_to_silver
notebook_task:
notebook_path: ../notebooks/10_ingest_bronze_to_silver.py
base_parameters:
storage_account: ${var.storage_account}
catalog: ${var.silver_catalog}
data_processamento: "{{job.start_time.iso_date}}"
job_cluster_key: small
- task_key: data_quality
depends_on: [{ task_key: ingest_bronze_to_silver }]
notebook_task:
notebook_path: ../notebooks/30_data_quality.py
base_parameters:
catalog: ${var.silver_catalog}
job_cluster_key: small
- task_key: silver_to_gold
depends_on: [{ task_key: data_quality }]
notebook_task:
notebook_path: ../notebooks/20_silver_to_gold.py
base_parameters:
silver_catalog: ${var.silver_catalog}
gold_catalog: ${var.gold_catalog}
job_cluster_key: small
job_clusters:
- job_cluster_key: small
new_cluster:
spark_version: "15.4.x-scala2.12"
node_type_id: Standard_DS3_v2
num_workers: 0
data_security_mode: SINGLE_USER
spark_conf:
spark.databricks.cluster.profile: singleNode
spark.master: local[*, 4]
custom_tags:
projeto: de
ambiente: ${bundle.target}
case: aw_e2e

Parte 5 β€” ADF orquestra a Copy + dispara o job DAB

Atualize o pipeline ADF (pl_aw_diario_e2e):

  1. ForEach sobre uma lista de tabelas [Customer, SalesOrderHeader, SalesOrderDetail, Product]:
    • Cada iteraΓ§Γ£o: Copy de SalesLT.<tabela> β†’ bronze/source=adventureworks/schema=salesLT/table=<tabela>/ingestion_date=@{utcNow:yyyy-MM-dd}/.
  2. Depois do ForEach: Databricks Job activity (nΓ£o Notebook) β†’ dispara o job aw_diario_e2e por nome/ID.

A activity β€œDatabricks Job” usa a API /jobs/run-now. Permite passar notebook_params se quiser sobrescrever. Diferente da β€œNotebook activity” que sobe cluster do zero.

Parte 6 β€” Power BI

6.1 β€” Instalar conector Databricks (uma vez)

Power BI Desktop β†’ Get Data β†’ Azure Databricks (vem nativo).

6.2 β€” Conectar

  • Server hostname: adb-xxxxxxx.xx.azuredatabricks.net.
  • HTTP path: do warehouse wh-prd-bi β†’ painel SQL Warehouses β†’ seu warehouse β†’ Connection details β†’ copia.
  • Authentication: AAD (Microsoft account).
  • Catalog: gold_prd.
  • Schema: adventureworks.

Importe fact_sales, dim_customer, dim_product, dim_date. Storage mode: Direct Query (nΓ£o importa tudo; usa o warehouse pra cada consulta).

6.3 β€” Modelo estrela

No Power BI, garanta:

  • fact_sales[date_key] ↔ dim_date[date_key].
  • fact_sales[customer_sk] ↔ dim_customer[customer_sk].
  • fact_sales[product_sk] ↔ dim_product[product_sk].
  • Cardinalidades: muitos pra um, single direction.

6.4 β€” Medidas DAX

Revenue = SUMX(fact_sales, fact_sales[order_qty] * fact_sales[unit_price])
Avg Ticket = DIVIDE(SUM(fact_sales[line_total]), DISTINCTCOUNT(fact_sales[sales_order_id]))
YoY Revenue % =
VAR cur = [Revenue]
VAR prev = CALCULATE([Revenue], SAMEPERIODLASTYEAR(dim_date[date_key]))
RETURN DIVIDE(cur - prev, prev)

6.5 β€” VisualizaΓ§Γ΅es

  • Card: Revenue total, Avg Ticket.
  • Line chart: Revenue por mΓͺs (eixo: dim_date.month_name).
  • Bar chart: Top 10 clientes por revenue.
  • Bar chart: Avg Ticket por product_category.

6.6 β€” Publicar

Power BI Service β†’ workspace β†’ Publish. Configure refresh agendado (apΓ³s o job das 06h BRT, ex.: 06h30) para garantir dado fresco.

Parte 7 β€” Monitoramento + alertas

7.1 β€” Action Group (Azure Monitor)

Terminal window
az monitor action-group create `
--resource-group $RG `
--name ag-data-alerts `
--short-name dataalerts `
--email-receivers name=eu email=team.old.schoolll@gmail.com

7.2 β€” Alerta de falha de Job Databricks

Atalho: o email_notifications.on_failure no DAB jΓ‘ cobre. Para sofisticar:

  • Workspace β†’ Jobs β†’ seu job β†’ Edit alerts β†’ adicione webhook (Slack/Teams), notificaΓ§Γ£o por SLA exceeded.

7.3 β€” Alerta de ADF

ADF β†’ Alerts β†’ + New β†’ mΓ©trica PipelineFailedRuns > 0, frequΓͺncia 5 min, action group ag-data-alerts.

7.4 β€” Lakehouse Monitor (opcional, premium)

Databricks tem Lakehouse Monitoring: estatΓ­sticas + drift por tabela. Ative na fact_sales:

CREATE MONITORING FOR TABLE gold_prd.adventureworks.fact_sales
PROFILE TYPE TIME_SERIES (
TIMESTAMP_COL_EXPR = date_key,
GRANULARITIES = (MONTH)
)
SCHEDULE EVERY 1 DAY AT 07:00 TIMEZONE 'America/Sao_Paulo';

Parte 8 β€” Custo final estimado (mΓͺs, dev contido)

ComponenteCusto aprox / mΓͺs
ADLS Gen2 (50 GB Hot + 100 GB Cool)USD 1.20
Azure SQL Serverless (sample, auto-pause)USD 1-2
ADF (30 execuΓ§Γ΅es diΓ‘rias)USD 0.40
Databricks Job Cluster (Single Node, 15 min/dia Γ— 30)USD 7
SQL Warehouse Pro Serverless (1h ativo/dia)USD 10
Key Vault, monitor, logsUSD 1
Total~USD 20-25/mΓͺs

Cabe nos USD 200 do free tier por 8 meses.

Parte 9 β€” Como contar este case em entrevista

Use STAR:

SituaΓ§Γ£o: β€œTime de BI usava extraΓ§Γ΅es manuais Excel da base SQL. Quebrava semanalmente, sem histΓ³rico.”

Tarefa: β€œConstruir lakehouse end-to-end com refresh diΓ‘rio, governanΓ§a e dashboards confiΓ‘veis.”

AΓ§Γ£o:

  1. Modelei medallion (bronze raw, silver tipado/dedup, gold estrela).
  2. IngestΓ£o: ADF Copy diΓ‘rio do SQL para ADLS Gen2 particionado por data.
  3. TransformaΓ§Γ£o: notebooks PySpark com MERGE upsert no silver, agregaΓ§Γ£o em star schema no gold. Tudo Delta.
  4. GovernanΓ§a: Unity Catalog (catΓ‘logos por camada, external locations, grants por grupo, row filter na visΓ£o de PII para analistas).
  5. CI/CD: Databricks Asset Bundles versionados em GitHub, GH Actions deploy automΓ‘tico em dev e gated em prd.
  6. OrquestraΓ§Γ£o: ADF dispara o job multi-task (ingest β†’ DQ check β†’ transform). Falha em DQ aborta downstream.
  7. Consumo: SQL Warehouse Serverless + Power BI Direct Query.
  8. Observabilidade: email on failure, ADF alerts, Lakehouse Monitor.

Resultado:

  • Dashboard atualizado em < 30 min apΓ³s meia-noite.
  • Custo USD ~25/mΓͺs.
  • 0 incidentes de qualidade no 1ΒΊ mΓͺs (DQ check derrubando antes do gold).
  • Lineage UC documenta cada coluna do dashboard atΓ© a fonte β†’ audit ready.

Pegadinhas finais

  1. Job multi-task no DAB: cada task tem job_cluster_key. Mesmas keys = mesmo cluster reaproveitado (mais barato). Keys diferentes = clusters separados (isolado, mais caro).
  2. {{job.start_time.iso_date}}: macro do Databricks Job β€” passa a data no formato YYYY-MM-DD. Use no data_processamento.
  3. Power BI conectado em catΓ‘logo errado: usuΓ‘rio tΓ­pico vΓͺ dados de dev por engano. Use SQL Warehouse separado por ambiente.
  4. OPTIMIZE durante leitura ativa: pode causar pequena latΓͺncia. Rode antes do horΓ‘rio de pico do BI.
  5. MERGE em tabela sem partiΓ§Γ΅es/ZORDER nas chaves: full scan β†’ lento. Sempre Z-order pela chave de merge.

Checklist final da trilha

  • Pipeline rodou pelo menos 1 vez end-to-end em dev.
  • Dashboard Power BI mostra os 3 KPIs do briefing.
  • RepositΓ³rio Git com databricks.yml, notebooks, workflow deploy.yml.
  • Tabelas registradas em Unity Catalog (silver_prd e gold_prd).
  • Alertas: email on failure configurado, ADF alert criado.
  • VocΓͺ consegue explicar em 5 minutos todo o pipeline desenhando no papel.
  • VocΓͺ descreveu o case STAR em atΓ© 3 minutos.

E agora?

VocΓͺ terminou a trilha. Para subir o nΓ­vel:

  1. Substitua o Single Node por cluster com workers e meΓ§a o ganho num dataset 10x.
  2. Adicione Streaming: Event Hubs β†’ Auto Loader β†’ silver streaming.
  3. DLT (Delta Live Tables): refatore o silver/gold como DLT pipeline e veja a comparaΓ§Γ£o.
  4. Tire DP-203 ou DP-700.
  5. Tire Databricks DE Associate.
  6. Publique este repositΓ³rio como case pΓΊblico no LinkedIn (sem secrets, claro).

Boa carreira de DE Sr. πŸš€

← Anterior
M07 Cicd Dab Github
PrΓ³ximo β†’
M09 Case Uber Eats Azure