RDD, List Scala ou table SQL : ce que « distribué » change

Un RDD n’est pas une table, ni une List plus grosse. Ce que le distribué retire (index, évaluation immédiate), et en quoi Spark SQL diffère du SQL.

7 min de lecturesparkscalarddspark-sqlbig-data

Deux questions revenables à chaque premier contact avec Spark : « un RDD, c’est comme créer une table ? » et « les structures normales sont sur une seule machine, c’est ça ? ». Les deux touchent au même point, et c’est le seul concept qu’il faut vraiment intégrer avant d’écrire du code : une collection distribuée n’est pas une collection locale plus grosse. C’est un objet d’une autre nature, avec des capacités en moins et des capacités en plus.

Cet article pose ce modèle mental, puis étend la même question à SQL : en quoi Spark SQL diffère-t-il du SQL que vous connaissez ?

Une List Scala : tout dans une mémoire

scala
val nombres = List(1, 2, 3, 4, 5)

Cette liste vit dans le tas d’une seule JVM, sur une seule machine. Ce qui en découle :

scala
nombres(2)          // 3 — accès direct, immédiat
nombres.length      // 5 — connu instantanément
nombres.map(_ * 2)  // calculé tout de suite

Accès par index, taille immédiate, évaluation immédiate. Et une limite dure : si les données dépassent la mémoire de la machine, c’est OutOfMemoryError. Pas de plan B.

List, Array, Map, Set — toutes les collections Scala standard fonctionnent ainsi. Elles sont locales par conception.

Un RDD : le même contenu, une autre nature

scala
val rdd = sc.parallelize(List(1, 2, 3, 4, 5), 2)

Les mêmes cinq nombres, mais découpés en partitions réparties sur les exécuteurs du cluster :

List Scala                 RDD Spark
┌──────────────┐          ┌─────────┐  ┌─────────┐
│ 1 2 3 4 5    │          │  1 2 3  │  │  4 5    │
│ une mémoire  │          │ nœud A  │  │ nœud B  │
└──────────────┘          └─────────┘  └─────────┘

Ce qui change immédiatement, et qui surprend :

scala
rdd(2)              // ✗ n'existe pas — pas d'accès par index
rdd.count()         // déclenche un JOB sur le cluster
rdd.map(_ * 2)      // ne calcule RIEN pour l'instant

Pas d’accès par index. Aucun nœud ne sait ce que contiennent les autres. Demander « l’élément numéro 2 » supposerait de savoir où il vit — l’information n’existe nulle part de façon bon marché. Pour prendre quelques éléments, on utilise take(n) ou first(), qui lisent une partition et s’arrêtent.

Le comptage est un calcul. count() lance une tâche par partition, agrège les résultats et vous les renvoie. Sur une List, la taille est un champ ; sur un RDD, c’est un job distribué.

Rien ne s’exécute avant une action. map, filter, flatMap construisent un plan. Le calcul démarre au premier collect(), count() ou write — c’est l’évaluation paresseuse, et c’est ce qui permet à Spark d’optimiser l’ensemble avant de commencer.

Ce que vous gagnez en échange

CapacitéListRDD
Accès par indexOuiNon
Taille instantanéeOuiNon (job)
ÉvaluationImmédiateParesseuse
Volume maximumMémoire d’une machineMémoire du cluster + disque
ParallélismeManuel (threads)Automatique
Tolérance aux pannesAucuneRecalcul via la lignée

Le dernier point est le plus remarquable et le plus méconnu. Un RDD garde la recette qui l’a produit — sa lignée. Si un exécuteur meurt, Spark recalcule uniquement les partitions perdues à partir de cette recette. Aucune collection locale n’offre ça.

Non, un RDD n’est pas une table

C’est la seconde confusion, et la réponse est nette : un RDD n’a ni schéma, ni colonnes, ni types nommés. C’est une collection distribuée d’objets quelconques.

scala
// Un RDD de String — aucune notion de colonne
sc.parallelize(List("Ali,15", "Sara,12"))

// Un RDD de tuples — Spark ne sait pas que le premier élément est un prénom
sc.parallelize(List(("Ali", 15), ("Sara", 12)))

Dans le second cas, vous savez que _1 est un prénom et _2 une note. Spark n’en sait rien. Il voit des tuples opaques, et il ne peut donc rien optimiser sur leur contenu.

L’objet qui ressemble vraiment à une table, c’est le DataFrame :

scala
import spark.implicits._

val df = Seq(("Ali", 15), ("Sara", 12)).toDF("prenom", "note")

df.printSchema()
// root
//  |-- prenom: string (nullable = true)
//  |-- note: integer (nullable = false)

Lignes, colonnes nommées, types déclarés, nullabilité. Et surtout : parce que Spark connaît cette structure, l’optimiseur Catalyst peut réécrire vos requêtes. C’est ce qui rend le DataFrame presque toujours préférable au RDD.

Le résumé en une ligne :

Vous cherchezUtilisez
Une collection distribuée d’objetsRDD
Une table avec colonnes et schémaDataFrame
Une table typée à la compilationDataset[T]

Spark SQL vs SQL classique

Puisque le DataFrame est une table, on peut l’interroger en SQL :

scala
df.createOrReplaceTempView("etudiants")

spark.sql("""
  SELECT prenom, AVG(note) AS moyenne
  FROM etudiants
  GROUP BY prenom
  ORDER BY moyenne DESC
""").show()

Le même SQL que vous connaissez. Attention toutefois à ce que createOrReplaceTempView fait exactement : il enregistre un nom, pas des données — les portées, la durée de vie et le piège du préfixe global_temp sont traités dans les vues temporaires Spark.

Alors quelle est la différence avec PostgreSQL ou MySQL ?

La vraie différence n’est pas « une machine contre plusieurs »

C’est la nuance qu’il faut poser tout de suite, parce que la version simplifiée est fausse : les SGBD modernes savent aussi travailler en cluster (Citus, CockroachDB, Aurora, Greenplum, Snowflake, BigQuery). Le nombre de machines n’est pas le critère.

La différence est une question de cible de conception :

SGBD transactionnel (OLTP)Spark SQL
Optimisé pourRequêtes ponctuelles, très rapidesBalayages de gros volumes
Latence typiqueMillisecondesSecondes à minutes
IndexCentraux (B-tree)Aucun — élagage de partitions et statistiques
Transactions ACIDCœur du produitVia Delta Lake / Iceberg seulement
UPDATE / DELETE par ligneNaturelCoûteux ou impossible
StockagePropriétaire, coupléFichiers sur un lac (Parquet, S3)
Cible d’usageServir une applicationAnalyser, transformer, alimenter

Concrètement : demander à Spark SQL SELECT * FROM commandes WHERE id = 84213 est une mauvaise idée — il n’a pas d’index, il balaiera les fichiers. Demander à PostgreSQL d’agréger huit milliards de lignes en scannant 4 To est également une mauvaise idée. Ce sont deux outils pour deux formes de question.

Un autre écart structurel : Spark SQL travaille sur des fichiers que vous possédez, pas sur un stockage interne. La même table Parquet est lisible par Spark, Trino, DuckDB, pandas ou Athena. C’est la promesse du lac de données — et la raison pour laquelle le format d’écriture compte tant.

Les trois écritures sont équivalentes
scala
// API DataFrame
df.groupBy("prenom").agg(avg("note").as("moyenne"))

// SQL
spark.sql("SELECT prenom, AVG(note) AS moyenne FROM etudiants GROUP BY prenom")

// Expression SQL dans l'API
df.selectExpr("prenom", "note").groupBy("prenom").agg(expr("avg(note) as moyenne"))

Les trois passent par le même Catalyst et produisent le même plan physique. Vérifiez-le avec .explain(true) : les plans sont identiques. Choisissez selon la lisibilité — le SQL est souvent plus clair pour les agrégations complexes, l’API DataFrame pour les transformations enchaînées et le code paramétrable.

Le modèle mental complet

Ce qu’il faut retenir

Passer au distribué, c’est accepter un échange : vous perdez l’accès par index et l’évaluation immédiate, vous gagnez un volume qui n’est plus limité par une machine, le parallélisme automatique et la tolérance aux pannes par recalcul. Un RDD est une collection distribuée d’objets ; un DataFrame est une table distribuée avec un schéma — et c’est ce schéma qui débloque l’optimiseur.

Quant à Spark SQL, ce n’est pas « du SQL sur plusieurs machines » : c’est du SQL conçu pour balayer de gros volumes plutôt que pour répondre en millisecondes à une requête ponctuelle. Les deux mondes ne se remplacent pas ; ils occupent deux places différentes dans une architecture, et savoir laquelle est laquelle évite les deux erreurs symétriques les plus coûteuses.

Ce sujet fait partie d’un cours complet

Développement et déploiement de solutions de données — les premiers modules sont en accès libre.

Voir le plan du cours

Continuer sur le même sujet