PySpark, dalle fondamenta Lezione 3 / 60

Cos'è Spark e perché ha sostituito Hadoop MapReduce

Il paper di Matei Zaharia del 2010, l'esecuzione in memoria, il DAG, la lazy evaluation e la dichiarazione del '100x più veloce': cosa significa davvero e cosa no.

Abbiamo chiuso la lezione 2 con MapReduce che arrivava alla fine della sua vita utile attorno al 2014, soffocato dall’I/O di disco e dalle assunzioni architetturali del decennio precedente. Oggi guardiamo il sistema che l’ha sostituito: Apache Spark. Alla fine di questa lezione saprai da dove viene Spark, cosa fa che Hadoop MapReduce non poteva fare, cosa significa davvero la famosa dichiarazione “100x più veloce”, e (altrettanto importante) cosa Spark non è, perché metà della confusione su Spark nell’industria viene dal fatto che la gente lo crede un database, o un sistema di storage, o uno scheduler, mentre non è nessuna di queste cose.

Da dove viene

Spark è nato nel 2009 all’AMPLab dell’UC Berkeley, un gruppo di ricerca focalizzato su algorithms, machines, and people (da cui le iniziali A, M, P). L’autore principale era uno studente di dottorato di nome Matei Zaharia, che lavorava sotto i professori Scott Shenker e Ion Stoica. Zaharia aveva lavorato a Facebook e aveva visto il problema iterativo del machine learning di MapReduce da vicino: ricercatori che facevano girare lo stesso algoritmo sullo stesso dataset, pagando il costo pieno di I/O di disco a ogni iterazione. Si mise a sistemare esattamente quello.

La prima release pubblica di Spark fu nel 2010. Il paper fondante, “Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing”, fu pubblicato a NSDI nel 2012. Zaharia e i suoi collaboratori fondarono Databricks nel 2013, che è ora il vendor commerciale dominante di Spark ed è anche, a fine 2026, l’azienda che di fatto guida la maggior parte della roadmap di Spark. Il progetto fu donato all’Apache Software Foundation a metà 2013 e diventò un top-level Apache project a febbraio 2014. Spark 1.0 è uscito a maggio 2014. Spark 2.0 nel 2016 ha introdotto la moderna DataFrame API. Spark 3.0 nel 2020 ha portato l’adaptive query execution, che è una di quelle feature che in silenzio rende le query di tutti più veloci senza che debbano fare niente. Spark 4.0 è atterrato nel 2025. Il progetto è stato su una cadenza di major version di circa due-tre anni ed è stato notevolmente stabile nelle sue astrazioni di base.

Vedrai tutto questo accreditato a “Databricks” nel materiale di marketing e ad “Apache” nel materiale tecnico. Sono entrambi corretti. Spark è un progetto Apache; Databricks è l’entità commerciale costruita attorno; le stesse persone stanno al centro di entrambi.

Le tre innovazioni

La proposta di Spark nel paper originale è, distillata: prendi MapReduce, ma fai in modo che non sia lento. Il modo in cui lo ha fatto si riduce a tre scelte architetturali, ognuna delle quali ha affrontato uno dei punti di dolore specifici di MapReduce visti nella lezione 2.

Innovazione uno: un’astrazione di dataset in memoria. Questo è l’RDD, il Resilient Distributed Dataset, la cosa da cui prende il nome il paper di Zaharia. Un RDD è una collezione logica di record sparsi sul cluster, proprio come un dataset intermedio di MapReduce, tranne che il framework può tenerlo in RAM tra le operazioni invece di scriverlo su HDFS ogni singola volta. La “Resilient” nel nome si riferisce alla fault tolerance: ogni RDD ricorda come è stato derivato dal suo parent (il suo lineage), così se una partition viene persa perché un worker è morto, Spark può ricomputare solo quella partition dal lineage invece di far ripartire l’intero job. Il risultato è che ottieni la fault tolerance di MapReduce senza l’I/O di disco. Per i workload iterativi, dove riusi lo stesso dataset attraverso molti pass, questa è la differenza tra un job che ci mette 30 minuti e un job che ci mette 30 secondi.

Non scriviamo più molto codice RDD: usiamo la DataFrame API di più alto livello, che incontreremo nella lezione 5, ma ogni operazione su DataFrame compila ancora a RDD sotto. L’astrazione è fondazionale e sopravviverà all’API che ci sta sopra.

Innovazione due: un DAG di trasformazioni, non un modello fisso a due stage. MapReduce forzava ogni computazione esattamente in un map più un reduce. Spark ti permette di concatenare quante trasformazioni vuoi in un singolo job, e il motore costruisce un DAG (un directed acyclic graph) che rappresenta l’intera computazione. Filter, map, join, group-by, projection, aggregate, sort: qualunque combinazione, in qualunque ordine, tutto in un singolo job logico. Il motore Spark analizza l’intero DAG, capisce quali operazioni possono essere fuse insieme (per esempio, due chiamate filter consecutive possono essere unite in una), dove gli shuffle sono davvero richiesti (i join e i group-by; non i map e i filter), e produce un piano fisico che esegue solo gli shuffle di cui ha assolutamente bisogno.

Questa è una cosa molto, molto più grossa di come suona. In MapReduce, una pipeline a sei step significava sei job distinti, sei round-trip pieni su disco e sei round di overhead di startup della JVM. In Spark, una pipeline a sei step è un job, con magari due shuffle nel mezzo, tutto il lavoro intermedio che avviene in memoria. Scrivi la stessa query logica e gira un ordine di grandezza più veloce, senza alcuno sforzo extra da parte tua oltre all’usare l’API di più alto livello.

Innovazione tre: la lazy evaluation. La maggior parte delle operazioni in Spark non fa davvero niente quando le chiami. df.filter(...), df.select(...), df.join(...): nessuna di queste esegue. Costruiscono il DAG, ma nessun lavoro avviene. Solo quando chiami un action (count(), collect(), write.parquet(...), show()) il motore parte, guarda l’intero DAG che hai costruito, lo ottimizza e lo esegue.

Questo è ciò che rende possibile l’optimizer Catalyst. Catalyst è il query optimizer di Spark; vede l’intera tua computazione prima che ne giri qualunque parte e la riorganizza per le performance. Può fare push down dei filtri verso la sorgente dati così leggi meno dati dal disco in primo luogo. Può riordinare i join in modo che i dataset più piccoli vengano joinati per primi. Può riconoscere che ti servono solo due colonne da un file Parquet a 200 colonne e leggere solo quelle due colonne. Niente di tutto questo è possibile in un sistema valutato in modo eager, dove ogni operazione gira immediatamente sul risultato della precedente. La lazy evaluation è il prezzo d’ingresso per un optimizer intelligente, e l’optimizer di Spark è una delle cose che lo tiene competitivo contro i nuovi arrivati.

Il trade-off è che il codice Spark è a volte confuso da debuggare se sei abituato a Pandas. Scrivi quelle che sembrano dieci righe di codice perfettamente ragionevoli, nessuna gira, chiami .show() alla fine, e allora le intere dieci righe vengono eseguite come un singolo batch ottimizzato e qualunque errore in qualunque di esse emerge alla riga del .show() con uno stack trace che non corrisponde proprio alla sorgente. Il modulo 3 di questo corso è in gran parte sul prendere confidenza con questo.

La dichiarazione “100x più veloce”, e cosa significa davvero

Se hai letto qualunque cosa su Spark, hai visto il titolo: 100x più veloce di Hadoop MapReduce. È sulla homepage di spark.apache.org. Era su ogni slide deck di Databricks per gran parte degli anni 2010. È, a seconda di cosa misuri, o interamente vero o follemente sopravvalutato. Entrambe le letture meritano attenzione perché entrambe escono in conversazioni reali.

Dove la dichiarazione è genuinamente vera. Gli algoritmi iterativi (machine learning, graph processing, qualunque cosa che riusi lo stesso dataset attraverso molti pass) girano davvero circa 100 volte più veloci su Spark che su MapReduce, e i benchmark originali di Berkeley erano onesti su questo. Il motivo è esattamente quello che ti aspetteresti: MapReduce legge e scrive il dataset su HDFS a ogni iterazione; Spark lo legge una volta, lo tiene in RAM e lo riusa. Se il tuo dataset entra nella memoria del cluster e il tuo algoritmo fa 20 pass, Spark fa circa 1 lettura da disco mentre MapReduce ne fa 20, più risparmia 20 round di startup della JVM. Ottieni uno speedup di un-due ordini di grandezza quasi meccanicamente.

Le pipeline multi-stage (sei job MapReduce concatenati con Oozie) girano genuinamente molto più veloci anch’esse su Spark, per lo stesso motivo. Spark fonde i sei stage in un job, li fa girare in memoria tra gli shuffle, e salta la maggior parte dell’overhead di disco e orchestrazione.

Dove la dichiarazione è follemente sopravvalutata. Le aggregazioni single-pass (leggi un file grande, fai un GROUP BY, scrivi il risultato) sono limitate dall’I/O grezzo sia su Spark sia su MapReduce. Leggere 1 TB da S3 ci mette circa lo stesso tempo che tu lo stia facendo in Spark o in Hadoop, perché il collo di bottiglia è la rete e lo storage, non il compute. In quello scenario potresti vedere Spark essere 1,5 - 3 volte più veloce, a volte 5, ma non vedrai 100x. Chiunque ti dica che la sua query SELECT COUNT(*) GROUP BY country è diventata 100 volte più veloce passando da Hadoop a Spark sta ricordando male, o sta confrontando hardware del 2010 con hardware del 2024, o sta confrontando un job Hive mal tunato con un job Spark ben tunato, oppure sta semplicemente ripetendo il marketing.

Il riassunto onesto: Spark è da modestamente più veloce a drammaticamente più veloce di Hadoop MapReduce, a seconda del workload, e a questo punto nel 2026 il confronto è perlopiù storico comunque, perché nessuno sta scegliendo tra Spark e MapReduce su un progetto nuovo.

Cosa Spark non è

Questa è la parte che nessuno ti dice, ed è la fonte di circa metà delle conversazioni confuse su Spark che ho avuto negli anni.

Spark non è un database. Non memorizza i tuoi dati. Non ha tabelle nel modo in cui Postgres ha tabelle. La “tabella Hive” che interroghi tramite Spark SQL è metadata che punta a file nell’object storage; Spark stesso non possiede niente di tutto ciò. Non esiste una cosa come un “database Spark” nel modo in cui esiste un “database Postgres”. Se cancelli il tuo bucket S3, le tue tabelle sono andate, e Spark non può aiutarti.

Spark non è un sistema di storage. Non sostituisce HDFS, S3, GCS, ADLS, o qualunque altro posto in cui tieni i dati. Tu porti lo storage; Spark legge da e scrive su di esso. Spark ha connettori per dozzine di formati (Parquet, ORC, JSON, CSV, Avro, Delta Lake, Iceberg, JDBC, Kafka, e molti altri), ma lo storage stesso è il problema di qualcun altro.

Spark non è un cluster manager. Gira su un cluster manager. Le tue opzioni sono YARN (quello di Hadoop, ancora comune), Kubernetes (sempre più il default nel 2026), Mesos (deprecato dal 2020 e rimosso in Spark 3.5, menzionato solo perché vecchie risposte di StackOverflow lo referenziano), o lo standalone manager built-in di Spark stesso (va bene per piccoli cluster e ambienti di sviluppo). Il cluster manager alloca macchine e processi; Spark li usa.

Spark è, nello specifico, un motore di calcolo distribuito. Quella è l’intera job description. Gli dai i dati (da qualche parte), gli dai un cluster (da qualche parte), e fa girare la tua computazione attraverso il cluster contro i dati. Storage e orchestrazione sono preoccupazioni separate, intenzionalmente. Per questo i deployment di Spark hanno l’aspetto dei Lego (scegli il tuo storage layer, scegli il tuo cluster manager, scegli il tuo catalog, scegli il tuo formato di tabella) e per questo migrare tra, diciamo, Hadoop on-prem e cloud-native S3 + Kubernetes è perlopiù un esercizio di configurazione, non un riscrittura.

Il panorama competitivo, in un paragrafo

Nel 2026, Spark è il motore di calcolo distribuito open source dominante, punto. Ci sono concorrenti e ognuno è interessante nella propria nicchia. Apache Flink è genuinamente migliore di Spark sui workload di vero streaming a bassa latenza, e se stai costruendo un sistema di rilevamento frodi in tempo reale che richiede latenza sub-secondo, dovresti guardarlo. Dask è un’alternativa pure-Python che scala il codice in stile Pandas su cluster ed è più piacevole per lavoro di data science in piccoli team che non richiede supporto cross-linguaggio. Ray è il framework di calcolo distribuito Python-native più associato ai workload moderni di ML e di reinforcement learning. DuckDB è, a seconda di come la guardi, o un database analitico single-node o una seria minaccia a Spark nel range small-to-medium-data che abbiamo discusso nella lezione 1. Polars sta facendo la stessa cosa sul lato in-memory. Tutti questi sono reali, hanno tutti il loro posto, e nessuno di loro ha sloggiato Spark per l’ETL batch e streaming sui warehouse più grandi di una singola macchina grossa. Quello è ancora il terreno di casa di Spark, ed è dove questo corso ti terrà per le prossime 57 lezioni.

Lezione successiva: l’architettura. Driver, executor, cluster manager, task, stage, job: tutti i pezzi che devono essere nella tua testa prima che iniziamo a scrivere codice. Dopo possiamo finalmente installare qualcosa.

Per approfondire, la documentazione di Apache Spark è il riferimento canonico, il paper originale sull’RDD è il testo fondante, e il sorgente di Spark su GitHub è genuinamente leggibile se mai vuoi sapere perché il motore sta facendo quello che sta facendo.

Cerca