M06 — Unity Catalog + Governança
Duração: 3h Pré-requisitos: M01-M05 ok. Workspace Databricks Premium, Storage com bronze/silver/gold, pelo menos uma tabela Delta no Silver. Objetivo: habilitar Unity Catalog, criar metastore, catalogs, schemas, external locations, registrar suas tabelas, configurar grants e ver lineage.
Por que Unity Catalog
Antes do UC, cada workspace tinha seu próprio Hive Metastore. Permissões eram em nível de cluster (“Table ACL”). Lineage não existia.
Com UC:
- 1 metastore por região, compartilhado entre workspaces.
- Modelo 3-níveis:
catalog.schema.table(vs 2-níveis antigoschema.table). - Grants centralizados (
GRANT SELECT ON TABLE catalog.schema.tabela TO grupo). - Lineage automática (de coluna a coluna).
- Audit logs com quem leu o quê.
- Volumes (governança para arquivos não-tabulares).
- External locations (caminhos ADLS governados centralmente).
Arquitetura
ACCOUNT (sua conta Databricks corporativa) └─ METASTORE (1 por região) ├─ CATALOG bronze_dev │ ├─ SCHEMA nyctaxi │ │ └─ TABLE yellow_trips_raw │ └─ SCHEMA adventureworks ├─ CATALOG silver_dev ├─ CATALOG gold_dev └─ EXTERNAL LOCATIONS (referência a caminhos ADLS) ├─ ext_loc_bronze ──► abfss://bronze@stdewdevbrs.dfs.core.windows.net/ ├─ ext_loc_silver ──► abfss://silver@stdewdevbrs.dfs.core.windows.net/ └─ ext_loc_gold ──► abfss://gold@stdewdevbrs.dfs.core.windows.net/ └─ STORAGE CREDENTIALS (acesso ao storage) └─ sc_adls_dev (Access Connector for Databricks)Parte 1 — Verificar / habilitar UC na conta
Em Azure Databricks, a Account Console fica em https://accounts.azuredatabricks.net (precisa de Global Admin ou Databricks Account Admin). Como você é o owner da subscription, é você mesmo.
Passo 1.1 — Acessar Account Console
- No workspace → canto superior direito → seu e-mail → Manage account.
- Ou abra direto:
https://accounts.azuredatabricks.net.
Passo 1.2 — Criar metastore (se ainda não existe)
- Menu esquerdo → Data → Create metastore.
- Nome:
metastore-brazilsouth. - Region: Brazil South.
- ADLS Gen2 path para metastore-managed tables: você precisa outro container dedicado (não use bronze/silver/gold). Crie:
az storage container create --account-name $ST --name "uc-metastore" --auth-mode loginPath no campo: abfss://uc-metastore@stdewdevbrs.dfs.core.windows.net/.
- Access connector for Azure Databricks: precisa ser criado uma vez — é um recurso Azure que dá identidade gerenciada ao UC.
$AC = "ac-databricks-uc-brs"az databricks access-connector create ` --resource-group $RG ` --name $AC ` --location $REG ` --identity-type SystemAssigned
# Pega o ID para colar no Account Console:az databricks access-connector show -g $RG -n $AC --query id -o tsvCole o ID em Access connector ID.
- Create → atribua ao seu workspace
dbw-de-dev-brs.
Passo 1.3 — Dar à MI do Access Connector permissão no storage
$ACMI = az databricks access-connector show -g $RG -n $AC --query identity.principalId -o tsv$STID = az storage account show -n $ST -g $RG --query id -o tsv
az role assignment create ` --assignee $ACMI ` --role "Storage Blob Data Contributor" ` --scope $STIDParte 2 — Storage Credential + External Locations
Passo 2.1 — Storage Credential
No workspace UI:
- Catalog Explorer (ícone de banco de dados, esquerda).
- External Data → Storage Credentials → Create credential.
- Nome:
sc_adls_dev. - Tipo: Azure Managed Identity.
- Access Connector ID: o
iddoac-databricks-uc-brs(do passo anterior). - Create.
Ou via SQL:
CREATE STORAGE CREDENTIAL sc_adls_dev WITH AZURE_MANAGED_IDENTITY ( `access_connector_id` = '/subscriptions/<sub>/resourceGroups/rg-de-dev-brs/providers/Microsoft.Databricks/accessConnectors/ac-databricks-uc-brs' );Passo 2.2 — External Locations (3, uma por camada)
Pra cada container, uma external location:
CREATE EXTERNAL LOCATION ext_loc_bronze URL 'abfss://bronze@stdewdevbrs.dfs.core.windows.net/' WITH (STORAGE CREDENTIAL sc_adls_dev) COMMENT 'raw zone';
CREATE EXTERNAL LOCATION ext_loc_silver URL 'abfss://silver@stdewdevbrs.dfs.core.windows.net/' WITH (STORAGE CREDENTIAL sc_adls_dev);
CREATE EXTERNAL LOCATION ext_loc_gold URL 'abfss://gold@stdewdevbrs.dfs.core.windows.net/' WITH (STORAGE CREDENTIAL sc_adls_dev);Valida:
SHOW EXTERNAL LOCATIONS;Parte 3 — Catalogs e Schemas
-- Catalog por camada (ou por domínio — depende da estratégia da empresa)CREATE CATALOG IF NOT EXISTS bronze_dev MANAGED LOCATION 'abfss://uc-metastore@stdewdevbrs.dfs.core.windows.net/bronze_dev/';CREATE CATALOG IF NOT EXISTS silver_dev MANAGED LOCATION 'abfss://uc-metastore@stdewdevbrs.dfs.core.windows.net/silver_dev/';CREATE CATALOG IF NOT EXISTS gold_dev MANAGED LOCATION 'abfss://uc-metastore@stdewdevbrs.dfs.core.windows.net/gold_dev/';
-- Schemas (= bancos)CREATE SCHEMA IF NOT EXISTS silver_dev.nyctaxi COMMENT 'Camada Silver - dataset NYC Taxi';
CREATE SCHEMA IF NOT EXISTS silver_dev.adventureworks COMMENT 'Camada Silver - dataset AdventureWorks';
CREATE SCHEMA IF NOT EXISTS gold_dev.nyctaxi;Padrão alternativo (mais comum em empresa grande): catalog por domínio de negócio (
vendas,marketing,rh), schemas por camada (bronze,silver,gold). Discuta com seu time qual estratégia adotar.
Parte 4 — Registrar a tabela do M04 como External Table
A tabela Silver do M04 foi escrita direto em abfss://silver@.../domain=nyctaxi/yellow_trips/ (sem passar por UC). Vamos “registrá-la”:
CREATE TABLE IF NOT EXISTS silver_dev.nyctaxi.yellow_trips USING DELTA LOCATION 'abfss://silver@stdewdevbrs.dfs.core.windows.net/domain=nyctaxi/yellow_trips/';Valida:
DESCRIBE EXTENDED silver_dev.nyctaxi.yellow_trips;SELECT COUNT(*) FROM silver_dev.nyctaxi.yellow_trips;SELECT pickup_date, COUNT(*) qtd FROM silver_dev.nyctaxi.yellow_trips GROUP BY pickup_date ORDER BY pickup_date LIMIT 5;Managed vs External Tables
| Managed | External | |
|---|---|---|
| Quem controla os arquivos | UC | Você |
DROP TABLE apaga arquivos? | Sim | Não, só desregistra |
| Caso de uso | Maioria | Tabelas em paths já existentes ou compartilhados com outras tools |
Recomendação: para a trilha, External (já temos os paths). Em greenfield, Managed.
Parte 5 — Adaptar o notebook M04 para usar UC
Substitua a parte final do M04 por:
silver_table = "silver_dev.nyctaxi.yellow_trips"
( df_dedup .write .format("delta") .mode("overwrite") .option("overwriteSchema", "true") .partitionBy("pickup_date") .saveAsTable(silver_table) # em vez de .save(path))
display(spark.table(silver_table).limit(5))Vantagem: você nunca mais precisa lembrar o path. Lê com spark.table("silver_dev.nyctaxi.yellow_trips") ou via SQL SELECT * FROM silver_dev.nyctaxi.yellow_trips.
Parte 6 — Camada Gold: fato + dim
Vamos criar um exemplo Gold a partir do Silver. Notebook 03_silver_to_gold.py:
# Databricks notebook sourcefrom pyspark.sql import functions as F
silver = spark.table("silver_dev.nyctaxi.yellow_trips")
# COMMAND ----------# MAGIC %md ## Dim Date
dim_date = ( silver.select(F.col("pickup_date").alias("date_key")) .distinct() .withColumn("year", F.year("date_key")) .withColumn("month", F.month("date_key")) .withColumn("day", F.dayofmonth("date_key")) .withColumn("weekday", F.dayofweek("date_key")) .withColumn("is_weekend", F.col("weekday").isin([1, 7])))
dim_date.write.format("delta").mode("overwrite").saveAsTable("gold_dev.nyctaxi.dim_date")
# COMMAND ----------# MAGIC %md ## Dim Location (vai precisar de lookup externo; aqui só com IDs)
dim_location = ( silver.select(F.col("pu_location_id").alias("location_id")).distinct() .unionByName(silver.select(F.col("do_location_id").alias("location_id")).distinct()) .distinct() .orderBy("location_id"))dim_location.write.format("delta").mode("overwrite").saveAsTable("gold_dev.nyctaxi.dim_location")
# COMMAND ----------# MAGIC %md ## Fact Trips (granularidade: 1 linha = 1 corrida)
fact_trips = ( silver .select( F.col("pickup_date").alias("date_key"), F.col("pu_location_id"), F.col("do_location_id"), F.col("vendor_id"), F.col("passenger_count"), F.col("trip_distance_km"), F.col("trip_duration_min"), F.col("fare_amount"), F.col("tip_amount"), F.col("total_amount"), F.col("payment_type"), ))( fact_trips .write.format("delta").mode("overwrite") .partitionBy("date_key") .saveAsTable("gold_dev.nyctaxi.fact_trips"))
# COMMAND ----------# MAGIC %md ## Validação
display(spark.sql(""" SELECT d.year, d.month, COUNT(*) trips, AVG(f.total_amount) avg_total FROM gold_dev.nyctaxi.fact_trips f JOIN gold_dev.nyctaxi.dim_date d ON f.date_key = d.date_key GROUP BY d.year, d.month ORDER BY 1,2"""))Parte 7 — Grants (mínimo viável)
Conceito de hierarquia
Permissões herdam do nível superior:
metastore → catalog → schema → tableGrant em catalog vale pra todos os schemas dentro.
Grants úteis
-- Time de analytics lê tudo do Gold:GRANT USE CATALOG ON CATALOG gold_dev TO `grp-data-analysts`;GRANT USE SCHEMA ON SCHEMA gold_dev.nyctaxi TO `grp-data-analysts`;GRANT SELECT ON SCHEMA gold_dev.nyctaxi TO `grp-data-analysts`;
-- DE escreve em silver/gold:GRANT USE CATALOG, CREATE SCHEMA ON CATALOG silver_dev TO `grp-data-engineers`;GRANT ALL PRIVILEGES ON SCHEMA silver_dev.nyctaxi TO `grp-data-engineers`;
-- Cientista vê só uma tabela:GRANT USE CATALOG ON CATALOG silver_dev TO `cientista@empresa.com`;GRANT USE SCHEMA ON SCHEMA silver_dev.nyctaxi TO `cientista@empresa.com`;GRANT SELECT ON TABLE silver_dev.nyctaxi.yellow_trips TO `cientista@empresa.com`;Ver permissões
SHOW GRANTS ON TABLE silver_dev.nyctaxi.yellow_trips;SHOW GRANTS `grp-data-analysts`;Row Filters / Column Masks (avançado, cai em entrevista)
-- Função que retorna TRUE se o usuário pode ver a linhaCREATE FUNCTION mascara_estado(uf STRING)RETURNS BOOLEAN RETURN IS_MEMBER('admin') OR uf = 'SP'; -- todo mundo só vê SP, exceto admin
ALTER TABLE silver_dev.clientes.cadastroSET ROW FILTER mascara_estado ON (uf);
-- Mask: ofusca colunasCREATE FUNCTION mascara_email(email STRING)RETURNS STRING RETURN CASE WHEN IS_MEMBER('admin') THEN email ELSE regexp_replace(email, '(.+)@', '****@') END;
ALTER TABLE silver_dev.clientes.cadastroALTER COLUMN email SET MASK mascara_email;Parte 8 — Lineage
UC captura automaticamente. Para ver:
- Catalog Explorer →
silver_dev→nyctaxi→yellow_trips. - Aba Lineage.
- Você vê:
- Upstream: notebooks/jobs que ESCREVERAM nessa tabela.
- Downstream: o que LEU.
- Column lineage: quais colunas do gold vieram de quais colunas do silver.
Para lineage aparecer, precisa rodar pelo menos uma vez no cluster UC-enabled após criar a tabela.
Parte 9 — Volumes (governança de arquivos não-tabulares)
Útil para CSV pré-processado, modelos ML, imagens. Substitui dbfs:/ em UC.
CREATE VOLUME silver_dev.nyctaxi.landing COMMENT 'arquivos brutos antes de virarem tabela';
-- Path acessível:-- /Volumes/silver_dev/nyctaxi/landing/<arquivo>Em notebook:
files = dbutils.fs.ls("/Volumes/silver_dev/nyctaxi/landing/")Pegadinhas
- Cluster não-UC (modo
No isolation shared) não vê catalogs UC. Garantadata_security_mode: SINGLE_USERouUSER_ISOLATION(M03). PERMISSION_DENIEDem external location: a MI do Access Connector não tem role no storage. Volte ao Passo 1.3.- Confundir Storage Credential com External Location: SC = “como acessa o storage”; EL = “qual caminho está liberado”.
- Criar tabela apontando para path fora de external location → falha. EL precisa cobrir o path.
DROP TABLEem managed table apaga os dados! External só desregistra. Cuidado com o tipo.SHOW GRANTSretorna vazio → você está olhando o nível errado da hierarquia. Confira no nível de catalog/schema também.
Audit logs
Logs de uso ficam em system tables UC:
SELECT * FROM system.access.audit WHERE event_date >= current_date() - 7 AND service_name = 'unityCatalog' ORDER BY event_time DESC LIMIT 100;Útil pra “quem leu PII na semana passada”.
Checklist de saída
- Metastore criado e atribuído ao workspace.
- Access Connector com role
Storage Blob Data Contributorno storage. - Storage Credential + 3 External Locations (bronze, silver, gold).
- Catalogs
bronze_dev,silver_dev,gold_devcriados. - Schema
silver_dev.nyctaxicom a tabelayellow_trips. - Notebook M04 adaptado para
saveAsTable("silver_dev.nyctaxi.yellow_trips"). - Notebook 03 cria pelo menos
fact_tripsedim_dateno Gold. - Conseguiu ver pelo menos 1 link de lineage.
- Sabe responder em entrevista: managed vs external, hierarquia de grants, row filter / column mask.