Aller au contenu principal

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.

Un seul principe pour choisir

É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 ; explain est le premier outil de diagnostic.

Module suivant : ce qui distingue transformations et actions, et pourquoi l'évaluation paresseuse rend explain indispensable.