TP 10 – Les logs en temps réel
L’application de réservation écrit ses logs dans stream/logs/. Une panne survient quelque part dans la matinée.
Objectif : la détecter en temps réel.
Avant de commencer
Section intitulée « Avant de commencer »python scripts/stream_simulator.py logs --reset # en direct : un fichier toutes les 2 secondes# ou, si vous êtes en retard / en mode dégradé :python scripts/stream_simulator.py logs --all --reset # tout d'un coupExemple de ligne de log :
2024-01-15T08:22:03.120 ERROR endpoint=/payment status=503 latency_ms=812 zone=1611. Lire les logs en continu
Section intitulée « 1. Lire les logs en continu »readStream.text("stream/logs") : une colonne value par ligne. Avec .option("maxFilesPerTrigger", 3) pour avoir
plusieurs micro-batches, même avec --all.
2. Parser avec regexp_extract
Section intitulée « 2. Parser avec regexp_extract »Motif fourni :
motif = r"^(\S+) +(\w+) +endpoint=(\S+) status=(\d+) latency_ms=(\d+) zone=(\d+)$"Colonnes : ts (to_timestamp(..., "yyyy-MM-dd'T'HH:mm:ss.SSS")), niveau, endpoint, status (int),
latence_ms (int).
3. Watermark de 2 minutes, fenêtre de 1 minute par endpoint
Section intitulée « 3. Watermark de 2 minutes, fenêtre de 1 minute par endpoint »Dans agg : requetes (count), erreurs (sum(when(status >= 500, 1).otherwise(0))), latence_p95
(percentile_approx(latence_ms, 0.95)) ; puis taux_erreur = erreurs / requetes avec withColumn.
4. Écrire en mode append vers Parquet (out/tp10/par_minute), avec un checkpoint
Section intitulée « 4. Écrire en mode append vers Parquet (out/tp10/par_minute), avec un checkpoint »En direct : trigger(processingTime="5 seconds") et awaitTermination(120). Avec --all :
trigger(availableNow=True). Supprimez out/checkpoints/tp10 et out/tp10 avant de démarrer.
5. Lire le Parquet (en batch) et afficher les alertes : taux d’erreur > 10 %
Section intitulée « 5. Lire le Parquet (en batch) et afficher les alertes : taux d’erreur > 10 % »Quel endpoint ? À quelles minutes ? Que devient la latence p95 ?
6. Combien de lignes ont été ignorées par le watermark ?
Section intitulée « 6. Combien de lignes ont été ignorées par le watermark ? »Indice : q.recentProgress, champ stateOperators[0]["numRowsDroppedByWatermark"]. Pourquoi les toutes dernières
minutes n’apparaissent-elles pas dans le Parquet ?
Bonus – Fenêtre glissante de 5 minutes, toutes les minutes
Section intitulée « Bonus – Fenêtre glissante de 5 minutes, toutes les minutes »L’alerte est-elle plus stable ?
Correction
Section intitulée « Correction »import shutilfrom pyspark.sql import functions as Fspark.conf.set("spark.sql.shuffle.partitions", "4")for d in ["out/checkpoints/tp10", "out/tp10"]: shutil.rmtree(d, ignore_errors=True)
# 1. Une ligne de texte par événementbrut = spark.readStream.option("maxFilesPerTrigger", 3).text("stream/logs")
# 2. Parsermotif = r"^(\S+) +(\w+) +endpoint=(\S+) status=(\d+) latency_ms=(\d+) zone=(\d+)$"logs = brut.select( F.to_timestamp(F.regexp_extract("value", motif, 1), "yyyy-MM-dd'T'HH:mm:ss.SSS").alias("ts"), F.regexp_extract("value", motif, 2).alias("niveau"), F.regexp_extract("value", motif, 3).alias("endpoint"), F.regexp_extract("value", motif, 4).cast("int").alias("status"), F.regexp_extract("value", motif, 5).cast("int").alias("latence_ms"),)
# 3. Fenêtre de 1 minute sur le temps de l'ÉVÉNEMENT, watermark de 2 minutespar_minute = (logs .withWatermark("ts", "2 minutes") .groupBy(F.window("ts", "1 minute"), "endpoint") .agg(F.count("*").alias("requetes"), F.sum(F.when(F.col("status") >= 500, 1).otherwise(0)).alias("erreurs"), F.round(F.expr("percentile_approx(latence_ms, 0.95)"), 0).alias("latence_p95")) .withColumn("taux_erreur", F.round(F.col("erreurs") / F.col("requetes"), 3)))
# 4. append vers Parquet : une fenêtre n'est écrite qu'UNE fois, quand le watermark l'a closeq = (par_minute.writeStream .outputMode("append") .format("parquet") .option("path", "out/tp10/par_minute") .option("checkpointLocation", "out/checkpoints/tp10") .trigger(processingTime="5 seconds") # ou trigger(availableNow=True) avec --all .start())q.awaitTermination(120)q.stop()
# 5. Lire en batch et détecter l'incidentres = spark.read.parquet("out/tp10/par_minute")(res.filter("endpoint = '/payment'") .select(F.date_format("window.start", "HH:mm").alias("minute"), "requetes", "erreurs", "taux_erreur", "latence_p95") .orderBy("minute").show(40))res.filter("taux_erreur > 0.10") \ .select(F.date_format("window.start", "HH:mm").alias("minute"), "endpoint", "taux_erreur").orderBy("minute").show()
# 6. Lignes ignorées par le watermarkmicro_batchs = q.recentProgress # un dictionnaire de métriques par micro-batchignorees = sum(int(mb["stateOperators"][0].get("numRowsDroppedByWatermark", 0)) for mb in micro_batchs if mb["stateOperators"])print("Lignes ignorées car trop en retard (> 2 min) :", ignorees)Résultats attendus
Section intitulée « Résultats attendus »| Mesure | Valeur |
|---|---|
| Endpoint en panne | /payment |
| Minutes en alerte | 8h22 à 8h25 |
Taux d’erreur de /payment | de ≈ 1 % à 29–38 % |
| Latence p95 | de ≈ 230 ms à ≈ 1 500 ms |
| Lignes ignorées par le watermark | 50 |
| Fenêtres absentes du Parquet | 8h27–8h29 : pas encore closes en append |
# Bonus – fenêtres glissantes : 5 minutes, toutes les minutes (chaque événement compte dans 5 fenêtres)glissant = (spark.read.text("stream/logs") .select(F.to_timestamp(F.regexp_extract("value", motif, 1), "yyyy-MM-dd'T'HH:mm:ss.SSS").alias("ts"), F.regexp_extract("value", motif, 4).cast("int").alias("status")) .groupBy(F.window("ts", "5 minutes", "1 minute")) .agg(F.round(F.avg((F.col("status") >= 500).cast("int")), 3).alias("taux_erreur_5min")) .orderBy("window"))La fenêtre glissante lisse le signal : l’alerte monte plus progressivement, mais résiste mieux aux pics isolés.
Pièges rencontrés en séance
Section intitulée « Pièges rencontrés en séance »| Symptôme | Cause | Solution |
|---|---|---|
Append output mode not supported when there are streaming aggregations without watermark | Watermark oublié | withWatermark("ts", "2 minutes") avant le groupBy |
ts est null partout | Format de date | yyyy-MM-dd'T'HH:mm:ss.SSS (le T entre quotes) |
| Rien dans le Parquet | Aucune fenêtre encore close | Attendre les micro-batches suivants ; vérifier numRowsDroppedByWatermark |
| Watermark affiché à 13h27 | UTC | 8h27 à New York |
| Fenêtre de 7h52 | Lignes très en retard au début du flux | Normal |
- Un participant présente son alerte et sa courbe.
- Pourquoi 8h27–8h29 n’apparaissent-elles pas ? Fenêtres pas encore closes.
- Watermark de 2 min ou de 10 min pour une alerte ? Court pour la réactivité ; long ou batch de rattrapage pour l’exhaustivité.
- À retenir : le watermark arbitre entre réactivité et exhaustivité.
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.