Immagina la riunione. La finanza sta riconciliando un numero trimestrale con una fonte esterna e i due valori non coincidono. L’indagine fa risalire la differenza a una trasformazione nella tua daily revenue pipeline che è stata sbagliata per sei mesi, da quando un refactor che nessuno aveva segnalato è entrato in produzione. Ogni tabella downstream costruita sopra quella trasformazione è stata sbagliata per sei mesi. Le dashboard, i report dell’esecutivo, i dati di training del ML, i numeri che il consiglio di amministrazione ha visto il trimestre scorso.
E adesso la domanda atterra su di te. Come facciamo a sistemarlo.
Questo è il problema del backfill, e ogni sistema batch sopra una dimensione triviale prima o poi lo incontra. La lezione precedente parlava di idempotenza, che è ciò che rende possibile il backfill in primo luogo. Questa lezione è sui pattern che trasformano un backfill da una catastrofe lunga settimane in un martedì pomeriggio.
Perché sei mesi di “rilancia il job e via” è difficile
Il piano ingenuo è “rilancia il daily job per ciascuno degli ultimi 180 giorni, in ordine, e le tabelle saranno corrette”. Il piano ingenuo non sopravvive al contatto con la realtà, per quattro ragioni distinte.
Costo del cluster e wall-clock time. Un daily job che ci mette quattro ore richiede 720 ore di compute per essere rieseguito serialmente su 180 giorni. Quello è un mese di wall-clock time su un cluster che deve anche far girare il job di stanotte. Se parallelizzi, ti serve 180 volte la capacità del cluster per la durata. Entrambe le dimensioni fanno male. Il backfill è un workload sopra il workload di regime, e il cluster deve assorbirli entrambi.
Preservazione dei source data. La trasformazione legge degli input. Quegli input sono ancora lì? Se la sorgente è un database transazionale che fa il purge delle vecchie righe dopo 90 giorni, gli input di sei mesi fa sono spariti, e rilanciare contro i source data attuali non riproduce l’output storico. Se la sorgente è un log archiviato in object storage, sei a posto. Se la sorgente è “lo snapshot di ieri di una tabella che viene sovrascritta ogni giorno”, non lo sei.
Idempotenza. Il backfill riscrive partizioni che hanno già dei dati. Un job in append duplica al rilancio; un job partition-overwrite è sicuro. La lezione 38 ne ha trattato in dettaglio. La rilevanza qui è che un job non idempotente non può essere fatto in backfill senza intervento manuale, e un job “quasi idempotente” è una trappola perché i failure mode emergono solo nelle condizioni inusuali di un backfill.
Impatto downstream. La tabella corretta viene letta da dashboard, ML pipeline, export, regulator. Alcuni consumer usano cache; alcuni sono batch job che vedranno comparire nuovi dati nelle partizioni storiche e si confonderanno; alcuni useranno silenziosamente i vecchi (e ancora sbagliati) numeri perché l’ultimo refresh è successo prima del backfill. Il blast radius di “sto riscrivendo sei mesi di una tabella” è più grande della tabella stessa.
Un backfill, in altre parole, non è una riesecuzione di un job. È un piccolo progetto di migration, con un piano, una rollback story, e un thread di comunicazione.
I pattern che rendono i backfill una routine
Sei pattern, ciascuno utile indipendentemente, che si compongono quando li hai tutti.
Tieni i raw data per sempre, o quasi. L’object storage nel 2026 costa all’incirca 0.02 USD per GB al mese per il cold storage. Un petabyte di raw event per un anno è dell’ordine di un quarto di milione di dollari l’anno. Sono soldi veri, ed è molto meno del costo ingegneristico di non poter fare un backfill quando ne hai bisogno. La Lambda architecture, articolata da Nathan Marz attorno al 2011, ha reso esplicito questo principio: un batch layer che ricalcola i risultati derivati a partire da un raw log immutabile su richiesta. Tieni i raw data, nella loro forma raw, in storage immutabile economico, con una retention policy abbastanza lunga da poterci fare backfill sopra. Qualunque cosa tu abbia derivato è stata calcolata da qualcosa; tieni quel qualcosa.
Job idempotenti. Ogni batch job dovrebbe essere ri-eseguibile in sicurezza. La lezione 38 ha dato i pattern: semantica partition-overwrite, output deterministici, niente mutazione in place. Un job che è idempotente per il run normale di stanotte è idempotente per un backfill di un qualsiasi giorno storico. Un job che non è idempotente forza uno step di pulizia manuale ogni volta che fai backfill, il che significa in pratica che non fai backfill, scrivi scuse.
Backfill parallelizzabili. I 180 giorni del backfill sono indipendenti. La trasformazione per il 2025-09-15 non ha bisogno che sia girato il 2025-09-14. (Se ne ha bisogno, il tuo job ha hidden state attraverso i giorni; sistema prima quello.) Quando i giorni sono indipendenti, il backfill li lancia in parallelo, limitato solo dalla capacità del cluster e dai rate limit lato sorgente. Un backfill di 180 giorni su un cluster dimensionato per 30 job paralleli finisce in 6 onde batch, non 180.
Output versionati. Non scrivere i dati corretti sopra i dati sbagliati in place. Scrivi su una nuova versione della tabella. Alcuni team usano un nome con suffisso di data (fct_revenue_v2_20260123), altri uno snapshot di Iceberg o Delta, altri uno swap a livello di partizione. La forma è la stessa: la nuova tabella sta accanto alla vecchia, tu validi, e fai il cutover atomico. La vecchia versione resta in giro per una retention window nel caso tu debba fare rollback.
Reset dei watermark. La maggior parte delle batch pipeline gira su un modello high-water-mark: il job ricorda l’ultima partizione che ha processato. Per fare backfill, resetti il watermark per la finestra interessata. Sembra banale e non lo è, perché il watermark spesso è sparso su più sistemi (l’orchestrator, lo state del job stesso, i watermark dei consumer downstream). Un piano di backfill ha bisogno di una lista di watermark da resettare e di una lista da far ripartire alla fine.
Pause o design per la tolleranza downstream. O metti in pausa i job downstream che leggono la tabella che stai riscrivendo, oppure li progetti per tollerare aggiornamenti alle partizioni storiche. Mettere in pausa è operativamente semplice e politicamente costoso. La tolleranza è architetturalmente più difficile e più duratura: il job downstream è esso stesso idempotente, raccoglie i nuovi dati al passaggio successivo, e riporta la propria completezza in modo che i consumer sappiano quando fidarsi della risposta. Se ti aspetti di fare backfill regolarmente, costruisci per la tolleranza.
Replay, il cugino streaming
Il replay è la versione streaming-system del backfilling. Un po’ di vocabolario che vale la pena introdurre qui, anche se il modulo 6 è dove vive davvero.
In un sistema streaming, l’input è un event log (Kafka, Kinesis, Pulsar) con una retention window, e i consumer leggono da offset in quel log. Fare replay significa resettare l’offset di un consumer a una posizione storica e ri-processare da lì. Se la retention è di sette giorni e il tuo bug è stato introdotto sei giorni fa, il replay è economico: riavvolgi il consumer, lascialo ri-processare, l’output corretto compare in tempo reale. Se il bug è stato introdotto un anno fa, il replay dal solo log è impossibile; ti serve il batch layer per ricalcolare dall’archivio raw durevole.
Kafka rende il replay strutturalmente economico perché il log è lo storage durevole, i consumer sono solo bookmark, e riavvolgere un bookmark è una sola chiamata API. La Lambda architecture originale trattava batch e stream come due path verso la stessa risposta; la variante moderna (a volte chiamata Kappa) collassa i due dando allo streaming layer abbastanza retention perché il replay copra la maggior parte dei casi per cui serviva il batch. Il modulo 6 affronta i trade-off.
Il punto rilevante qui: il replay è il backfill in un vocabolario diverso. I pattern si trasferiscono. Tieni i raw event. Rendi i consumer idempotenti. Versiona gli output. Coordina il downstream.
Camminare attraverso un piano vero
Esempio concreto. Il daily revenue report ha un bug nella join sul tasso di cambio. Per 90 giorni, le transazioni in valute diverse dall’USD sono state convertite usando il tasso sbagliato (il tasso del giorno precedente, non il tasso del giorno della transazione). Le tabelle downstream sono fct_daily_revenue (90 partizioni daily impattate) e tre report costruiti sopra (executive dashboard, export di riconciliazione finance, monthly board pack).
Il piano, nell’ordine in cui viene eseguito:
- Congela i report impattati. Notifica ai consumer (finance, exec ops, board prep) che la dashboard leggerà “as of yesterday” per le prossime 48 ore mentre una correzione è in corso. Aggiungi un banner sulla dashboard.
- Verifica che i source data siano intatti. La tabella dei tassi di cambio risale a due anni indietro, quindi i tassi storici sono disponibili. La transaction table è partizionata per giorno e tenuta per tre anni, quindi gli input per i 90 giorni interessati sono ancora lì. Bene.
- Resetta il watermark per
fct_daily_revenue. L’orchestrator memorizza l’high-water-mark in una metadata table; lo riportiamo indietro di 90 giorni per il job interessato. - Backfill in una tabella versionata. Il job scrive su
fct_daily_revenue__backfill_20260123invece che sufct_daily_revenue. Il cluster è dimensionato per girare 15 giorni in parallelo, quindi il backfill di 90 giorni richiede 6 onde batch, all’incirca 6 ore di wall clock. - Valida. Un job di riconciliazione confronta i totali fra la vecchia e la nuova tabella per ciascun giorno interessato. La maggior parte delle differenze dovrebbero essere piccole (la fix sul tasso di cambio), e segno e magnitudine dovrebbero corrispondere a quello che ci aspettiamo dall’analisi del bug. Tiriamo fuori un campione random di transazioni e verifichiamo i tassi corretti a mano contro la fonte FX.
- Cut over. Una singola operazione atomica fa lo swap delle partizioni interessate di
fct_daily_revenueper puntare ai nuovi dati. In Iceberg o Delta è uno swap di metadata, non un movimento di dati. I vecchi dati restano su disco per la rollback window. - Triggera le rebuild downstream. I report leggono dalla tabella corretta; sono essi stessi idempotenti e si ricostruiscono per la finestra interessata.
- Comunica. Una nota a finance, exec ops, e al team di board-prep che riassume il problema, la fix, il periodo interessato, e la magnitudine della correzione. Senza questo step, il lavoro ingegneristico era tecnicamente corretto e organizzativamente invisibile, che è la stessa cosa di non averlo fatto.
- Decommissiona. Dopo una retention window (diciamo, due settimane) la vecchia tabella
__backfill_viene droppata.
Diagramma da creare: un flowchart orizzontale che mostra i nove step sopra come una swim lane. Lane in alto: azioni di data engineering (freeze, reset watermark, backfill, cut over, decommission). Lane in basso: azioni di comunicazione (notifica ai consumer, validazione con finance, invio summary). Frecce fra le lane mostrano i punti di coordinamento.
flowchart LR
A[Freeze reports] --> B[Verify source data]
B --> C[Reset watermark]
C --> D[Backfill to versioned table]
D --> E[Validate against source]
E --> F[Atomic cutover]
F --> G[Rebuild downstream]
G --> H[Communicate]
H --> I[Decommission old version]
Il piano non è esotico. È lo stesso playbook su cui converge ogni team che gestisce infrastruttura batch seria, perché le alternative (riscritture in place, fix manuali in SQL, “staremo solo più attenti la prossima volta”) sono state tutte provate e tutte hanno fallito.
Il cost-benefit del “tieni il raw per sempre”
La singola scelta architetturale più consequenziale per il backfilling è se tieni gli input raw. I team che perdono questa battaglia lo fanno per ragioni prevedibili: lo storage sembra costoso in astratto, la compliance spinge verso retention più corte, e nessuno vuole difendere un anno di raw event data alla bill review.
Il contro-argomento è il bug che non hai ancora scoperto. Ogni team che ho visto operare infrastruttura batch seria ha avuto almeno un momento in cui la differenza fra “possiamo sistemarlo” e “dobbiamo scrivere delle scuse al regulator” era se i raw data erano ancora in giro. L’object storage ai prezzi del 2026 è l’assicurazione più economica che la data platform vende.
Le carve-out legali e di privacy sono reali e hanno bisogno di un piano. I PII hanno obblighi di retention più corti di quello che vorresti per il backfilling; la risposta è tenere il raw event log con i campi PII tokenizzati o tombstoned dopo la finestra di retention legale, mantenendo per più tempo le parti non-PII. Più lavoro che “tieni tutto per sempre”, ma preserva la capacità di backfill per la maggior parte dei dati onorando le regole per le parti sensibili.
Il modulo 5 si chiude qui, quasi
La prossima lezione è il case study che mette tutto il modulo 5 in movimento alla scala del petabyte. Netflix gestisce una delle più grandi e più pubbliche batch data platform dell’industria, e leggere la loro architettura contro le astrazioni di questo modulo è il modo più pulito per vedere come i pezzi combaciano quando la posta in gioco è reale e i dati sono enormi. Dopo quello, il modulo 6 inizia con lo streaming.
Citazioni e ulteriori letture
- Nathan Marz e James Warren, “Big Data: Principles and best practices of scalable real-time data systems”, Manning, 2015. Il libro che ha articolato la Lambda architecture e il principio “tieni il raw log per sempre”.
- Jay Kreps, “Questioning the Lambda Architecture”, O’Reilly Radar, 2014,
https://www.oreilly.com/radar/questioning-the-lambda-architecture/(consultato 2026-05-01). La replica della Kappa-architecture che sostiene che uno stream layer a long retention sussume il batch layer. - Apache Iceberg documentation, “Maintenance and snapshot expiration”,
https://iceberg.apache.org/docs/latest/maintenance/(consultato 2026-05-01). I meccanismi delle tabelle versionate e degli atomic snapshot swap che rendono possibili i backfill sicuri. - “Designing Data-Intensive Applications” (Martin Kleppmann, O’Reilly, 2017), capitolo 10. Il riferimento standard per batch processing, idempotenza, e le realtà operative che questa lezione riassume.