
Delta Lake: ACID on data lakes
Delta Lake: ACID transactions, time travel, and schema enforcement on S3.
What you will learn
- Understand the analytical problem and the decision-making context
- Apply examples, metrics, and controls to real cases
import pandas as pd
Delta Lake: ACID on data lakes
Un data lake fatto di soli file Parquet fatica a gestire update, rollback, concorrenza e correzioni storiche. Delta Lake risponde proprio a questi limiti: aggiunge un transaction log, il versioning e garanzie ACID che trasformano file distribuiti in tabelle affidabili. Conviene leggere la lezione tenendo a mente quando serve davvero, cioè quando devi correggere dati, garantire letture coerenti e ricostruire versioni passate senza che ogni fix diventi un intervento manuale rischioso.
Il problema da risolvere
Il caso tipico: una tabella ordini riceve correzioni tardive e cancellazioni GDPR mentre più job leggono e scrivono nello stesso intervallo. Senza un transaction log ACID, un lettore può vedere uno stato a metà, una scrittura può sovrascrivere quella di un altro job e la storia delle modifiche va persa. Delta evita letture incoerenti, overwrite manuali e perdita di storia.
La domanda guida non è “cos’è Delta Lake?” ma “quale operazione diventa meno rischiosa se la tabella è transazionale?”. Update puntuali, rollback dopo un caricamento sbagliato, audit di cosa conteneva la tabella un mese fa: sono questi i momenti in cui il file lake tradizionale mostra i suoi limiti.
Come ragionare sulla scelta
Prima di adottare Delta conviene fissare l’unità su cui ragioni e il segnale che osservi, così la decisione resta discutibile invece di affidata alla moda del momento.
| Element | Operational Definition | Controllo minimo |
|---|---|---|
| Unit of analysis | Oggetto su cui misuri l’affidabilità | Tabella, partizione, versione |
| Signal | Cosa osservi per decidere | Frequenza di update, concorrenza, obblighi di correzione |
| Baseline | Rispetto a cosa giudichi | Comportamento attuale con soli file Parquet |
| Rischio residuo | Errore che resta dopo l’adozione | Lock-in sull’ecosistema sbagliato |
La scelta è solida quando un altro ingegnere può ricostruire il ragionamento, criticare le assunzioni e arrivare alla stessa conclusione partendo dagli stessi vincoli.
Delta vs Iceberg
La domanda pratica più frequente è quale dei due table format adottare. La differenza principale è l’ecosistema: Delta nasce dentro Databricks e Spark, Iceberg è pensato per più motori.
| Delta Lake | Iceberg | |
|---|---|---|
| Creator | Databricks | Netflix → Apache |
| Primary ecosystem | Spark, Databricks | Multi-engine (Trino, Presto, Flink, Spark) |
| Compaction | OPTIMIZE command | Manual or via external tools |
| Z-ordering | Yes (multi-dimensional) | No native |
| Change Data Feed | Yes | Yes (via snapshot diffs) |
Se lavori in Databricks, Delta Lake è la scelta naturale. Se sei multi-cloud o multi-engine, Iceberg è più portabile.
Essential Delta Lake operations
Tre operazioni coprono la maggior parte del lavoro quotidiano: l’upsert via merge, il time travel per leggere uno stato passato e l’OPTIMIZE con Z-ordering per tenere i file compatti e veloci da scansionare.
# Scrittura con merge (upsert)
from delta.tables import DeltaTable
delta_table = DeltaTable.forPath(spark, "s3://lake/orders")
delta_table.alias("t").merge(
new_orders.alias("s"), "t.order_id = s.order_id"
).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute()
# Time travel: leggi tabella com'era 7 giorni fa
df = spark.read.format("delta").option("versionAsOf", 7).load("s3://lake/orders")
# Compaction e Z-ordering
spark.sql("OPTIMIZE orders ZORDER BY (customer_id, order_date)")
Schema evolution in Delta Lake
Delta Lake gestisce sia l’evoluzione automatica dello schema, per esempio l’aggiunta di colonne, sia lo schema enforcement, che blocca le scritture con colonne non previste. Il comportamento si configura con mergeSchema: lo lasci attivo quando una sorgente legittima cambia spesso, lo tieni spento quando vuoi che uno schema imprevisto fallisca subito invece di corrompere silenziosamente la tabella.
Delta come parte di un sistema
In un progetto reale Delta non vive isolato. Fa parte di un sistema fatto di decisioni, dati disponibili, vincoli tecnici e incentivi organizzativi. Il rischio del principiante è trattarlo come una definizione da memorizzare; la pratica professionale è capire quale problema risolve e cosa succede quando le assunzioni saltano, per esempio quando scegli Delta in un contesto multi-engine in cui Iceberg sarebbe stato più adatto.
La prima domanda non è “quale comando uso?” ma quale decisione dipende dall’affidabilità della tabella. Un transaction log ha valore solo se riduce incertezza su una scelta concreta: poter correggere un dato senza fermare i lettori, poter dimostrare cosa conteneva la tabella a una certa data, poter fare rollback dopo un errore. Un modo concreto di impostare il lavoro:
- define the problem in business language;
- identificare l’unità di analisi corretta: tabella, partizione, versione;
- check if the data truly measures the phenomenon or just its shadow;
- build an interpretable metric;
- segment to avoid the average hiding opposite patterns;
- turn the result into a verifiable recommendation.
Real case: Netflix and the discipline of metrics
Netflix è un esempio utile perché ha costruito molte decisioni di prodotto intorno a segnali comportamentali osservabili: completamento degli episodi, tempo di ricerca prima della riproduzione, abbandono dopo pochi minuti, ritorno nella settimana successiva, efficacia delle raccomandazioni. Non a caso Iceberg nasce proprio in Netflix. Il punto non è copiare l’azienda, ma trattare il dato come infrastruttura decisionale e non come ornamento.
Quando si valuta una modifica, per esempio una nuova riga di raccomandazioni o un diverso algoritmo di ranking, non si misura solo il click immediato ma anche se l’utente segue il contenuto e se torna nei giorni successivi. Lo stesso vale per Delta: le garanzie ACID hanno senso solo se servono un outcome, cioè se rendono affidabili i dati su cui poggiano quelle decisioni.
SQL example: building a control view
Il pattern seguente è volutamente generico ma eseguibile nella maggior parte dei warehouse moderni. Crea una base analitica con metrica, segmento e finestra temporale, così da confrontare periodi e gruppi senza riscrivere la logica ogni volta.
WITH base_events AS (
SELECT
user_id,
account_id,
event_type,
event_time,
DATE_TRUNC('week', event_time) AS week,
source,
device_type
FROM events
WHERE event_time >= CURRENT_DATE - INTERVAL '180 days'
AND user_id IS NOT NULL
),
weekly_user_metrics AS (
SELECT
week,
user_id,
COALESCE(source, 'unknown') AS source,
COALESCE(device_type, 'unknown') AS device_type,
COUNT(*) AS total_events,
COUNT(DISTINCT DATE(event_time)) AS active_days,
COUNT(DISTINCT event_type) AS event_diversity,
MAX(CASE WHEN event_type IN ('purchase', 'subscribe', 'activation') THEN 1 ELSE 0 END) AS reached_key_outcome
FROM base_events
GROUP BY week, user_id, source, device_type
)
SELECT
week,
source,
device_type,
COUNT(DISTINCT user_id) AS users,
ROUND(AVG(active_days), 2) AS avg_active_days,
ROUND(AVG(event_diversity), 2) AS avg_event_diversity,
ROUND(AVG(reached_key_outcome) * 100, 2) AS key_outcome_rate
FROM weekly_user_metrics
GROUP BY week, source, device_type
ORDER BY week, source, device_type;
La query crea una superficie di osservazione (trend, segmenti, differenze tra canali) da cui formulare ipotesi più precise. Su una tabella Delta puoi rieseguirla con versionAsOf per confrontare due stati nel tempo.
Python example: checking stability and anomalies
Una metrica utile deve essere stabile abbastanza da orientare decisioni e sensibile abbastanza da segnalare cambiamenti reali. In Python si possono individuare variazioni anomale settimana su settimana.
# df contiene: week, segment, users, key_outcome_rate
# key_outcome_rate espresso in percentuale, es. 12.4
df = df.sort_values(['segment', 'week']).copy()
df['previous_rate'] = df.groupby('segment')['key_outcome_rate'].shift(1)
df['wow_change_pp'] = df['key_outcome_rate'] - df['previous_rate']
df['rolling_mean'] = df.groupby('segment')['key_outcome_rate'].transform(
lambda s: s.rolling(4, min_periods=2).mean()
)
df['rolling_std'] = df.groupby('segment')['key_outcome_rate'].transform(
lambda s: s.rolling(4, min_periods=2).std()
)
df['z_score'] = (df['key_outcome_rate'] - df['rolling_mean']) / df['rolling_std']
anomalies = df[df['z_score'].abs() >= 2].sort_values('z_score')
print(anomalies[['week', 'segment', 'key_outcome_rate', 'wow_change_pp', 'z_score']])
Il valore del controllo è pratico: evita di reagire a ogni oscillazione casuale e segnala quando una variazione merita un’indagine. In azienda alimenta alert, review settimanali e retrospettive.
Common mistakes to avoid
Il primo errore è adottare Delta per moda quando il contesto è multi-engine: in quel caso Iceberg evita un lock-in che pagheresti caro più avanti. Il secondo è disattivare lo schema enforcement per comodità, lasciando che scritture impreviste corrompano la tabella senza che nessuno se ne accorga. Il terzo è dimenticare la compaction: senza OPTIMIZE periodico la tabella si frammenta in tanti piccoli file e le query rallentano.
Per ridurre questi rischi ogni adozione dovrebbe avere almeno tre controlli: una scelta motivata del table format rispetto all’ecosistema, una policy esplicita su mergeSchema, e una manutenzione pianificata di compaction e Z-ordering.
Summary
Delta Lake diventa utile quando rende affidabile un’operazione rischiosa, non quando aggiunge terminologia. Il merge gestisce gli upsert, il time travel ricostruisce stati passati, OPTIMIZE e Z-ordering tengono le query veloci, lo schema enforcement protegge la tabella da scritture impreviste. La scelta tra Delta e Iceberg si decide sull’ecosistema, non sulla simpatia per un vendor, e una buona adozione finisce con una procedura chiara di correzione, rollback e manutenzione invece che con un semplice “ora abbiamo le transazioni ACID”.
Related Path
Lessons to read together
Questi collegamenti portano la lezione dentro il resto del corso: basi da riprendere, passaggi successivi e connessioni tematiche tra moduli.