Ecosistema Apache Spark
Introducció
Apache Spark va néixer el 2009 al laboratori AMPLab de la Universitat de Berkeley com a resposta a les limitacions de MapReduce. La idea central era deceptivament simple: i si en lloc d'escriure i llegir d'HDFS entre cada pas del pipeline, mantinguéssim les dades en memòria RAM? El resultat va ser un sistema de processament distribuït fins a 100 vegades més ràpid que MapReduce per a algoritmes iteratius.
El 2025, Apache Spark 3.5 és l'estàndard de facto per al processament de Big Data. El 90% de les ofertes de feina de Data Engineer mencionen Spark. Databricks, la empresa fundada pels creadors de Spark, va assolir una valoració de 43.000 milions de dòlars. PySpark (l'API Python de Spark) domina àmpliament sobre Scala.
Arquitectura de Spark
graph TB
subgraph Client
DR[Driver Program\n- SparkContext/SparkSession\n- DAG Scheduler\n- Task Scheduler]
end
subgraph ClusterManager["Cluster Manager (YARN / Kubernetes / Standalone)"]
CM[Gestor de recursos\nAssigna contenidors]
end
subgraph Workers
subgraph Worker1["Worker Node 1"]
EX1[Executor 1\n- Tasks\n- Memoria cache\n- Disk spill]
end
subgraph Worker2["Worker Node 2"]
EX2[Executor 2\n- Tasks\n- Memoria cache\n- Disk spill]
end
subgraph Worker3["Worker Node 3"]
EX3[Executor 3\n- Tasks\n- Memoria cache\n- Disk spill]
end
end
DR -->|demana recursos| CM
CM -->|assigna| EX1
CM -->|assigna| EX2
CM -->|assigna| EX3
DR -->|envia tasks| EX1
DR -->|envia tasks| EX2
DR -->|envia tasks| EX3
EX1 -->|resultat final| DR
Driver: el procés principal de l'aplicació Spark. Crea la SparkSession, construeix el pla d'execució (DAG) i coordina els executors. Si el Driver mor, l'aplicació falla.
Executors: els processos que executen les tasques reals. Cada executor té una memòria assignada i un conjunt de CPU cores. Llegeixen i escriuen les dades, realitzen les transformacions i comuniquen els resultats al Driver.
Cluster Manager: el sistema que gestiona els recursos del clúster. Spark suporta YARN (Hadoop), Kubernetes, Mesos i el seu propi Standalone mode.
Components de l'ecosistema Spark
graph TD
SPARK[Apache Spark Core]
SQL[Spark SQL\nDataFrames / Datasets]
STREAM[Structured Streaming]
MLLIB[MLlib\nMachine Learning]
GRAPH[GraphX\nProcessament de Grafs]
SPARK --> SQL
SPARK --> STREAM
SPARK --> MLLIB
SPARK --> GRAPH
| Component | Descripció | Cas d'ús típic |
|---|---|---|
| Spark Core | Motor d'execució distribuïda, RDDs | Base de tots els components |
| Spark SQL | Consultes SQL sobre DataFrames | ETL, analytics, BI |
| Structured Streaming | Processament en temps real | Pipelines de streaming, Kafka |
| MLlib | Algoritmes ML distribuïts | Models sobre grans volums |
| GraphX | Algoritmes de grafs distribuïts | Xarxes socials, recomanació |
MLlib: Machine Learning distribuït
MLlib és la biblioteca de Machine Learning distribuïda de Spark. La seva Pipeline API segueix el patró de scikit-learn:
from pyspark.ml import Pipeline
from pyspark.ml.feature import Tokenizer, StopWordsRemover, HashingTF, IDF, StringIndexer
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
# Exemple: classificació de ressenyes de productes
df_ressenyes = spark.createDataFrame([
("El producte és excel·lent, molt recomanable!", "positiu"),
("Terribles. No compreu mai aquest producte.", "negatiu"),
("Qualitat mediocre, no val el preu que demanen.", "negatiu"),
("Impressionant! Supera totes les expectatives.", "positiu"),
], ["text", "etiqueta"])
# Pipeline de NLP
tokenizer = Tokenizer(inputCol="text", outputCol="paraules")
remover = StopWordsRemover(
inputCol="paraules",
outputCol="paraules_netes",
stopWords=StopWordsRemover.loadDefaultStopWords("catalan")
)
hashingTF = HashingTF(inputCol="paraules_netes", outputCol="tf", numFeatures=10000)
idf = IDF(inputCol="tf", outputCol="features")
indexer = StringIndexer(inputCol="etiqueta", outputCol="label")
lr = LogisticRegression(maxIter=100, regParam=0.01)
pipeline = Pipeline(stages=[tokenizer, remover, hashingTF, idf, indexer, lr])
# Divisió train/test
train, test = df_ressenyes.randomSplit([0.8, 0.2], seed=42)
# Entrenar i avaluar
model = pipeline.fit(train)
prediccions = model.transform(test)
evaluador = MulticlassClassificationEvaluator(metricName="accuracy")
print(f"Precisió: {evaluador.evaluate(prediccions):.2%}")
PySpark vs Scala Spark el 2025
La pregunta de si usar PySpark o Spark amb Scala és recurrent. La resposta el 2025 és clara per a la majoria de casos:
Usa PySpark quan: - El teu equip domina Python (el 90% dels Data Scientists i Data Engineers) - Necessites integrar biblioteques Python (pandas, scikit-learn, PyTorch, Hugging Face) - Vols iteració ràpida i notebooks interactius - El rendiment és acceptable per al teu cas d'ús (Spark 3.x amb Pandas on Spark és molt ràpid)
Considera Scala quan: - El rendiment és crític i l'overhead Python → JVM importa (casos rars amb Spark 3.5+) - El teu equip ve d'un background Java/Scala - Construeixes extensions personalitzades de Spark (custom transformers, optimitzadors)
Configuració i tuning de Spark
# Configuració d'una SparkSession per a producció
spark = SparkSession.builder \
.appName("Pipeline_Produccio_2025") \
.config("spark.executor.memory", "8g") \
.config("spark.executor.cores", "4") \
.config("spark.executor.instances", "10") \
.config("spark.driver.memory", "4g") \
.config("spark.driver.maxResultSize", "2g") \
.config("spark.sql.shuffle.partitions", "200") \
.config("spark.sql.adaptive.enabled", "true") \
.config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \
.config("spark.sql.parquet.filterPushdown", "true") \
.config("spark.sql.parquet.mergeSchema", "false") \
.config("spark.default.parallelism", "40") \
.getOrCreate()
Regles de tuning:
- Particions:
spark.sql.shuffle.partitionsper defecte és 200. Per a datasets petits (<10 GB), reduir a 8-50 millora el rendiment. - Memòria:
spark.executor.memoryhauria de ser un 75% de la RAM del worker disponible per a Spark. - Skew: si una partició és molt més gran, usar
SKEW_HINTo AQE skew join. - Cache: usar
.cache()o.persist(StorageLevel.MEMORY_AND_DISK)per a DataFrames usats múltiples vegades. - Broadcast: si un DataFrame és petit (<100 MB), forçar un broadcast join per evitar shuffles.
from pyspark.sql.functions import broadcast
# Forçar un broadcast join
df_resultat = df_gran \
.join(broadcast(df_petit), on="id_categoria", how="left")
Databricks: la plataforma Spark empresarial
Databricks és la plataforma de Big Data i IA més usada a les empreses el 2025. Fundada pels creadors de Spark, ofereix una experiència molt superior al Spark "pur":
- Databricks Runtime: versió de Spark optimitzada, típicament 5-10x més ràpida que open-source
- Unity Catalog: governança unificada de dades, ML i IA generativa
- Delta Sharing: compartir dades de forma segura entre organitzacions
- Notebooks: entorn col·laboratiu en temps real (com Google Docs per a notebooks)
- Workflows: orquestració de pipelines sense Airflow addicional
- MLflow integrat: tracking d'experiments i desplegament de models
Databricks Community Edition
Databricks ofereix un account Community Edition gratuït a community.cloud.databricks.com que inclou un clúster de processament limitat però suficient per aprendre. Inclou exemples de datasets i notebooks de tutorial.
Miniactivitat: Explorar el Spark UI
Mentre executes qualsevol job Spark (local o en Docker), obre el navegador a http://localhost:4040. Aquí pots veure:
- Tots els jobs en execució i completats
- El DAG (grafo de dependències) de cada job
- Les estadístiques de cada tasca (temps, bytes llegits/escrits, shuffle)
- L'ús de memòria dels executors
Identifica quines transformacions causen un "shuffle" (redistribució de dades entre executors) i quines no. Els shuffles són els principals colls d'ampolla en Spark.