Un lakehouse alimentato da pipeline incrementali produce, senza manutenzione, migliaia di file Parquet minuscoli. Su Delta Lake ogni MERGE o APPEND scrive nuovi file e aggiunge una entry al transaction log: dopo qualche centinaio di esecuzioni la tabella “logica” da 10 GB è fisicamente composta da 40.000 file da 250 KB. È il small file problem, e in Microsoft Fabric colpisce due volte: rallenta Spark e SQL analytics endpoint, e degrada il caricamento del modello semantico Direct Lake.

Perché i file piccoli costano

Ogni file Parquet comporta overhead fisso: apertura, lettura del footer, decompressione del dizionario, task Spark dedicato. Con file da poche centinaia di KB il tempo di I/O e di scheduling supera il tempo di elaborazione utile. In più il file skipping basato sulle statistiche min/max del Delta log diventa inefficace: se ogni file contiene poche righe sparse su tutto il dominio, nessun file viene scartato e la query fa full scan.

La dimensione target di riferimento è 128 MB – 1 GB per file, con row group Parquet ampi.

OPTIMIZE: bin-compaction e Z-Order

OPTIMIZE risolve il problema riscrivendo i dati:

Operazione Cosa fa Quando serve
Bin-compaction Unisce molti file piccoli in pochi file grandi (circa 1 GB) Sempre, dopo carichi incrementali frequenti
Z-Order Co-localizza i valori di 1–4 colonne nello stesso file, migliorando il data skipping Colonne usate spesso in filtri/join ad alta cardinalità
VACUUM Rimuove i file non più referenziati oltre la retention (default 7 giorni) Dopo OPTIMIZE, per non far esplodere lo storage
OPTIMIZE Vendite ZORDER BY (DataOrdine, IdCliente);
VACUUM Vendite RETAIN 168 HOURS;

Attenzione: OPTIMIZE non elimina i file vecchi, li dereferenzia soltanto. Senza VACUUM lo spazio in OneLake cresce e il time travel resta possibile ma costoso. Z-Order su troppe colonne (>4) diluisce il beneficio; su colonne a bassissima cardinalità è inutile — meglio il partizionamento.

Quando pianificarla: come step finale della pipeline di ingestion (notebook con OPTIMIZE dopo il MERGE), oppure come job schedulato in finestra a bassa concorrenza — tipicamente notturno. OPTIMIZE è write-heavy e consuma capacity unit: eseguirlo a ogni micro-batch è un antipattern. In alternativa si usa la voce Table maintenance del Lakehouse (menu contestuale sulla tabella → Maintenance), che espone OPTIMIZE con V-Order e VACUUM senza scrivere codice.

V-Order: ottimizzazione di scrittura specifica di Fabric

V-Order è un layer di ottimizzazione applicato in scrittura ai file Parquet: ordinamento speciale delle righe, encoding a dizionario e compressione pensati per il motore VertiPaq di Power BI. Il risultato resta Parquet standard, leggibile da qualunque engine conforme, ma con letture 10–50% più rapide da parte di VertiPaq e footprint compresso minore.

Il beneficio si concentra su Direct Lake: il modello semantico non copia i dati, li carica in memoria (transcoding) leggendo direttamente i file Parquet. File V-Ordered significa transcoding più veloce, cold cache più corta, refresh visuale più rapido.

Controllo:

  • sessione Spark: spark.conf.set("spark.sql.parquet.vorder.enabled", "true")
  • per tabella: TBLPROPERTIES ("delta.parquet.vorder.enabled" = "true")
  • in maintenance: OPTIMIZE ... VORDER

Trade-off: V-Order aumenta il tempo di scrittura (tipicamente 10–15%) e il consumo CPU. Nei layer bronze write-heavy, dove i dati vengono solo atterrati e mai letti da un modello semantico, conviene disattivarlo e riattivarlo su silver/gold, dove le letture analitiche dominano. Stessa logica per Warehouse: i nuovi Warehouse Fabric non applicano V-Order di default proprio perché privilegiano il throughput di scrittura.

Il collegamento con il fallback Direct Lake

Direct Lake ha delle guardrail legate allo SKU della capacity (numero di file Parquet, numero di row group, dimensione del modello). Una tabella frammentata in decine di migliaia di file può superare i limiti: il modello smette di usare Direct Lake e ricade in DirectQuery verso l’SQL analytics endpoint — query più lente, nessuna cache in memoria. In Performance Analyzer e nella vista Direct Lake del modello, la presenza di query “DirectQuery” al posto di “Direct Lake” è il sintomo diagnostico. Il comportamento è governato dalla proprietà DirectLakeBehavior (Automatic, DirectQuery only, Direct Lake only): impostandola su Direct Lake only le query falliscono invece di degradare silenziosamente — utile per accorgersi del problema in test.

Trappole tipiche d’esame

  • Scenario: il refresh di un report Direct Lake è lentissimo e la tabella ha 30.000 file da 200 KB da un MERGE orario → risposta: eseguire OPTIMIZE (bin-compaction) e schedulare la table maintenance, non aumentare lo SKU.
  • Scenario: dopo OPTIMIZE lo storage OneLake è raddoppiato → risposta: eseguire VACUUM; OPTIMIZE dereferenzia ma non cancella i file storici.
  • Scenario: la pipeline di ingestion bronze è diventata più lenta dopo aver abilitato ottimizzazioni globali → risposta: disabilitare V-Order sulle tabelle bronze write-heavy, mantenerlo su silver/gold.
  • Scenario: query filtrate su IdCliente scansionano tutta la tabella nonostante file grandi → risposta: applicare ZORDER BY (IdCliente) per abilitare il data skipping, non aggiungere partizioni ad alta cardinalità.
  • Scenario: Performance Analyzer mostra che le visual usano DirectQuery invece di Direct Lake → risposta: si è verificato un fallback per superamento delle guardrail; ridurre i file con OPTIMIZE + V-Order o salire di SKU.