Architettura di dati e sistemi, dalle fondamenta Lezione 34 / 80

Fondamenti del batch processing: le lezioni di Hadoop

Cosa MapReduce ha azzeccato, cosa ha sbagliato, e la forma del batch processing che è sopravvissuta.

La lezione 33 si chiudeva con l’osservazione che i moderni cloud warehouse rendono il batch processing invisibile: scrivi SQL, e da qualche parte sotto il cofano un motore distribuito legge i file dall’object storage e produce la risposta. Il motore invisibile non è magia. È il discendente di uno specifico paper che Google ha pubblicato nel 2004, che una community open-source ha poi passato quindici anni a rincorrere, poi a superare, e poi a mandare silenziosamente in pensione. Questa lezione ripercorre l’ascesa e la caduta di Hadoop, perché le sue parti sopravvissute sono ancora le idee portanti dietro ogni sistema batch che userai.

Il motivo per spendere una lezione su una tecnologia che nessuno è entusiasta di deployare nel 2026 è che quella tecnologia aveva ragione in modi che il marketing ha offuscato, e aveva torto in modi altrettanto istruttivi. I moderni motori batch non hanno sostituito le idee di Hadoop; hanno sostituito la sua implementazione.

I paper del 2003 e del 2004

Nell’ottobre 2003, Sanjay Ghemawat, Howard Gobioff e Shun-Tak Leung pubblicarono “The Google File System” a SOSP. Il paper descriveva un filesystem distribuito che girava su macchine commodity economiche, assumeva che dischi e rete sarebbero falliti costantemente, ed era ottimizzato per il pattern di accesso che i workload batch di Google avevano davvero: grandi letture sequenziali e grandi append, con pochissime piccole scritture casuali. GFS non era un filesystem general-purpose. Era un filesystem modellato su una specifica forma di lavoro, e la forma contava più dell’eleganza.

Nel dicembre 2004, Jeffrey Dean e Sanjay Ghemawat pubblicarono “MapReduce: Simplified Data Processing on Large Clusters” a OSDI. Il paper descriveva un modello di programmazione e un runtime: scrivi una funzione map che processa un record alla volta e una funzione reduce che aggrega i record per chiave, e il runtime distribuirà il calcolo su migliaia di macchine, gestirà i fallimenti, riavvierà i task morti e produrrà un risultato. La coppia di paper (storage layer più execution layer) definiva un sistema completo per girare job batch alla scala alla quale Google stava già girando internamente.

Doug Cutting e Mike Cafarella, lavorando sul motore di ricerca Nutch, iniziarono nel 2005 una clean-room reimplementation di GFS e MapReduce in Java. Nel 2006 il progetto si trasferì a Yahoo, fu rinominato Hadoop, e diventò lo standard open-source per il batch processing distribuito. Yahoo, Facebook, LinkedIn, Twitter e la maggior parte delle aziende internet cresciute tra il 2008 e il 2012 hanno girato su cluster Hadoop a un certo punto. Nel 2012, “big data” e “Hadoop” erano quasi sinonimi sulla stampa di settore, e c’erano tre distribuzioni commerciali (Cloudera, Hortonworks, MapR) che si contendevano il mercato enterprise.

Questo è l’arco della storia. Ciò che conta è il contenuto tecnico sotto.

Il modello MapReduce

Un job MapReduce ha tre fasi. Map esegue una funzione fornita dall’utente su ogni record di input in maniera indipendente, in parallelo, su molte macchine. La funzione emette zero o più coppie chiave-valore. Shuffle è il lavoro del runtime: ridistribuire tutte le coppie emesse attraverso il cluster, in modo che tutte le coppie con la stessa chiave finiscano sulla stessa macchina. Reduce esegue un’altra funzione fornita dall’utente una volta per chiave, con tutti i valori per quella chiave come input, ed emette l’output finale.

L’esempio classico è il word count. La funzione map legge ogni riga di un documento ed emette (word, 1) per ogni parola che trova. Lo shuffle raggruppa tutte le coppie (word, 1) per parola. La funzione reduce riceve (word, [1, 1, 1, ...]) ed emette (word, total). L’intero calcolo sono due brevi funzioni utente più un runtime che gestisce la distribuzione.

Il modello è abbastanza semplice da entrare in una slide e abbastanza potente da esprimere la maggior parte del lavoro di aggregazione, join e filtering. Le query SQL si possono compilare in job MapReduce (Hive lo faceva dal 2008 in poi). Gli algoritmi sui grafi si possono esprimere come iterazioni di passate MapReduce. I job ETL si mappano naturalmente su di esso.

flowchart LR
    I[(Input split<br/>on GFS/HDFS)] --> M1[Map task 1]
    I --> M2[Map task 2]
    I --> M3[Map task 3]
    M1 --> SH[Shuffle<br/>partition by key]
    M2 --> SH
    M3 --> SH
    SH --> R1[Reduce task 1]
    SH --> R2[Reduce task 2]
    R1 --> O[(Output<br/>on GFS/HDFS)]
    R2 --> O

Cosa MapReduce ha azzeccato

Quattro idee sono sopravvissute all’era MapReduce e sono ora assunzioni portanti di ogni moderno motore batch.

Portare il codice ai dati, non i dati al codice. I task map girano sulle stesse macchine che ospitano i dati di input, o il più vicino possibile rispetto alla topologia di rete. Il framework legge il layout dei dati, schedula ogni task su un nodo che ha localmente il blocco corrispondente, e ricade su una lettura remota solo se nessun nodo locale è disponibile. Alla scala dei petabyte, spostare il codice (qualche kilobyte di JAR compilato) verso i dati (terabyte per nodo) è drammaticamente più economico che spostare i dati verso il codice. Questo principio, chiamato data locality, è il modo in cui ogni sistema batch distribuito schedula il lavoro nel 2026.

Fault tolerance via replicazione e ri-esecuzione. Un cluster da mille macchine ha almeno una macchina che fallisce in qualunque momento. MapReduce assumeva questo e costruiva il failure handling dentro il runtime: i dati di input sono replicati su più nodi (tre repliche di default in HDFS), e se un task muore il master lo schedula per girare di nuovo su un nodo diverso. Gli autori dei job non scrivono logica di retry; il framework fa retry per loro conto. Il modello funziona solo perché le funzioni map e reduce sono pure: rieseguirle produce lo stesso output. Questo vincolo è il prezzo della fault tolerance economica, ed è un prezzo che ogni framework batch paga ancora.

Schema-on-read. I dati atterrano su HDFS in qualunque forma siano arrivati: righe di log, blob JSON, record binari custom. Lo schema non è imposto dallo storage layer; è interpretato dal job che legge i dati. È l’opposto del modello warehouse con cui la lezione 33 contrastava ETL: in un mondo Hadoop il dato grezzo è durevole e autoritativo, e la struttura è applicata dal codice. Il pattern persiste oggi in ogni architettura “lake” e “lakehouse”.

Hardware commodity alla scala. Prima di MapReduce, scalare carichi analitici significava comprare una macchina più grossa: un appliance Teradata, una Oracle box di fascia alta, una partizione di mainframe IBM. MapReduce ha dimostrato che mille macchine economiche, ciascuna individualmente inaffidabile, potevano girare più veloci ed economiche di una costosa, se scrivevi il software assumendo il fallimento. Ogni sistema dati cloud-scale è oggi costruito sulla stessa assunzione.

Cosa MapReduce ha sbagliato

Tre debolezze, in ordine di severità crescente.

Stato intermedio pesante su disco. Tra la fase map e la fase reduce, MapReduce scriveva l’intero output dello shuffle su disco. Ogni task map scriveva il suo output su disco locale, il servizio di shuffle lo rileggeva via rete, e il task reduce scriveva i suoi risultati su HDFS. Per un job con un singolo map e reduce, era accettabile. Per un algoritmo iterativo (training di machine learning, algoritmi sui grafi come PageRank, qualunque cosa che cicli sugli stessi dati più volte), ogni iterazione era un nuovo round-trip via disco. Spark avrebbe poi mostrato che gli stessi job iterativi potevano girare cento volte più veloci tenendo lo stato intermedio in memoria.

API verbose, difficili da debuggare. Scrivere un job Hadoop in Java significava scrivere due classi (un Mapper e un Reducer), collegarle in una classe driver, configurare la serializzazione, e sottomettere il JAR. L’equivalente SQL di select count(*) from t group by x era sessanta righe di boilerplate. Hive e Pig hanno avvolto questa cosa in linguaggi di più alto livello, ma l’API sottostante era abbastanza dolorosa da rendere “MapReduce expertise” un job title per diversi anni. Debuggare un job che falliva sulla macchina 487 di 1000 con uno stack trace che coinvolgeva la serializzazione Java era una sua speciale forma di sofferenza.

Complessità operativa. L’ecosistema Hadoop è cresciuto in modo esplosivo tra il 2008 e il 2014, e il risultato era uno stack che richiedeva un piccolo team per essere operato. Un cluster tipico girava HDFS per lo storage, MapReduce per il batch, YARN per lo scheduling delle risorse, Hive per SQL, HBase per letture a bassa latenza, ZooKeeper per la coordinazione, Oozie per lo scheduling dei job, Sqoop per l’ingestione relazionale, Flume per l’ingestione dei log, Kafka per lo streaming (alla fine), Ranger per l’access control, e Kerberos per l’autenticazione. Ogni componente aveva la sua configurazione, le sue failure mode, le sue metriche, i suoi demoni da tenere vivi. Le distribuzioni Hadoop commerciali facevano soldi impiegando le persone che riuscivano davvero a tenere tutto questo in piedi.

La combinazione delle tre debolezze è il motivo per cui “Hadoop” come brand è effettivamente in pensione. Cloudera e Hortonworks si sono fuse nel 2019 per consolidare un mercato in contrazione, poi sono andate private nel 2021. MapR ha venduto la sua IP a HPE nel 2019. I cloud provider hanno offerto object storage gestito che era più economico e più facile che far girare HDFS, Spark gestito che era più veloce di MapReduce, e warehouse gestiti che nascondevano del tutto il cluster.

Cosa è sopravvissuto

L’implementazione è sbiadita. Le idee no.

Scheduling distribuito e consapevolezza della data-locality. Ogni motore batch moderno, da Spark a Trino al runtime Dremel interno di BigQuery, schedula i task con consapevolezza di dove i dati vivono fisicamente. Lo scheduler YARN di Hadoop è sparito; il principio che imponeva è nelle estensioni di scheduling di Kubernetes, nel task planner di Spark, in ogni motore di query serverless.

Batch fault-tolerant come primitiva. L’assunzione che qualunque task possa morire e che il framework lo riavvii è incorporata in Spark, in Beam, nel batch mode di Flink, in ogni warehouse batch. Il costo (purezza funzionale) e il beneficio (affidabilità economica alla scala) sono passati in blocco.

Formati di storage colonnari. Apache Parquet (nato a Twitter e Cloudera, 2013) e Apache ORC (Hortonworks, 2013) sono emersi dalla community Hadoop come formati colonnari su disco progettati per i pattern di accesso analitico che MapReduce aveva reso popolari. Sono ora il formato di storage di default per i dati analitici fuori dal warehouse, inclusi tutti i moderni table format lakehouse che ci si appoggiano sopra (Delta, Iceberg, Hudi, tutti nella lezione 37). HDFS è sbiadito; i formati di file che ha incubato sono dappertutto.

Object storage come data lake. HDFS era un filesystem distribuito su dischi cluster-attached. Una volta che Amazon S3 (2006), Google Cloud Storage (2010), e Azure Blob Storage (2010) hanno offerto storage durevole, economico, scala-internet, con un’API abbastanza simile, il caso per gestire un proprio cluster HDFS è collassato. Il “data lake” del 2026 è un bucket su S3 o GCS o Azure Blob, con file Parquet in gerarchie di cartelle, interrogato da Spark, Trino, DuckDB o dal motore del warehouse. La forma è esattamente quella che HDFS più Hive aveva nel 2014, e il costo operativo è una connection string al posto di un team di sei persone.

MapReduce come primitiva dentro altri motori. Il modello di esecuzione RDD di Spark è una generalizzazione di MapReduce: i task sono schedulati su partizioni, gli shuffle ridistribuiscono per chiave, la fault tolerance avviene per ri-esecuzione. L’utente non scrive più direttamente map e reduce (scrive operazioni DataFrame o SQL), ma il runtime sotto sta facendo quello che faceva MapReduce, con migliore ottimizzazione e stato intermedio in memoria.

La forma del batch moderno

Mettendo insieme le idee sopravvissute, l’architettura batch moderna ha la stessa forma che aveva MapReduce, con parti sostituite.

Lo storage layer è object storage (S3, GCS, Azure Blob), che ospita file immutabili in un formato colonnare (Parquet, a volte ORC), spesso organizzati da un open table format (Delta, Iceberg, Hudi) che aggiunge transazioni e versioning sopra.

Il compute layer è disaccoppiato dallo storage e scala in modo indipendente. Spark, Trino, DuckDB e i motori proprietari dentro i cloud warehouse leggono tutti gli stessi file. Puoi far girare più motori di compute contro lo stesso lake nello stesso momento, cosa che sarebbe stata un incubo di configurazione nell’era Hadoop.

Il modello di esecuzione è ancora scheduling di task distribuiti con data locality, fault tolerance via ri-esecuzione, e una fase di shuffle tra gli stage. L’API rivolta all’utente è SQL o DataFrame, non funzioni map e reduce grezze, ma il runtime sotto sta facendo lo stesso lavoro descritto dal paper del 2004.

Il modello operativo è drammaticamente più semplice. Non c’è cluster da tenere vivo tra i job nel caso warehouse; c’è un Kubernetes operator e un Helm chart nel caso Spark-on-Kubernetes. Il core-site.xml da mille righe è andato.

Dove tutto questo porta

Il motore batch general-purpose dominante dell’era moderna, quello che ha fatto da ponte tra il mondo Hadoop e quello cloud-native, è Apache Spark. Spark è quello che la maggior parte dei team va a prendere quando ha bisogno di batch processing che non entra in una query SQL del warehouse, ed è il motore sotto Databricks, la più grande piattaforma commerciale costruita sulle lezioni di MapReduce. La lezione 35 copre Spark, il resto dello stack batch moderno del 2026, e l’albero decisionale di quando ogni strumento si guadagna il suo posto.

Citazioni e letture aggiuntive

  • Sanjay Ghemawat, Howard Gobioff, Shun-Tak Leung, “The Google File System”, SOSP 2003, https://research.google/pubs/the-google-file-system/ (consultato 2026-05-01).
  • Jeffrey Dean, Sanjay Ghemawat, “MapReduce: Simplified Data Processing on Large Clusters”, OSDI 2004, https://research.google/pubs/mapreduce-simplified-data-processing-on-large-clusters/ (consultato 2026-05-01). Il paper originale. Breve e chiaro; vale la pena leggerlo ancora oggi.
  • Documentazione di Apache Hadoop, https://hadoop.apache.org/docs/stable/ (consultato 2026-05-01).
  • Documentazione di Apache Parquet, https://parquet.apache.org/docs/ (consultato 2026-05-01).
  • “Hadoop: The Definitive Guide” (Tom White, O’Reilly, 4a edizione, 2015). Il riferimento standard per l’ecosistema Hadoop al suo apice; utile per il contesto storico anche se non hai intenzione di far girare un cluster.
  • “Designing Data-Intensive Applications” (Martin Kleppmann, O’Reilly, 2017), capitolo 10. Il riassunto moderno più pulito del modello MapReduce e dei suoi limiti.
Cerca