Partie 1 – Big Data, calcul distribué et premiers pas avec Spark 4
Et si vous deviez analyser 10 ans de courses de taxi… avec Excel ? Comment feriez-vous ?
Le problème : quand une seule machine ne suffit plus
Section intitulée « Le problème : quand une seule machine ne suffit plus »Excel plafonne à un million de lignes. pandas charge tout en mémoire. Une base SQL tient sur un serveur. Trois limites d’une même idée : une seule machine. Le Big Data, ce sont les situations où cette machine ne suffit plus :
| Les 3 V | Ce que ça veut dire | Sur notre fil rouge |
|---|---|---|
| Volume | Des téraoctets, au-delà de la RAM et du disque d’un serveur | 3 millions de courses par mois, 10 ans d’historique |
| Vélocité | Des données qui arrivent en continu | Chaque course est enregistrée au moment où elle se termine |
| Variété | Logs, JSON, images, tables | Courses (CSV, Parquet), zones (référentiel), logs applicatifs (texte) |
Acheter un serveur plus gros (scale up) coûte vite très cher et atteint une limite physique. La solution est de répartir le travail sur plusieurs machines ordinaires (scale out) :
- Découper les données en partitions.
- Envoyer le calcul là où sont les données, plutôt que les données vers le calcul.
- Traiter les partitions en parallèle sur N machines.
- Tolérer les pannes : si une machine tombe, on recalcule sa partition ailleurs.
Analogie : pour compter les habitants d’une ville, on n’envoie pas un seul recenseur. On envoie un recenseur par quartier, puis on additionne.
🔍 Vulgarisation : Scale Up vs Scale Out (l'analogie du déménagement)
Imaginez que vous devez déménager 100 tonnes de cartons en une matinée :
- Scale Up (vertical) : vous commandez un camion-benne géant sur mesure capable de porter 100 tonnes d’un coup.
- Problème 1 : il coûte le prix de 50 camionnettes classiques.
- Problème 2 : si vous passez à 500 tonnes demain, aucun camion au monde n’est assez grand (limite physique de la RAM et des processeurs d’une carte mère).
- Problème 3 : s’il crève un pneu, tout le déménagement s’arrête.
- Scale Out (horizontal — l’approche Spark) : vous louez 20 camionnettes standard, chacune transporte 5 tonnes (une partition) en même temps.
- Si vous passez à 200 tonnes, vous louez simplement 20 camionnettes de plus.
- Si la camionnette n°7 tombe en panne, une autre camionnette refait son trajet de 5 tonnes pendant que les 19 autres terminent leur travail.
Pourquoi Spark, et pas MapReduce ?
Section intitulée « Pourquoi Spark, et pas MapReduce ? »Le calcul distribué existait avant Spark : Hadoop MapReduce (2006) découpe un traitement en phases map et reduce et écrit sur disque (HDFS) entre chaque phase. Pour un traitement en une seule passe, c’est acceptable. Pour un pipeline à plusieurs étapes ou un algorithme itératif – le Machine Learning fait des dizaines de passes sur les mêmes données – c’est un goulot d’étranglement massif.
Spark (UC Berkeley, 2009 ; article fondateur de Zaharia et al.) garde les données et les résultats intermédiaires en mémoire vive (RAM) d’une étape à l’autre : jusqu’à 100 fois plus rapide en mémoire (et environ 10 fois plus rapide même sur disque) sur les algorithmes itératifs. Il ajoute une API unifiée – SQL, DataFrames, Machine Learning, streaming, graphes – en Python, Scala, Java, R et SQL, avec un seul moteur d’exécution. Spark ne remplace pas HDFS : il lit et écrit dessus, comme sur S3, ADLS ou une base de données.
MapReduce (1 itération = 1 lecture HDFS + shuffle disque + 1 écriture HDFS) : HDFS ──▶ [Lecture 1] ──▶ Map ──▶ Reduce ──▶ [Écriture 1 sur HDFS] ──▶ [Lecture 2] ──▶ Map ──▶ Reduce ──▶ [Écriture 2 sur HDFS] ──▶ [Lecture 3] ──▶ Map ──▶ Reduce ──▶ [Écriture 3 sur HDFS] …
Spark (1 seule lecture initiale + tout le reste en RAM + 1 écriture finale) : HDFS ──▶ [Lecture 1] ──▶ RAM ──▶ Étape 1 ──▶ Étape 2 ──▶ Étape 3 … ──▶ [Écriture finale]Faisons le calcul : combien de lectures et d’écritures sur disque ?
Section intitulée « Faisons le calcul : combien de lectures et d’écritures sur disque ? »Dans MapReduce, deux jobs ne peuvent partager des données qu’en passant par HDFS : chaque itération lit tout le jeu de données sur disque au début et réécrit le résultat sur disque à la fin (sans même compter le spill local entre Map et Reduce, ni la réplication HDFS ×3 à chaque écriture).
Dans Spark, on lit les données une seule fois depuis le disque, on les épingle en mémoire (.cache()), on
enchaîne toutes les itérations en RAM, et on écrit le résultat final une seule fois.
Prenons un jeu de données de 100 Go :
| Scénario | MapReduce (disque à chaque étape) | Spark (en mémoire) | Ce qu’on économise sur disque |
|---|---|---|---|
| 2 opérations enchaînées (ex. filtrer puis agréger) | 2 lectures + 2 écritures = 4 passages sur disque (400 Go d’E/S) | 1 lecture + 1 écriture (les 2 opérations sont fusionnées en RAM) = 2 passages (200 Go) | 2× moins d’accès disque dès 2 opérations |
| 4 itérations (ex. 4 étapes d’un pipeline ou 4 tours de K-Means) | 4 lectures + 4 écritures = 8 passages sur disque (800 Go d’E/S) | 1 lecture au départ + 4 tours en RAM + 1 écriture à la fin = 2 passages | 6 passages disque évités sur 8 (75 % d’E/S en moins) |
| 10 itérations (ex. PageRank ou validation croisée) | 10 lectures + 10 écritures = 20 passages sur disque (2 To d’E/S) | 1 lecture + 10 tours en RAM + 1 écriture = 2 passages | 18 passages disque évités sur 20 (90 % d’E/S en moins) |
| 20 itérations (ex. régression logistique / descente de gradient — le benchmark historique de Berkeley) | 20 lectures + 20 écritures = 40 passages sur disque (4 To d’E/S) | 1 lecture + 20 tours en RAM + 1 écriture = 2 passages | 38 passages disque évités sur 40 (95 % d’E/S en moins) |
| $N$ itérations (formule générale) | $N$ lectures + $N$ écritures = $2N$ accès disque | 1 lecture + 1 écriture = 2 accès disque (les $N$ tours restent en RAM) | Facteur $N\times$ sur le nombre d’accès disque |
D’où vient exactement le facteur « jusqu’à 100× plus rapide » ?
Section intitulée « D’où vient exactement le facteur « jusqu’à 100× plus rapide » ? »Ce chiffre n’est pas un slogan marketing : il vient de deux effets qui se multiplient quand le nombre d’itérations augmente :
- Le débit de la RAM vs le disque + réseau : lire en mémoire vive se fait à 10 à 50 Go/s par nœud, contre 0,1 à 0,5 Go/s pour un disque HDFS avec sérialisation, parsing et réplication ×3 à chaque écriture.
- L’accumulation sur les itérations :
- Sur 1 seule passe, Spark et MapReduce doivent tous les deux lire le fichier sur disque : l’écart reste modeste (souvent 2× à 3×, grâce au moteur DAG de Spark).
- Dès 4 itérations, MapReduce relit et réécrit 4 fois sur disque pendant que Spark travaille déjà en RAM : l’écart grimpe à 10× – 20×.
- Sur 10 à 20 itérations (le benchmark officiel de l’article de Berkeley sur la régression logistique et K-Means), chaque itération supplémentaire coûte quelques dizaines de secondes à MapReduce (lecture HDFS + démarrage de tâche + écriture HDFS) contre moins d’une seconde en RAM dans Spark : ≈ 80 s par itération en Hadoop contre < 1 s en Spark après le premier chargement, soit jusqu’à 100× plus rapide.
🧮 Chronométrage seconde par seconde : comment passe-t-on de 26 minutes (MapReduce) à 18 secondes (Spark) sur 20 itérations ?
Vérifions le calcul avec des ordres de grandeur réels sur un petit cluster traitant 100 Go de données avec un algorithme itératif (ex. K-Means ou régression logistique sur 20 itérations) :
-
En Hadoop MapReduce :
- Lire 100 Go depuis HDFS + parser le texte : ≈ 35 s
- Calculer l’itération en CPU : ≈ 2 s
- Sérialiser + écrire 100 Go sur HDFS (avec réplication ×3 sur le réseau) + démarrer la JVM du job suivant : ≈ 43 s
- Coût d’1 itération MapReduce : $35 + 2 + 43 =$ 80 secondes.
- Sur 4 itérations : $4 \times 80\text{ s} =$ 320 s (5 min 20 s).
- Sur 20 itérations : $20 \times 80\text{ s} =$ 1 600 s (≈ 26 minutes et 40 secondes), dont 97 % du temps passé à attendre le disque et le réseau.
-
En Apache Spark (avec
.cache()en RAM) :- Itération 1 : lire les 100 Go depuis HDFS et les placer en RAM = ≈ 35 s + calcul 0,8 s.
- Itérations 2 à 20 (19 tours) : les données sont déjà en RAM (débit 20 à 50 Go/s, aucun parsing disque) → chaque tour ne prend que ≈ 0,8 seconde !
- Sur 4 itérations : $35\text{ s} + (4 \times 0,8\text{ s}) \approx$ 38 s (contre 320 s en MapReduce).
- Sur 20 itérations : après le chargement initial, les 19 itérations suivantes prennent $19 \times 0,8\text{ s} \approx$ 15 secondes (contre 1 520 secondes en MapReduce), soit un rapport de $80\text{ s} / 0,8\text{ s} = 100\times$ plus rapide par itération en mémoire !
Spark 3 et Spark 4 en bref
Section intitulée « Spark 3 et Spark 4 en bref »| Version | Ce qu’elle a apporté |
|---|---|
| Spark 3.0 – 3.5 (2020 – 2023) | AQE (optimisation adaptative, activé par défaut depuis 3.2), Pandas API on Spark, Kubernetes en production, Spark Connect (3.4). 3.5 est la version LTS, maintenue jusqu’en 2027. |
| Spark 4.0 (mai 2025) | ANSI SQL par défaut : une division par zéro ou un cast invalide lève une erreur au lieu de produire un NULL silencieux. Type VARIANT pour le JSON semi-structuré (jusqu’à 8× plus rapide que les chaînes). Java 17+, Scala 2.13. Mesos supprimé. Spark Connect quasi complet. SparkR déprécié. |
| Spark 4.1 (2026) | Pipelines déclaratifs (Declarative Pipelines), mode temps réel du Structured Streaming (latence en millisecondes). |
Deux changements de Spark 4 cassent du code existant : le mode ANSI (le bonus 2 du TP 4 vous le fera toucher du
doigt avec try_divide) et la suppression de Mesos (migration vers Standalone, YARN ou Kubernetes).
⚠️ Focus Spark 4 : Pourquoi l'activation d'ANSI SQL par défaut est-elle cruciale (exemple concret) ?
Dans Spark 3, si une course de taxi avait trip_distance = 0 (client qui monte puis annule immédiatement) et que vous calculiez le prix au mile total_amount / trip_distance :
- Spark 3 renvoyait silencieusement
NULLsans aucun avertissement. - Si 5 % de vos lignes devenaient
NULLà cause d’une erreur de format ou d’une division par zéro, votreAVG()ignorait ces lignes et produisait un chiffre faux en silence !
Dans Spark 4, le mode ANSI SQL est activé par défaut (spark.sql.ansi.enabled = true) :
total_amount / 0lève immédiatement une erreur explicite[DIVIDE_BY_ZERO]qui vous oblige à décider quoi faire.- Pour autoriser explicitement le retour
NULLsur une division par zéro, vous utilisez la fonction dédiéetry_divide(total_amount, trip_distance)(outry_castpour une conversion). Votre code devient explicite et sûr.
Installer Spark 4 et le tester avec Jupyter
Section intitulée « Installer Spark 4 et le tester avec Jupyter »Spark est écrit en Scala et tourne sur la JVM : il faut un Java 17 ou 21. PySpark est le paquet Python qui embarque Spark et le pilote depuis Python.
# 1. Vérifier Java 17 (ou 21)java -version
# 2. Installer PySpark (inclut Spark) et Jupyterpip install pyspark jupyter# 3. Dans un notebook (VS Code ou Jupyter)from pyspark.sql import SparkSession
spark = (SparkSession.builder .appName("decouverte") .master("local[*]") .getOrCreate())
spark.range(1_000_000).count()print(spark.sparkContext.uiWebUrl) # 4. Spark UI : http://localhost:4040local[*]: Spark utilise tous les cœurs de votre machine, dans un seul processus, sans cluster.local[2]en utiliserait deux.- Standalone : Spark seul, sans Hadoop ni YARN. C’est le mode du TP 3.
- La Spark UI (port 4040, ou 4041 si le port est pris) montre chaque job, ses stages et ses tâches. Le lien
fiable est
spark.sparkContext.uiWebUrl.
🔍 Sous le capot : Que se passe-t-il exactement sur votre portable avec local[*] ?
Quand vous écrivez .master("local[*]") sur un PC portable à 8 cœurs logiques :
┌─────────────────── Votre PC portable (1 seule JVM) ───────────────────┐│ Driver (planifie le DAG) + Executor local (8 threads de calcul) ││ ││ Thread 1 ──▶ Partition 1 Thread 5 ──▶ Partition 5 ││ Thread 2 ──▶ Partition 2 Thread 6 ──▶ Partition 6 ││ Thread 3 ──▶ Partition 3 Thread 7 ──▶ Partition 7 ││ Thread 4 ──▶ Partition 4 Thread 8 ──▶ Partition 8 │└───────────────────────────────────────────────────────────────────────┘- Spark ne démarre qu’un seul processus Java (JVM) qui joue à la fois le rôle de Driver (chef d’orchestre) et d’Executor (ouvrier).
- Le caractère
*demande à Spark de créer autant de threads de calcul que votre processeur a de cœurs logiques (ex. 8 cœurs = 8 partitions traitées simultanément). - L’immense avantage : le code que vous écrivez en
local[*]est 100 % identique à celui qui tournera demain sur un cluster de 100 serveurs.
Le fil rouge en 4 étapes
Section intitulée « Le fil rouge en 4 étapes »Toute la journée travaille sur data/trips.csv et data/trips.parquet (120 000 courses, format officiel NYC TLC) et
sur le référentiel data/taxi_zone_lookup.csv. Les 5 colonnes utiles aujourd’hui : tpep_pickup_datetime,
PULocationID, trip_distance, total_amount, et la table des zones (LocationID → Borough, Zone).
| TP | Question | Avec quoi |
|---|---|---|
| TP 1 | Charger et explorer | SparkSession, Spark UI |
| TP 2 | Top 10 des zones | RDD, PairRDD |
| TP 3 | Le même job sur un cluster + HDFS | Standalone, spark-submit, HDFS |
| TP 4 | Le même job en Spark SQL | DataFrames, SQL |
Le détail du jeu de données est sur la page Le fil rouge.
À vous de jouer
Section intitulée « À vous de jouer »→ TP 1 – Premier contact (30 min)
Transition : « 120 000 lignes, ça passe sur un portable. Et 3 milliards ? Ouvrons le capot : que fait vraiment Spark
quand vous lancez count() ? » → Partie 2 – Spark Core
Dépôt des participants : formations2026/formation-spark-ml (branche participants)
Contenu pédagogique © Ambient IT · 140 Avenue Jean Lolive, 93500 Pantin · formation@ambient-it.net · +33 (0)1 48 10 09 46
Organisme de formation enregistré sous le numéro 11 75 54743 75. Cet enregistrement ne vaut pas agrément de l'État.