TP 9 – Votre premier stream
Objectif : suivre en direct le chiffre d’affaires et le nombre de courses par arrondissement.
Avant de commencer
Section intitulée « Avant de commencer »Dans un terminal, à la racine du dépôt :
python scripts/stream_simulator.py courses --reset # en direct : un fichier toutes les 2 secondes# ou, si vous êtes en retard / en mode dégradé :python scripts/stream_simulator.py courses --all --reset # tout d'un coupLes fichiers apparaissent dans stream/courses : c’est notre « production ». Dans le notebook :
from pyspark.sql import SparkSession, functions as Fspark = SparkSession.builder.appName("TP").master("local[*]").getOrCreate()spark.sparkContext.setLogLevel("ERROR")spark.conf.set("spark.sql.shuffle.partitions", "4") # petit volume : 200 partitions par défaut, c'est trop1. Lire le dossier stream/courses en continu
Section intitulée « 1. Lire le dossier stream/courses en continu »Un stream exige un schéma explicite : prenez celui de data/trips.parquet (spark.read.parquet(...).schema).
Indices : spark.readStream.schema(schema).option("maxFilesPerTrigger", 2).json("stream/courses"), puis
isStreaming.
2. Jointure stream-statique avec les zones
Section intitulée « 2. Jointure stream-statique avec les zones »Le référentiel data/taxi_zone_lookup.csv est un DataFrame statique ordinaire. Ajoutez la colonne Borough.
3. Chiffre d’affaires et nombre de courses par Borough
Section intitulée « 3. Chiffre d’affaires et nombre de courses par Borough »groupBy("Borough").agg(count, sum(total_amount)).
4. Démarrer la requête
Section intitulée « 4. Démarrer la requête »outputMode("complete"), format("console"), trigger(processingTime="5 seconds"), un checkpointLocation.
Laissez tourner 60 secondes : q.awaitTermination(60), puis q.stop(). Les tableaux s’affichent sous la cellule, un
par micro-batch.
5. Le suivi : q.lastProgress, q.recentProgress
Section intitulée « 5. Le suivi : q.lastProgress, q.recentProgress »Combien de micro-batches ? Combien de lignes par micro-batch (numInputRows) ?
Bonus – Le même calcul en batch (spark.read) sur les mêmes fichiers
Section intitulée « Bonus – Le même calcul en batch (spark.read) sur les mêmes fichiers »Obtenez-vous les mêmes comptes ? Si le stream en a moins : comparez le rythme du simulateur (1 fichier toutes les
2 s) et maxFilesPerTrigger (2 fichiers par micro-batch de 5 s). Le stream a-t-il pris du retard ? Que se passe-t-il
si on le laisse tourner plus longtemps ?
Correction
Section intitulée « Correction »import shutilshutil.rmtree("out/checkpoints/tp9", ignore_errors=True) # repartir de zéro
# 1. Schéma explicite, repris des données statiquesschema = spark.read.parquet("data/trips.parquet").schemacourses = (spark.readStream .schema(schema) .option("maxFilesPerTrigger", 2) # 2 fichiers par micro-batch : on voit le flux avancer .json("stream/courses"))print("isStreaming :", courses.isStreaming) # True
# 2. Jointure stream-statiquezones = spark.read.csv("data/taxi_zone_lookup.csv", header=True, inferSchema=True)enrichies = courses.join(zones.select(F.col("LocationID").alias("PULocationID"), "Borough"), "PULocationID")
# 3. Agrégation continue depuis le début du fluxpar_borough = (enrichies.groupBy("Borough") .agg(F.count("*").alias("courses"), F.round(F.sum("total_amount"), 2).alias("ca")))
# 4. Démarrer : complete = toute la table résultat à chaque micro-batchq = (par_borough.writeStream .outputMode("complete") .format("console").option("truncate", False) .trigger(processingTime="5 seconds") .option("checkpointLocation", "out/checkpoints/tp9") .start())q.awaitTermination(60)q.stop()
# 5. Suivibatches = q.recentProgressprint(f"{len(batches)} micro-batches, {sum(p['numInputRows'] for p in batches)} courses traitées")print({k: batches[-1][k] for k in ("batchId", "numInputRows")})
# Bonus – le même calcul en batch(spark.read.schema(schema).json("stream/courses") .join(zones.select(F.col("LocationID").alias("PULocationID"), "Borough"), "PULocationID") .groupBy("Borough").agg(F.count("*").alias("courses")).orderBy(F.desc("courses")).show())Résultats attendus
Section intitulée « Résultats attendus »Avec le flux complet : Manhattan 11 880 courses, Queens 1 553… soit 14 992 courses en 15 micro-batches. Le batch sur les mêmes fichiers donne exactement les mêmes comptes : le streaming est un batch incrémental.
Pièges rencontrés en séance
Section intitulée « Pièges rencontrés en séance »| Symptôme | Cause | Solution |
|---|---|---|
Schema must be specified when creating a streaming source DataFrame | Schéma oublié | .schema(schema) repris de trips.parquet |
Rien ne s’affiche après un --reset | Le checkpoint a gardé l’ancien état et les fichiers déjà lus | Supprimer out/checkpoints/tp9 avant de relancer |
| Micro-batches lents (plusieurs secondes) | spark.sql.shuffle.partitions = 200 | Le passer à 4 |
| Le stream compte moins que le batch | 2 fichiers par micro-batch de 5 s contre 1 fichier toutes les 2 s : il prend du retard | Le laisser tourner : une fois le simulateur arrêté, il rattrape |
| Requête modifiée, erreur au redémarrage | Même checkpoint pour une requête différente | Nouveau checkpoint |
- Le batch donne-t-il le même résultat ? Oui : le streaming est un batch incrémental.
- Pourquoi un schéma explicite ? Spark ne peut pas inférer un schéma sur des fichiers qui n’existent pas encore.
- À retenir :
readStream/writeStream, un schéma, un output mode, un trigger, un checkpoint. Le reste est du batch.
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.