Como criar partições dinâmicas em tabelas Delta no Lakehouse
Este tutorial mostra como criar partições dinâmicas numa tabela Delta no Lakehouse para melhorar a leitura e escrita de dados. Particionar correctamente reduz custos de I/O, acelera consultas e evita ficheiros pequenos quando se escreve em paralelo. Além disso, partições bem definidas facilitam operações de limpeza e retenção e ajudam o engine a aplicar partition pruning, o que pode reduzir o volume lido em 10x a 100x em cenários comuns.
Pré-requisitos
- Conta com acesso ao Microsoft Fabric e um Lakehouse criado.
- Permissões para criar/alterar tabelas Delta no Lakehouse.
- Ficheiros de exemplo (CSV/JSON) carregados num path do Lakehouse ou OneLake.
- Noções básicas de SQL e PySpark.
Passo 1: Escolher a coluna de partição certa
Porquê: uma boa coluna de partição equilibra o número de ficheiros e a selectividade das consultas. Evite colunas com alta cardinalidade (ex.: ID único com milhões de valores) — isso cria muitas pequenas partições e aumenta a sobrecarga de metadados. Também não use colunas com cardinalidade muito baixa (ex.: um campo com sempre o mesmo valor), porque aí não há benefício. Para dados temporais, usar uma hierarquia como year/month[/day] é uma prática comum. Por exemplo:
- Datas: year/month — bom equilíbrio para anos com ~12 meses e centenas de milhares a milhões de registos por mês.
- Alta cardinalidade: user_id com 10M de utilizadores — não usar como partição.
- Baixa cardinalidade: country com 5-10 valores — não suficiente para redução significativa de I/O.
Regra prática: cada partição deve conter pelo menos dezenas de MB a centenas de MB (ex.: 100-500 MB) para ser eficiente com ficheiros Delta. Se as suas partições ficarem consistentemente abaixo de ~64 MB, pense em reduzir a granularidade das partições.
Passo 2: Criar uma tabela Delta não particionada (opcional, para importar dados iniciais)
É útil começar sem partição para validar esquema e depois converter para particionado quando tiver carga de dados real. Isto ajuda a verificar tipos e nulos com dados reais antes de definir partições definitivas. Exemplo em SQL no SQL Analytics endpoint ou notebook:
CREATE TABLE IF NOT EXISTS my_db.raw_events_delta
USING DELTA
LOCATION 'lakehouse:////raw/events'
AS SELECT * FROM csv.`/paths/events/*.csv`;
Depois de importar alguns milhões de linhas, inspecione amostras e observe a distribuição temporal para decidir se usa year/month/day.
Passo 3: Criar tabela Delta particionada com schema final
Crie a tabela com PARTITIONED BY. Aqui usamos year e month extraídos da coluna event_timestamp. Isto cria uma estrutura de pastas organizada, por exemplo /year=2024/month=06/, que facilita pruning e gestão de dados.
CREATE TABLE IF NOT EXISTS my_db.events_delta
(
event_id STRING,
event_timestamp TIMESTAMP,
user_id STRING,
event_type STRING,
payload STRING
)
USING DELTA
PARTITIONED BY (year, month)
LOCATION 'lakehouse:////tables/events';
Nota: escolher PARTITIONED BY evita a necessidade de criar colunas físicas separadas para year e month se preferir extraí-las no SELECT, mas ter colunas explícitas facilita queries e índices.
Passo 4: Escrever dados usando partições dinâmicas com PySpark
Ao escrever, calcule year e month e use write.partitionBy para criar partições dinâmicas. Isto garante que os ficheiros são gravados na pasta correcta conforme a data do evento. Exemplo PySpark minimal:
from pyspark.sql.functions import year, month, to_timestamp
df = spark.read.csv('/mnt/lake/events/*.csv', header=True)
df = df.withColumn('event_timestamp', to_timestamp(df.event_timestamp))
df = df.withColumn('year', year(df.event_timestamp))
df = df.withColumn('month', month(df.event_timestamp))
(df.write
.format('delta')
.mode('append')
.partitionBy('year','month')
.save('lakehouse:////tables/events'))
Exemplo concreto: se tiver 10M de registos por mês e um cluster com 200 executores, estime os ficheiros finais por partição e ajuste repartition para atingir ~128 MB por ficheiro.
Passo 5: Evitar ficheiros pequenos (small files) ao escrever em paralelo
Erro comum: cada executor escreve poucos MB, criando centenas de ficheiros pequenos por partição, o que degrada o desempenho. Soluções:
- Aumentar o tamanho dos ficheiros com coalesce/repartition antes de escrever: por exemplo, se pretende ficheiros ~128 MB e tem 1 TB de dados, estime 8000 ficheiros e gere repartition(8000).
- Reparticionar por uma chave de partição lógica (ex.: concat year-month) para agrupar dados da mesma partição antes do write.
- Executar OPTIMIZE / compaction depois da ingestão para combinar ficheiros pequenos (quando suportado no ambiente).
# Reparticionar por partição aproximada antes de escrever
from pyspark.sql.functions import concat_ws
# Criar uma coluna de partition_key para reparticionar
df = df.withColumn('partition_key', concat_ws('-', df.year.cast('string'), df.month.cast('string')))
# Repartition por partition_key para reduzir ficheiros
df = df.repartition('partition_key')
(df.drop('partition_key')
.write
.format('delta')
.mode('append')
.partitionBy('year','month')
.save('lakehouse:////tables/events'))
Passo 6: Atualizar esquema ou adicionar partições
Se adicionar novas colunas, use ALTER TABLE para evitar reescrever toda a tabela. Para detectar novas pastas/partições externas, alguns ambientes suportam comandos como MSCK REPAIR; outros oferecem SHOW PARTITIONS. Exemplos:
ALTER TABLE my_db.events_delta ADD COLUMNS (device STRING);
-- Listar partições
SHOW PARTITIONS my_db.events_delta;
Se for necessário migrar de não particionado para particionado com milhões de linhas, faça uma escrita controlada em batch para evitar picos de I/O.
Verificar o resultado
Confirme que as partições existem e que queries fazem partition pruning. Execute:
-- Ver partições físicas
SHOW PARTITIONS my_db.events_delta;
-- Teste de partition pruning (verifica planos)
EXPLAIN SELECT * FROM my_db.events_delta WHERE year = 2024 AND month = 6;
-- Contar por partição
SELECT year, month, COUNT(*) FROM my_db.events_delta GROUP BY year, month ORDER BY year, month;
Se EXPLAIN mostrar que apenas os caminhos year=2024/month=6 são lidos, o pruning está a funcionar. Monitorize tempos de consulta e I/O: idealmente verá redução significativa quando o filtro corresponder a partições.
Conclusão
Partições dinâmicas em tabelas Delta no Lakehouse melhoram o desempenho e o custo quando bem escolhidas. Comece por uma granularidade conservadora (year/month), monitorize a distribuição de dados e o tamanho médio dos ficheiros e ajuste repartition na escrita. Próximos passos: automatizar partição em pipelines de ingestão, usar OPTIMIZE e ZORDER (quando suportado) para melhorar a ordenação interna e reduzir a latência de leitura, e criar alertas para detectar crescimento de ficheiros pequenos. Dica prática: vise ficheiros de ~128–256 MB por partição e reveja a estratégia a cada trimestre conforme o crescimento dos dados.