Module 2 — RDD, DataFrame et Dataset
Spark a traversé trois âges d'API, et les trois cohabitent encore dans la même bibliothèque. Comprendre ce lignage évite deux pièges opposés : utiliser le RDD par nostalgie et payer une lenteur inutile, ou fuir toute API bas niveau alors que quelques cas la réclament encore. Le module 1 a posé le décor matériel ; celui-ci pose le décor logique.
RDD — l'ancêtre, encore utile parfois
Le RDD (Resilient Distributed Dataset) est l'abstraction d'origine de Spark, apparue en 2012. C'est une collection distribuée et immuable d'objets Java ou Python arbitraires. On y écrit des transformations en termes de fonctions Python appliquées à chaque élément, sans schéma ni type de colonne.
lignes = spark.sparkContext.textFile("data/journal.log")
mots = lignes.flatMap(lambda l: l.split()).map(lambda m: (m, 1))
comptes = mots.reduceByKey(lambda a, b: a + b)
Le RDD reste pertinent pour des données vraiment non tabulaires (journaux
bruts, objets binaires personnalisés) ou pour un contrôle manuel du
partitionnement. Il souffre en revanche d'un défaut majeur : Spark ne
comprend pas ce que font vos map et filter. Impossible d'optimiser
l'ordre des opérations, impossible de pousser un filtre au stockage
Parquet. Chaque lambda Python est aussi une passerelle Java-Python
coûteuse, sérialisation à l'aller et au retour pour chaque partition.
DataFrame — le défaut d'aujourd'hui
Le DataFrame est arrivé en 2015 avec Spark SQL. C'est une collection
distribuée de lignes typées par un schéma (colonnes nommées, types
IntegerType, StringType, TimestampType…). L'API ressemble à celle
de pandas, mais les opérations sont exprimées de façon déclarative,
comme du SQL.
from pyspark.sql import functions as F
retards = (
vols
.filter(F.col("origine") == "CDG")
.groupBy("compagnie")
.agg(F.avg("retard_arrivee").alias("retard_moyen"))
.orderBy(F.desc("retard_moyen"))
)
Ce que Spark reçoit ici n'est pas une suite de fonctions Python à exécuter, c'est un plan logique : « garder les lignes où l'origine vaut CDG, grouper par compagnie, moyenner le retard ». L'optimiseur peut réordonner, fusionner, ou pousser le filtre au niveau de la lecture du fichier. Et parce que rien n'exécute du Python côté exécuteur pour la plupart des opérations, la passerelle Java-Python disparaît.
Sur des données tabulaires — c'est-à-dire la quasi-totalité de ce qu'on traite en apprentissage automatique — le DataFrame est de deux à dix fois plus rapide qu'un RDD équivalent, tout en étant plus lisible.
Dataset — l'entre-deux Scala
Le Dataset est une API typée qui combine le schéma du DataFrame avec des objets Scala ou Java. Elle offre la sécurité de typage à la compilation. En PySpark, le Dataset n'existe pas : Python n'a pas les types statiques nécessaires, et le DataFrame joue le rôle des deux. Nous n'en reparlerons plus, c'est un choix Scala qui n'affecte pas ce cours.
Catalyst, l'optimiseur qui change tout
Derrière chaque requête DataFrame, un moteur appelé Catalyst construit un plan logique, applique des règles d'optimisation (élagage de colonnes, poussée de filtres au stockage, réordonnancement de jointures) et génère un plan physique. Un moteur voisin, Tungsten, se charge du code de bas niveau : gestion mémoire hors tas, génération de code Java à la volée.
Vous inspectez ce plan avec .explain():
retards.explain(mode="formatted")
La sortie montre le plan physique : un Scan Parquet avec les colonnes
retenues (PushedFilters: [EqualTo(origine, CDG)]), un HashAggregate
local sur chaque partition, un brassage, puis un HashAggregate final.
Lire cette sortie devient rapidement un réflexe de diagnostic : c'est là
qu'on voit si le filtre a bien été poussé, si un Broadcast a été choisi
pour une jointure, si un Sort inattendu apparaît.
Spark SQL — la même chose, en SQL
Le DataFrame et Spark SQL sont deux façades sur le même moteur. Vous enregistrez un DataFrame comme vue temporaire, puis vous interrogez en SQL :
vols.createOrReplaceTempView("vols")
resultat = spark.sql("""
SELECT compagnie, AVG(retard_arrivee) AS retard_moyen
FROM vols
WHERE origine = 'CDG'
GROUP BY compagnie
ORDER BY retard_moyen DESC
""")
Le plan produit est identique, et le choix est purement une affaire de lisibilité et de compétences de l'équipe. Une équipe analyste préfère souvent le SQL ; une équipe de science des données préfère l'API DataFrame parce qu'elle s'enchaîne bien avec Spark ML au module 5.
Écrire en DataFrame ou en SQL par défaut. Ne descendre au RDD que si vous
manipulez du non-tabulaire, ou si vous avez une raison mesurée de sortir
du plan Catalyst. Un rdd.map(...) glissé dans une chaîne DataFrame casse
souvent la moitié des optimisations d'un coup.
En résumé
- Le RDD est bas niveau, sans schéma, opaque à l'optimiseur ; il survit pour du non-tabulaire ou du contrôle manuel.
- Le DataFrame est déclaratif, typé par un schéma, et bénéficie de l'optimiseur Catalyst ; c'est le défaut pour l'apprentissage automatique.
- Le Dataset est une API Scala typée qui n'existe pas en PySpark ; on passe.
- Spark SQL partage le moteur du DataFrame : lisibilité différente,
performances identiques ;
explainest le premier outil de diagnostic.
Module suivant : ce qui distingue transformations et actions, et pourquoi
l'évaluation paresseuse rend explain indispensable.