47. #DevoxxFR
Rôles du driver
• Gérer le cycle de vie des exécuteurs
• Gérer la répartition des données
• Effectuer le découpage job, stage, task
• Générer et affecter des tâches aux exécuteurs
23
48. #DevoxxFR
Rôles de l’exécuteur
• Executer les tâches demandées par le driver
• Maintenir un cache de bloc de données
• Gérer l’envoi et la reception des blocs de shuffle
24
58. #DevoxxFR
spark-submit
• Créer la configuration de l’application Spark adaptée
au cluster choisi
• Déploie les ressources vers le cluster
• Lance le driver sur la machine locale (client mode)
ou demande l’execution au cluster manager (cluster
mode)
27
60. #DevoxxFR
Tips: spark-submit sans jar local
spark-submit
--master yarn
--deploy-mode cluster
--packages my.fab:spark-job:1.2.2
--class my.fab.JobClass
dummy
28
61. #DevoxxFR
Tips: spark-submit sans jar local
spark-submit
--master yarn
--deploy-mode cluster
--packages my.fab:spark-job:1.2.2
--class my.fab.JobClass
dummy
...
151 WARN [main] DependencyUtils: Local
jar /Users/raphael/dummy does not exist,
skipping.
...
28
66. #DevoxxFR
Logging
• Log4j ¯_(ツ)_/¯
• Local à chaque machine, puis
consolidé sur HDFS ou S3 par YARN
• En mode client, le log driver n’est
pas consolidé !
33
67. #DevoxxFR
Logging Tips
• Modifier le template log4j.properties pour ajouter:
• le nom du thread courant
• un timestamp à la milliseconde
Exemple
%d{ISO8601} %p [%t] %c{1}: %m%n
34
73. #DevoxxFR
Infos dans Event Log
37
Configuration
Quantité de données échangées
Performance détaillée (wall-time et cpu-time) par Task
Plans d’execution
Métriques et accumulators
...
88. #DevoxxFR
C'est quoi ?
52
Catalyst:
Moteur d'optimisation des plans d'exécution des requêtes
Dataset & SQL
Tungsten:
Ensemble d'optimisation techniques notamment pour
réduire la pression mémoire et augmenter l'efficacité CPU
98. #DevoxxFR
Optimiseur Heuristique
57
Applique des règles heuristiques de transformation du graphe de
dépendance pour accélérer les traitements :
- PartitionPruning
- PredicatePushdown
- ProjectionPushdown
...
+ les règles de l'algèbre relationnel
111. #DevoxxFR
Optimiseur par coût
64
Permet d'utiliser les statistiques des sources de données
pour sélectionner le meilleur plan physique parmi tous les
plans possibles.
Principal impact sur les opérations "join"
Optimized
Plan
Physical
Plan Physical
Plan
Physical
Plan
Physical
Plan
Physical
Plan
Physical
Plan
Physical
Plan
Physical
Plan
Physical
Plan
112. #DevoxxFR
Optimiser par cout
65
Désactivé par défaut
spark.sql.cbo.enabled=true
spark.sql.cbo.joinReorder.enabled=true
Ne fonctionne que sur tables
Nécessite un calcul préalable des statistiques
ANALYZE TABLE mutable COMPUTE STATISTICS
ANALYZE TABLE mutable COMPUTE STATISTICS FOR COLUMNS col1, col2
113. #DevoxxFR
Dernière étape... codegen !
• Maximise l'efficacité de la structure
InternalRow utilisée pour stocker les
données en mémoire
• Evite le dispatch de méthodes
mégamorphiques
66
120. #DevoxxFR
Principes
• Nombre de Tasks d'un Stage est égal au nombre de
partitions RDD en fin de stage
• Donc 3 cas possibles:
• Input
• Shuffle
• Output
73
121. #DevoxxFR
Parallélisme d'Input
74
1 partition par split ou par fichier
Petites partitions concaténées pour être entre:
spark.sql.files.openCostInBytes (4M par défaut)
spark.sql.files.maxSplitBytes (128M par défaut)
124. #DevoxxFR
Exemple
77
200 Partitions sur le dernier stage
1 colonne de partition avec 20 valeurs
=> jusqu'à 200 * 20 fichiers résultats**
**multiplié par nombre de buckets si activé !
125. #DevoxxFR
Maîtriser le nombre de fichiers
78
Utiliser repartition pour regrouper les données en des groupes ayant
un sens
df.repartition('dpt)
.write
.partitionBy("dpt")
.parquet("my_dataset")
1 seul fichier de sortie par partition sur disque
127. #DevoxxFR
2 problèmes à résoudre
80
Déterminer le pool de ressource
optimal CPU/Mem pour l’application
Maximiser l’efficacité du découpage
entre exécutors
128. #DevoxxFR
Allocation pool
81
Multi-tenant:
Limité par répartition ressources cluster et queueing policy
YARN
Mono-tenant:
Limité par parallélisme des données et des stages
Allocation dynamique permet de simplifier cette gestion au
prix d’une latence de job un peu plus élevée
130. #DevoxxFR
Sizing executeurs
83
Moins d’exécuteurs:
- maximise les opportunités de partage de données intra-process
- augmente les temps de full-GC
- (HDFS) peut créer de la contention d’I/O
Plus d’exécuteurs:
- plus de shuffle
- plus simple à allouer par le cluster manager
131. #DevoxxFR
Heuristique
84
Maximum 6 cores par exécuteur
Affecter la taille mémoire pour maximiser l’utilisation du
noeud. 2 à 4G/core est une bonne base
Laisser des ressources à l'OS
Prendre en compte le YARN memoryOverhead (10% heap par
défaut)
Pour valider, mesurez les différences de performance
entre configurations !
132. #DevoxxFR
Exemple de sizing
85
m5.12xlarge - 48 cores - 192 GB RAM
1 core pour OS, ~5 à 10% RAM pour OS/Cache disque
-> YARN peut allouer au plus 47 cores et 180 G RAM
On prend 5 cores / executor -> 9 executors soit 20 GB total/
executor
Yarn memoryOverhead = 10 à 15% donc on fixe heap a 18G
—num-executors 9 —executor-cores 5 —executor-memory
18G
136. #DevoxxFR
2 types principaux types
de join
87
SortMergeJoin
Data 1 Data 2
Sort[key] Sort[key]
Merge
Data 3
HashJoin
Data 1 Data 2
Hash[key]
Merge
Data 3
137. #DevoxxFR
Cas d'utilisation
88
SortMergeJoin est le join par défaut mais coûteux dans le cas
général
1 shuffle + 2 sorts
Si Spark détecte qu'un des 2 datasets est "petit", il va utiliser
un BroadcastHashJoin à la place
Si Spark sait que les 2 datasets sont déjà partitionnés de
manière équivalentes (bucket), il va éviter le shuffle
138. #DevoxxFR
Bucketing
89
Activé uniquement pour les tables (donc via metastore), "pre
shuffle persistant" d'un dataset.
df.repartition(30,'year,'key)
.write
.partitionBy("year")
.bucketBy(30,"key")
.sortBy("key")
.saveAsTable("my_table")
Bucketing accélère group by, join et windowing sur les
colonnes concernées
139. #DevoxxFR
Skew et ré-équilibrage de charge
90
3 stratégies pour gérer le skew (déséquilibre de données):
- ajouter un sel pour mieux répartir les clés et agréger en 2
étapes
- avoir 2 traitements différents pour la clé dominante et le
reste (typiquement NULL handling)
- accepter le skew si le delta de temps n'est pas trop
important
146. #DevoxxFR
Spilling
94
Spilling: Quand la zone execution + storage est trop petite
pour tout accueillir, une partie de la mémoire est copiée sur
disque et libérée
Partie exécution n'est jamais spilled, toujours storage
Si le storage est non vide, il ne peut pas être spilled en dessous
du spark.memory.storageFraction
Spark a besoin de maxPartitionSize * executor-cores en
unified memory pour l'execution de toutes les tasks en
parallèle
147. #DevoxxFR
Gérer la pression sur la Heap
• Regarder les métriques !
• Ajuster le spark.memory.fraction
• Adapter le GC (plutôt G1GC ou ParallelGC)
• Réduire l'utilisation des UDF et opérations Dataset
• Diminuer le poids des structures de données
95
148. #DevoxxFR
Le cas du memoryOverhead
96
WARN TaskSetManager: Lost task 49.2 in stage
6.0 (TID xxx, xxx.xxx.xxx.compute.internal):
ExecutorLostFailure (executor 16 exited caused by
one of the running tasks) Reason: Container killed
by YARN for exceeding memory limits. 10.4 GB of
10.4 GB physical memory used. Consider boosting
spark.yarn.executor.memoryOverhead.
149. #DevoxxFR
Le cas du memoryOverhead
97
- JVM Natives pools (Metaspace, Compiler,
Threads stacks, DirectBuffers(nio), etc...)
- Python process (or R)
- Mémoire de libs JNI (xgboost, lightgbm, ...)
152. #DevoxxFR
Gérer le memoryOverhead
98
Limiter les DirectBuffers
spark.executor.extraJavaOptions=-XX:MaxDirectMemorySize=XXXM
ou
spark.shuffle.io.preferDirectBufs=false
Limiter et déclarer la RAM pour le process Python
spark.executor.pyspark.memory=XXXM
153. #DevoxxFR
Gérer le memoryOverhead
98
Limiter les DirectBuffers
spark.executor.extraJavaOptions=-XX:MaxDirectMemorySize=XXXM
ou
spark.shuffle.io.preferDirectBufs=false
Limiter et déclarer la RAM pour le process Python
spark.executor.pyspark.memory=XXXM
Augmenter le memory overhead
spark.executor.memoryOverhead=15% heap
154. #DevoxxFR
Gérer le memoryOverhead
98
Limiter les DirectBuffers
spark.executor.extraJavaOptions=-XX:MaxDirectMemorySize=XXXM
ou
spark.shuffle.io.preferDirectBufs=false
Limiter et déclarer la RAM pour le process Python
spark.executor.pyspark.memory=XXXM
Augmenter le memory overhead
spark.executor.memoryOverhead=15% heap
170. #DevoxxFR
Compression dans Spark
103
Spark peut lires les fichiers compressés directement mais ne
supporte pas .zip ou .7z
En interne, Spark utilise LZ4 par défaut pour la compression
des shuffles, spills et broadcast.
Pour améliorer les taux de compression:
spark.io.compression.lz4.blockSize=128k ou 256k
173. #DevoxxFR
Types d'instances
106
Les instances m.*, c.* ou r.* suffisent dans
la plupart des cas
Surveiller les IO et bande passante réseau
Des SSDs locaux sont utiles pour la
performance
174. #DevoxxFR
Stockage S3
107
Accès aux méta-données est très long
Pas d'opérations de move native
EMRFS peut introduire des bottlenecks
supplémentaires (chiffrement, cohérence via
dynamoDB)
183. #DevoxxFR
Bonnes pratiques de code
• Pas de traitement sur le driver
• Pensez utilisation ultérieure du dataset
(partition by, sort by, bucket by)
• Surveiller le nombre de fichiers d'output
• Maximiser le parallélisme des traitements
116
184. #DevoxxFR
Les limites
• Shuffle est la limite principale de montée en
charge et difficulté de paramétrage
• Efficacité du cache Spark est limitée
• Cost-based Optimizer pas encore universel
117