PySpark, dalle fondamenta Lezione 7 / 60

Installare PySpark in locale

Installare PySpark con pip, il requisito di Java che inciampa sempre tutti, e la gotcha di winutils di Hadoop solo su Windows.

Puoi leggere quaranta pagine su RDD, DataFrame, lineage e l’ottimizzatore Catalyst, ma se from pyspark.sql import SparkSession non gira sulla tua macchina, niente di tutto questo conta. Oggi installiamo PySpark, mettiamo in piedi una SparkSession, e mostriamo a schermo un piccolo DataFrame. Alla fine di questa lezione potrai fare copia e incolla degli esempi di ogni lezione successiva e farli funzionare.

Ci sono tre modi ragionevoli per far girare PySpark. Li facciamo tutti e tre velocemente, così puoi scegliere il tuo, e poi affrontiamo l’unica stranezza specifica di Windows che frega tutti la prima volta: winutils.exe.

Tre percorsi di installazione, scegline uno

Percorso A, pip-install di PySpark. Un solo comando, gira su Windows / macOS / Linux, ti dà tutto il necessario per scrivere ed eseguire script PySpark. Niente spark-submit, niente daemon master/worker, niente cluster: solo l’API Python e uno Spark locale embedded che gira dentro al tuo processo Python. Questo è quello che il 90% di chi impara e la maggior parte dei data engineer usa nel quotidiano. Consigliato per questo corso.

Percorso B, distribuzione completa di Apache Spark. Scarichi il tarball dal sito di Spark, lo estrai, imposti SPARK_HOME. Hai la stessa API PySpark più tutti gli script di shell: spark-submit, spark-shell, start-master.sh, start-worker.sh. Utile se vuoi simulare un vero cluster sul tuo portatile, o eseguire job Spark JVM accanto a Python. Esagerato per ora.

Percorso C, Databricks Community Edition. Notebook gratuiti in cloud che girano su un piccolo cluster Spark gestito. Niente installazione, niente Java, niente winutils. Ottimo se il tuo portatile è vecchio o se non vuoi combattere con il tuo ambiente. Iscriviti su community.cloud.databricks.com. Lo svantaggio: sei bloccato nella loro UI a notebook, non puoi eseguire script locali, e il cluster si spegne dopo un periodo di inattività.

Per il resto del corso assumerò il Percorso A (pip install). Se scegli il C, incolla il codice in una cella di notebook e salta gli step di installazione.

Il requisito di Java

Spark è un progetto JVM. PySpark è un wrapper Python che parla con una JVM in background tramite un ponte chiamato Py4J. Non puoi sfuggire al bisogno di avere Java sulla macchina.

Spark 3.5.x, la linea stabile attuale al momento in cui scrivo, supporta Java 8, 11 e 17. Spark 4.x (preview) richiede Java 17 o successivo. Qualsiasi altra cosa, e otterrai o un errore di startup oppure, peggio, crash misteriosi nel mezzo di un job.

Controlla cosa hai:

java -version

Vuoi un output tipo openjdk version "17.0.10" o "11.0.22". Se vedi 'java' is not recognized (Windows) o command not found (Mac/Linux), Java non è installato. Se vedi Java 21, fai downgrade: Spark 3.5 non supporta ancora ufficialmente la 21 e incapperai in problemi strani lato Hadoop.

La distribuzione più pulita da installare è Eclipse Temurin (l’OpenJDK costruito dalla community), da adoptium.net. Scegli Temurin 17 LTS. Esegui l’installer. Su Windows, spunta “Set JAVA_HOME variable” e “Add to PATH” durante l’installazione: entrambe sono disattivate di default e dimenticarle è la causa numero uno degli errori JAVA_HOME is not set.

Dopo aver installato, apri un terminale nuovo (le variabili d’ambiente non si propagano alle shell esistenti) e rilancia java -version. Dovrebbe funzionare.

Verifica che JAVA_HOME sia impostata:

# macOS / Linux
echo $JAVA_HOME

# Windows (PowerShell)
$env:JAVA_HOME

# Windows (cmd)
echo %JAVA_HOME%

Dovrebbe puntare a qualcosa tipo C:\Program Files\Eclipse Adoptium\jdk-17.0.10.7-hotspot o /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home.

Python: 3.8 o successivo

PySpark 3.5 supporta Python dalla 3.8 alla 3.12. Python 3.13 non è ancora ufficialmente supportato e PyArrow (una dipendenza transitiva) non ha ancora wheel stabili per essa. Se sei sulla 3.13 probabilmente combatterai con errori di installazione; fai downgrade alla 3.12 o usa pyenv/conda per gestire le versioni.

python --version
# Python 3.11.7

Usa un virtual environment. Sempre.

python -m venv .venv

# Attivalo:
# macOS / Linux
source .venv/bin/activate
# Windows (PowerShell)
.venv\Scripts\Activate.ps1
# Windows (cmd)
.venv\Scripts\activate.bat

Percorso A: pip install pyspark

Con Java installato, il virtual environment attivato e Python 3.8-3.12 a posto:

pip install pyspark==3.5.1

Fissare la versione è una buona abitudine. Senza ottieni qualunque sia l’ultima, che oggi è 3.5.1 ma potrebbe essere 3.5.5 o 4.0.0 il mese prossimo. Le lezioni che seguono assumono 3.5.x.

Il download è di circa 280 MB, in gran parte sono i JAR di Spark nel bundle. Pazienza.

Già che ci siamo, installa un’altra cosa:

pip install pyarrow

PyArrow non è strettamente richiesto, ma velocizza enormemente le conversioni Pandas-to-Spark e le chiamate toPandas() che useremo più avanti. PySpark moderno avvisa all’avvio se manca.

Percorso B (opzionale): distribuzione Spark completa

Se vuoi spark-submit e compagni, fai questo in aggiunta al Percorso A.

  1. Vai su spark.apache.org/downloads.
  2. Scegli Spark 3.5.1, package type “Pre-built for Apache Hadoop 3.3 and later.”
  3. Scarica il .tgz. Estrailo da qualche parte di stabile. Io tengo il mio in ~/spark su macOS e C:\spark su Windows.
  4. Imposta SPARK_HOME su quella cartella, e aggiungi $SPARK_HOME/bin al tuo PATH.
# macOS / Linux: aggiungi a ~/.zshrc o ~/.bashrc
export SPARK_HOME=$HOME/spark/spark-3.5.1-bin-hadoop3
export PATH=$SPARK_HOME/bin:$PATH

# Windows: System Environment Variables -> New -> SPARK_HOME = C:\spark\spark-3.5.1-bin-hadoop3
# Poi modifica Path -> Aggiungi %SPARK_HOME%\bin

Verifica:

spark-submit --version

Dovresti vedere un logo Spark in ASCII art. Se lo vedi, la distribuzione è collegata correttamente. Non la useremo molto in questo corso, ma c’è.

La danza di winutils, solo su Windows

Adesso la parte su cui inciampano tutti.

Spark usa internamente il codice di filesystem di Hadoop, anche quando in realtà non stai usando Hadoop. Su Linux e macOS, lo strato I/O di Hadoop chiama semplicemente l’OS. Su Windows, Hadoop vuole un binario specifico chiamato winutils.exe più un paio di DLL native. Non vengono distribuite con Hadoop o Spark. Devi scaricarle separatamente.

Se salti questo passaggio, il sintomo è uno di questi:

  • java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries
  • UnsatisfiedLinkError: org.apache.hadoop.io.nativeio.NativeIO$Windows
  • Un warning all’avvio che dice che Spark ignora i permessi sui file (meno catastrofico, ma brutto).

La fix:

  1. Scegli una versione di Hadoop. PySpark 3.5 viene distribuito contro Hadoop 3.3, quindi prendi winutils per Hadoop 3.3.
  2. Scarica i binari dal repository community-maintained su github.com/cdarlint/winutils. Specificamente la cartella hadoop-3.3.x/bin/.
  3. Mettili da qualche parte stabile: io uso C:\hadoop\bin\. Sia winutils.exe che hadoop.dll devono stare in quella cartella bin.
  4. Imposta HADOOP_HOME=C:\hadoop e aggiungi %HADOOP_HOME%\bin al tuo PATH.
# PowerShell, persistente
[Environment]::SetEnvironmentVariable("HADOOP_HOME", "C:\hadoop", "User")
$env:Path += ";C:\hadoop\bin"

Apri un terminale nuovo. Verifica:

winutils.exe ls C:\

Se stampa l’elenco di una directory, hai finito. PySpark troverà winutils.exe tramite HADOOP_HOME e smetterà di lamentarsi.

Utenti Mac e Linux: salta tutta questa sezione. Il path POSIX di Hadoop funziona bene sul tuo OS e non hai bisogno di niente di tutto questo.

Il sanity check da cinque righe

Salva questo come hello_spark.py:

from pyspark.sql import SparkSession

spark = SparkSession.builder.appName("HelloSpark").getOrCreate()

df = spark.createDataFrame([(1, "Narcis"), (2, "Spark"), (3, "Italy")], ["id", "name"])
df.show()

spark.stop()

Eseguilo:

python hello_spark.py

La prima volta che lo esegui, aspettati una marea di righe di log INFO e WARN. È normale: Spark di default è rumoroso. Da qualche parte nel rumore vedrai:

+---+------+
| id|  name|
+---+------+
|  1|Narcis|
|  2| Spark|
|  3| Italy|
+---+------+

Se vedi questa tabella, la tua installazione va bene. Passa alla lezione successiva.

Silenziare i log

Il livello di log di default è WARN, ma all’avvio Spark stampa un sacco di righe INFO da Hadoop e dalla JVM. Per avere un output più pulito:

from pyspark.sql import SparkSession

spark = (SparkSession.builder
         .appName("HelloSpark")
         .getOrCreate())

spark.sparkContext.setLogLevel("WARN")  # o "ERROR" per ancora più silenzio

df = spark.createDataFrame([(1, "Narcis"), (2, "Spark")], ["id", "name"])
df.show()

spark.stop()

Questo imposta il livello dopo che la sessione è stata costruita. Vedremo l’approccio più pulito (un file log4j2.properties in SPARK_HOME/conf/) quando arriveremo agli argomenti operativi nel Modulo 8.

Gli errori del primo avvio e come risolverli

Questi quattro colpiscono circa l’80% delle nuove installazioni. Memorizza la mappatura sintomo-soluzione.

1. JAVA_HOME is not set o 'java' is not recognized. Java non è installato, oppure è installato ma JAVA_HOME e PATH non puntano a esso. Reinstalla Temurin 17 con la spunta “Set JAVA_HOME” attivata. Apri un terminale nuovo dopo.

2. Could not locate executable null\bin\winutils.exe. Solo Windows. Hai saltato la sezione winutils. Torna indietro, installa winutils, imposta HADOOP_HOME, apri una nuova shell.

3. Py4JJavaError: An error occurred while calling ... Java gateway process exited before sending its port number. Spark ha avviato una JVM, la JVM è crashata prima di poter chiamare casa. Di solito una di queste:

  • Versione di Java sbagliata (hai 21 o 8 mentre Spark vuole 11/17). Cambia.
  • Antivirus o VPN aziendale che blocca il socket locale. Prova con la VPN spenta.
  • Su Mac con Apple Silicon, hai preso un JDK x86. Prendi invece la build ARM64 / aarch64 di Temurin.

4. WARN ProcfsMetricsGetter: Exception when trying to compute pagesize. Warning cosmetico Linux/macOS, ignoralo. Spark non riesce a trovare getconf PAGESIZE su alcune configurazioni. Nessun effetto sui job.

Se incappi in qualcosa che non è in questa lista, copia la prima eccezione nello stack trace (di solito sepolta sotto cinque livelli di wrapping Py4J) e cercala. Nove volte su dieci qualcuno su Stack Overflow ha avuto lo stesso problema dal 2016.

Due parole sugli IDE

Il corso non assume nessun particolare editor, ma per quel che vale: VS Code con le estensioni Python e Jupyter è un ambiente PySpark molto piacevole. Apri una qualsiasi cartella che contenga il tuo .venv dentro, premi Ctrl+Shift+P -> “Python: Select Interpreter,” scegli il venv. Da quel momento il terminale integrato attiva il venv automaticamente e l’editor sa dove vive pyspark.

Per il lavoro interattivo, butta un notebook Jupyter nella stessa cartella:

pip install jupyter
jupyter notebook

Puoi eseguire lo stesso codice di costruzione della SparkSession in una cella di notebook. La sessione resta viva tra le celle, quindi la costruisci una volta in cima e poi continui a smanettare con i DataFrame nelle celle successive. Useremo questo pattern per tutto il corso.

Se preferisci JetBrains, PyCharm Community funziona altrettanto bene. DataSpell è l’IDE notebook-flavoured di JetBrains e ha preview dei DataFrame migliori.

Verifica con un test un po’ più realistico

Lo script da cinque righe sopra dimostra che Spark si avvia. Questo dimostra che riesce a fare lavoro. Salva come verify_spark.py:

from pyspark.sql import SparkSession
from pyspark.sql.functions import col, count, avg

spark = (SparkSession.builder
         .appName("VerifySpark")
         .master("local[*]")
         .getOrCreate())
spark.sparkContext.setLogLevel("WARN")

# Genera 1 milione di righe sinteticamente, senza dati esterni
df = spark.range(0, 1_000_000) \
          .selectExpr("id",
                      "id % 7 AS day_of_week",
                      "(id * 13) % 100 AS amount")

# Group, aggregate, sort
result = (df.groupBy("day_of_week")
            .agg(count("*").alias("rows"),
                 avg("amount").alias("avg_amount"))
            .orderBy("day_of_week"))

result.show()

print(f"Spark version: {spark.version}")
print(f"Spark UI:      {spark.sparkContext.uiWebUrl}")

spark.stop()

Eseguilo:

python verify_spark.py

Dovresti vedere una tabella di 7 righe con day_of_week da 0 a 6, circa 142.857 righe in ogni bucket, e una media intorno a 49.5. Tempo di esecuzione totale circa 5-10 secondi su un portatile moderno. Se funziona, la tua installazione è completamente in forma da produzione: generatori, aggregazioni, ordinamenti, tutto.

Dove siamo adesso

Hai:

  • Java 11 o 17 installato e nel PATH.
  • Python 3.8-3.12 in un virtualenv.
  • pyspark==3.5.1 e pyarrow installati.
  • (Solo Windows) winutils.exe in %HADOOP_HOME%\bin.
  • Un hello_spark.py funzionante che stampa un piccolo DataFrame.

Questo è tutto l’ambiente di sviluppo per il resto del corso. Prossima lezione, apriamo SparkSession.builder e guardiamo ogni manopola di configurazione importante: cosa fa davvero local[*], perché spark.sql.shuffle.partitions ha default 200 anche quando hai 8 core, e come leggere la Spark UI su localhost:4040.

Cerca