Les tutoriels écrivent tantôt `sc.parallelize`, tantôt `spark.read` sans dire ce que sont ces objets. La différence, l’historique, et lequel utiliser.
Vous ouvrez un spark-shell, vous suivez un tutoriel, et deux objets apparaissent sans avoir jamais été présentés :
sc.parallelize(1 to 100) // d'où vient ce sc ?
spark.read.parquet("/data") // et ce spark ?Personne ne les a déclarés. Ils fonctionnent quand même. Et selon le tutoriel que vous lisez, c’est l’un ou l’autre — parfois sqlContext, s’il date un peu. Voici ce que chacun est, pourquoi il y en a plusieurs, et lequel écrire aujourd’hui.
| Objet | Nom complet | Sert à |
|---|---|---|
sc | SparkContext | L’API RDD : parallelize, textFile, accumulateurs, broadcast |
spark | SparkSession | L’API DataFrame / SQL : read, sql, createDataFrame, conf |
SparkSession est le point d’entrée moderne. Il contient un SparkContext, accessible par spark.sparkContext. Autrement dit : spark est la porte principale, sc est une porte de service qui existe toujours et qui reste nécessaire pour l’API RDD.
Cette confusion n’est pas un défaut de conception, c’est une strate géologique.
Spark 1.0 (2014) — seul SparkContext existe. Spark ne connaît que les RDD.
val conf = new SparkConf().setAppName("mon-job")
val sc = new SparkContext(conf)Spark 1.3 à 1.6 — les DataFrames arrivent, avec leur propre point d’entrée. Et comme le support Hive est optionnel, il en apparaît un second :
val sqlContext = new SQLContext(sc) // DataFrames
val hiveContext = new HiveContext(sc) // DataFrames + HiveTrois objets à jongler, avec des capacités qui se chevauchent — la situation était réellement pénible.
Spark 2.0 (2016) — SparkSession unifie tout. SQLContext et HiveContext deviennent obsolètes, SparkContext reste sous le capot.
val spark = SparkSession.builder()
.appName("mon-job")
.getOrCreate()C’est pour cela qu’un tutoriel avec sqlContext.read n’est pas faux — il est juste écrit avant 2016.
C’est la raison pour laquelle personne ne les déclare dans les tutoriels : spark-shell, pyspark, Databricks, Zeppelin et Jupyter avec findspark les créent automatiquement au démarrage.
// spark-shell — rien à écrire, tout est prêt
spark // SparkSession
sc // SparkContext
spark.version // "4.0.0"En revanche, dans une application compilée et soumise avec spark-submit, c’est à vous de la créer :
import org.apache.spark.sql.SparkSession
object MonJob {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName("mon-job")
.getOrCreate()
// Et si vous avez besoin de l'API RDD :
val sc = spark.sparkContext
import spark.implicits._ // active .toDF(), .as[T], $"colonne"
// … votre traitement …
spark.stop()
}
}Trois détails de cet extrait valent la peine d’être signalés.
getOrCreate() plutôt que new. Il n’y a qu’un seul SparkContext par JVM. Tenter d’en créer un second lève une exception. getOrCreate() réutilise la session existante si elle est là, ce qui rend le code utilisable indifféremment en shell, en test et en production.
import spark.implicits._ est ce qui active .toDF(), .as[T] et la syntaxe $"colonne". Son absence produit une erreur de compilation déroutante quand on découvre Spark — value toDF is not a member of…. C’est l’un des trois écarts à connaître pour porter un exemple PySpark vers Scala.
spark.stop() libère les ressources. Sans lui, un job soumis en mode cluster peut laisser des exécuteurs alloués.
val sc2 = new SparkContext(conf)
// org.apache.spark.SparkException: Only one SparkContext should be runningLe shell en a déjà créé un. La contrainte est technique : un SparkContext détient les connexions aux exécuteurs, le gestionnaire de blocs et l’ordonnanceur — deux instances se disputeraient les mêmes ressources.
Si vous devez vraiment changer une configuration liée au contexte, arrêtez le premier :
sc.stop()
val sc2 = new SparkContext(nouvelleConf)Mais dans la plupart des cas, spark.conf.set(...) suffit, sans redémarrage.
// ---- SparkContext (sc) : tout ce qui est RDD ----
sc.parallelize(1 to 100) // créer un RDD depuis une collection
sc.textFile("etudiants.txt") // lire un fichier en RDD de lignes
sc.broadcast(petitDictionnaire) // variable diffusée aux exécuteurs
sc.longAccumulator("compteur") // accumulateur
sc.setCheckpointDir("hdfs:///tmp/chk") // dossier de checkpoint
sc.defaultParallelism // parallélisme par défaut
// ---- SparkSession (spark) : tout ce qui est DataFrame / SQL ----
spark.read.parquet("s3a://bucket/data") // lire en DataFrame
spark.sql("SELECT * FROM commandes") // requête SQL sur une vue enregistrée
spark.createDataFrame(donnees) // créer un DataFrame
spark.table("catalogue.schema.table") // lire une table du catalogue
spark.conf.set("spark.sql.shuffle.partitions", "800")
spark.catalog.listTables() // explorer le catalogueLa règle est mécanique : si le mot « RDD » apparaît dans ce que vous faites, c’est sc. Sinon, c’est spark.
Et puisque écrire du RDD est rarement le bon choix, vous utiliserez spark la grande majorité du temps. sc reste indispensable pour trois choses qui n’ont pas d’équivalent côté session : les variables diffusées, les accumulateurs, et le dossier de checkpoint.
spark.conf en cours de routeToutes les configurations ne s’appliquent pas au même moment, et c’est une source de bugs discrets :
// Fonctionne à tout moment — lu à chaque requête
spark.conf.set("spark.sql.shuffle.partitions", "800")
// Sans effet après création de la session — lu au démarrage
spark.conf.set("spark.executor.memory", "8g") // ignoréLes propriétés qui dimensionnent le cluster (spark.executor.memory, spark.executor.cores, spark.default.parallelism) sont lues à la création du SparkContext. Elles se posent au builder() ou en ligne de commande :
spark-submit \
--conf spark.executor.memory=8g \
--conf spark.default.parallelism=480 \
mon-job.jarCelles du moteur SQL (spark.sql.*) se règlent à chaud, requête par requête. La distinction recoupe celle expliquée dans defaultParallelism : combien de cœurs Spark utilise-t-il.
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("mon-job").getOrCreate()
sc = spark.sparkContext
rdd = sc.parallelize(range(100))
df = spark.read.parquet("s3a://bucket/data")Les noms, la hiérarchie et les rôles sont identiques — c’est une constante de l’API, quel que soit le langage.
sc est le SparkContext, la porte d’entrée historique et celle de l’API RDD. spark est la SparkSession, le point d’entrée unifié depuis Spark 2.0, qui couvre les DataFrames et SQL et qui contient le SparkContext — récupérable par spark.sparkContext quand vous en avez besoin.
En pratique : écrivez spark par défaut, descendez vers sc pour les RDD, les variables diffusées, les accumulateurs et les checkpoints. Et si vous croisez sqlContext dans un tutoriel, vous savez maintenant qu’il a plus de dix ans — spark fait tout ce qu’il faisait, en mieux.
Développement et déploiement de solutions de données — les premiers modules sont en accès libre.