L’architettura medallion in Microsoft Fabric non è un obbligo tecnico ma una convenzione di responsabilità: ogni layer risponde a una domanda diversa e, soprattutto, ha una diversa tolleranza alla riscrittura. Capire dove collocare deduplica, tipizzazione e business logic è ciò che l’esame DP-600 verifica davvero.

I tre layer e le loro responsabilità

Layer Contenuto Operazione dominante Chi lo consuma
Bronze Copia fedele della sorgente, nessuna trasformazione APPEND (immutabile, storicizzato) Solo pipeline interne
Silver Dati puliti, tipizzati, deduplicati, conformati MERGE (upsert) Data scientist, analisti avanzati
Gold Modello dimensionale a star schema, aggregati MERGE / overwrite selettivo Semantic model, report Power BI

Bronze è append-only per un motivo preciso: se sbagli la logica di pulizia, devi poter ricostruire silver e gold senza ri-interrogare la sorgente. Qui non si fa casting, non si scartano righe “sporche”, non si applica filtro di business. Si conserva anche il timestamp di ingestion (_ingested_at) e la provenienza del file.

Silver è dove avviene il lavoro sporco: cast espliciti dei tipi, normalizzazione di date e valute, deduplica sulla business key, gestione dei valori nulli, join di conformazione fra sistemi diversi (il cliente CRM e il cliente ERP diventano un’unica anagrafica). Silver resta ancora in forma normalizzata o quasi.

Gold modella per il consumo: dimensioni e fatti, surrogate key, SCD Type 2 dove serve storicizzare gli attributi, misure precalcolate. È il layer su cui costruisci il semantic model in Direct Lake, quindi deve essere in Delta con V-Order abilitato e senza eccessiva frammentazione di file (usa OPTIMIZE periodico).

Il carico incrementale con watermark

Il pattern canonico in Data Pipelines di Fabric si compone di tre passaggi.

  1. Lookup del watermark corrente: un’attività Lookup legge il valore massimo già caricato, tipicamente da una tabella di controllo nel lakehouse o dal warehouse (SELECT MAX(LastModifiedDate) FROM silver.orders).
  2. Copy activity parametrizzata: la query verso la sorgente filtra WHERE LastModifiedDate > @watermark AND LastModifiedDate <= @nuovoWatermark. L’estremo superiore fisso evita di perdere righe scritte durante l’esecuzione.
  3. MERGE su Delta: un notebook Spark (o T-SQL sul warehouse) applica l’upsert.
from delta.tables import DeltaTable

target = DeltaTable.forName(spark, "silver.orders")
(target.alias("t")
  .merge(staging.alias("s"), "t.OrderId = s.OrderId")
  .whenMatchedUpdateAll(condition="s.LastModifiedDate > t.LastModifiedDate")
  .whenNotMatchedInsertAll()
  .execute())

Il MERGE è ciò che rende il carico idempotente: rieseguire la stessa finestra non duplica nulla, perché le righe già presenti vengono aggiornate anziché inserite. È la differenza sostanziale rispetto all’append cieco, che a ogni rerun (retry automatico, riesecuzione manuale dopo un fallimento parziale) moltiplica le righe e falsa ogni conteggio a valle.

Full reload, append, MERGE: quando usare cosa

  • Full reload (overwrite): tabelle piccole (dimensioni anagrafiche sotto il milione di righe), sorgenti senza colonna di modifica affidabile, o quando la storicizzazione non serve. Semplice e sempre corretto, ma non scala.
  • Append: legittimo solo su bronze e solo su dati intrinsecamente immutabili (log, eventi, telemetria), dove ogni riga è un fatto nuovo e non un aggiornamento.
  • MERGE: la scelta di default per silver e gold quando la sorgente aggiorna le righe esistenti.

CDC e cancellazioni logiche

Quando la sorgente espone Change Data Capture (SQL Server, Azure SQL con CDC/change tracking, oppure un mirroring), non serve un watermark: il feed fornisce già le operazioni I/U/D. In questo caso il MERGE estende la clausola con whenMatchedDelete(condition="s.__op = 'D'").

Se invece la sorgente non propaga le cancellazioni fisiche, hai due strade: un soft delete (colonna IsDeleted valorizzata dal MERGE, con le viste gold che filtrano) oppure una riconciliazione periodica full che confronta le chiavi presenti in sorgente con quelle in silver e marca le mancanti. Il soft delete è quasi sempre preferibile perché preserva l’auditabilità e non rompe le foreign key verso le fatti storiche.

Ricorda infine che Delta mantiene lo storico delle versioni: DESCRIBE HISTORY e il time travel (VERSION AS OF) permettono di verificare cosa ha fatto ogni MERGE e di ripristinare uno stato precedente dopo un carico errato — a patto che VACUUM non abbia già rimosso i file (retention di default 7 giorni).

Trappole tipiche d’esame

  • Scenario: dopo un retry della pipeline i totali di vendita raddoppiano → la causa è un APPEND su una tabella che riceve aggiornamenti. La risposta giusta è sostituirlo con un MERGE sulla business key, non aggiungere una deduplica a valle.
  • Scenario: si chiede dove applicare deduplica e cast dei tipi → silver, mai bronze. Bronze deve restare una copia fedele e append-only per consentire il rebuild dei layer superiori.
  • Scenario: la sorgente è grande, ha una colonna LastModifiedDate affidabile e serve minimizzare il tempo di caricamento → watermark + Lookup + Copy parametrizzata + MERGE, non full reload. Il full reload è la risposta corretta solo per tabelle piccole o senza colonna di modifica.
  • Scenario: alcune righe spariscono dal sistema sorgente ma restano nei report → serve gestire le cancellazioni, con whenMatchedDelete su feed CDC o soft delete tramite flag IsDeleted. Il watermark da solo non intercetta mai le delete fisiche.
  • Scenario: il report Power BI in Direct Lake è lento dopo mesi di carichi incrementali → i MERGE frequenti hanno generato molti file piccoli. La risposta è OPTIMIZE (con V-Order) sulle tabelle gold, non passare a Import mode.