web-dev-qa-db-fra.com

Spark Java.lang.OutOfMemoryError: espace de pile Java

Mon cluster: 1 maître, 11 esclaves, chaque nœud a 6 Go de mémoire.

Mes paramètres:

spark.executor.memory=4g, Dspark.akka.frameSize=512

Voici le problème:

Premièrement , je lis des données (2,19 Go) de HDFS à RDD:

val imageBundleRDD = sc.newAPIHadoopFile(...)

Deuxièmement , faites quelque chose sur ce RDD:

val res = imageBundleRDD.map(data => {
                               val desPoints = threeDReconstruction(data._2, bg)
                                 (data._1, desPoints)
                             })

Last , sortie sur HDFS:

res.saveAsNewAPIHadoopFile(...)

Quand je lance mon programme, il montre:

.....
14/01/15 21:42:27 INFO cluster.ClusterTaskSetManager: Starting task 1.0:24 as TID 33 on executor 9: Salve7.Hadoop (NODE_LOCAL)
14/01/15 21:42:27 INFO cluster.ClusterTaskSetManager: Serialized task 1.0:24 as 30618515 bytes in 210 ms
14/01/15 21:42:27 INFO cluster.ClusterTaskSetManager: Starting task 1.0:36 as TID 34 on executor 2: Salve11.Hadoop (NODE_LOCAL)
14/01/15 21:42:28 INFO cluster.ClusterTaskSetManager: Serialized task 1.0:36 as 30618515 bytes in 449 ms
14/01/15 21:42:28 INFO cluster.ClusterTaskSetManager: Starting task 1.0:32 as TID 35 on executor 7: Salve4.Hadoop (NODE_LOCAL)
Uncaught error from thread [spark-akka.actor.default-dispatcher-3] shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[spark]
Java.lang.OutOfMemoryError: Java heap space

Il y a trop de tâches?

PS: Tout va bien lorsque les données d'entrée sont d'environ 225 Mo. 

Comment puis-je résoudre ce problème?

176
hequn8128

J'ai quelques suggestions:

  • Si vos nœuds sont configurés avec un maximum de 6 g pour Spark (et en laissent un peu pour d'autres processus), utilisez 6 g plutôt que 4 g, spark.executor.memory=6g. Assurez-vous que vous utilisez le plus de mémoire possible en vérifiant l'interface utilisateur (elle indiquera la quantité de mémoire utilisée)
  • Essayez d’utiliser plus de partitions, vous devriez en avoir 2 à 4 par CPU. Augmenter le nombre de partitions IME est souvent le moyen le plus simple de rendre un programme plus stable (et souvent plus rapide). Pour des quantités énormes de données dont vous aurez peut-être besoin de plus de 4 par processeur, j'ai dû utiliser 8 000 partitions dans certains cas!
  • Diminuez la fraction de mémoire réservée pour la mise en cache, à l'aide de spark.storage.memoryFraction. Si vous n'utilisez pas cache() ou persist dans votre code, il peut également s'agir de 0. Sa valeur par défaut est 0,6, ce qui signifie que vous n'obtenez que 0.4 * 4g de mémoire pour votre segment de mémoire. IME réduisant le mem frac fait souvent disparaître les MOO. UPDATE: À partir de spark 1.6, apparemment, nous n'aurons plus besoin de jouer avec ces valeurs, spark les déterminera automatiquement.
  • Semblable à ce qui précède, mais fraction de mémoire shuffle. Si votre travail n'a pas besoin de beaucoup de mémoire vive, réglez-le sur une valeur plus faible (cela pourrait faire en sorte que votre lecture aléatoire se répande sur le disque, ce qui peut avoir un impact catastrophique sur la vitesse). Parfois, quand il s’agit d’une opération aléatoire, vous devez faire l’opération inverse, c’est-à-dire la définir sur une valeur trop grande, telle que 0.8, ou vous assurer que vos analyses aléatoires se répandent sur le disque (il s’agit de la valeur par défaut depuis 1.0.0).
  • Faites attention aux fuites de mémoire, elles sont souvent causées par la fermeture accidentelle d'objets dont vous n'avez pas besoin dans vos lambdas. La méthode de diagnostic consiste à rechercher la "tâche sérialisée sous forme d'octets XXX" dans les journaux. Si XXX est supérieur à quelques Ko ou plus à un Mo, vous risquez de rencontrer une fuite de mémoire. Voir https://stackoverflow.com/a/25270600/1586965
  • Relatif à ci-dessus; utilisez broadcast variables si vous avez vraiment besoin d'objets volumineux.
  • Si vous mettez en cache des RDD volumineux et que vous pouvez perdre du temps d'accès, envisagez de sérialiser le RDD http://spark.Apache.org/docs/latest/tuning.html#serialized-rdd-storage . Ou même les mettre en cache sur le disque (ce qui n'est parfois pas si grave si vous utilisez des disques SSD).
  • (Advanced) En relation avec ce qui précède, évitez les structures String et fortement imbriquées (telles que les classes Map et les cas imbriqués). Si possible, essayez d'utiliser uniquement des types primitifs et d'indexer toutes les non-primitives, en particulier si vous attendez beaucoup de doublons. Choisissez WrappedArray plutôt que des structures imbriquées. Ou même déployez votre propre sérialisation - VOUS aurez le plus d’informations sur la manière de sauvegarder efficacement vos données en octets, USE IT!
  • (bit hacky) Lors de la mise en cache, envisagez d'utiliser une variable Dataset pour mettre en cache votre structure car elle utilisera une sérialisation plus efficace. Cela devrait être considéré comme un bidouillage par rapport au point précédent. Construire votre connaissance de domaine dans votre algo/sérialisation peut réduire la mémoire/espace de cache de 100x ou 1000x, alors que tout ce que Dataset donnera sera 2x - 5x en mémoire et 10x compressé (parquet) sur disque.

http://spark.Apache.org/docs/1.2.1/configuration.html

EDIT: (pour que je puisse moi-même google plus facilement) Ce qui suit est aussi révélateur de ce problème:

Java.lang.OutOfMemoryError : GC overhead limit exceeded
292
samthebest

Pour ajouter un cas d'utilisation à cela qui n'est souvent pas abordé, je vais proposer une solution lors de la soumission d'une application Spark via spark-submit en mode local.

D'après le gitbook Mastering Apache Spark de Jacek Laskowski :

Vous pouvez exécuter Spark en mode local. Dans ce mode de déploiement à une seule machine virtuelle Java non distribuée, Spark génère tous les composants d'exécution (pilote, exécuteur, serveur et maître) dans la même machine virtuelle. C'est le seul mode dans lequel un pilote est utilisé pour l'exécution.

Ainsi, si vous rencontrez des erreurs OOM avec heap, il suffit d’ajuster le driver-memory plutôt que le executor-memory.

Voici un exemple:

spark-1.6.1/bin/spark-submit
  --class "MyClass"
  --driver-memory 12g
  --master local[*] 
  target/scala-2.10/simple-project_2.10-1.0.jar 
45
Brian Vanover

Vous devriez augmenter la mémoire du pilote. Dans votre dossier $ SPARK_HOME/conf, vous devriez trouver le fichier spark-defaults.conf, éditer et définir le spark.driver.memory 4000m en fonction de la mémoire de votre maître, je pense .

14
blueskin

Regardez les scripts de démarrage une taille de segment de mémoire Java est définie ici.

# Set SPARK_MEM if it isn't already set since we also use it for this process
SPARK_MEM=${SPARK_MEM:-512m}
export SPARK_MEM

# Set Java_OPTS to be able to load native libraries and to set heap size
Java_OPTS="$OUR_Java_OPTS"
Java_OPTS="$Java_OPTS -Djava.library.path=$SPARK_LIBRARY_PATH"
Java_OPTS="$Java_OPTS -Xms$SPARK_MEM -Xmx$SPARK_MEM"

Vous pouvez trouver la documentation pour déployer des scripts ici .

14
Tombart

Vous devez configurer les paramètres de mémoire offHeap comme indiqué ci-dessous:

`val spark = SparkSession
     .builder()
     .master("local[*]")
     .config("spark.executor.memory", "70g")
     .config("spark.driver.memory", "50g")
     .config("spark.memory.offHeap.enabled",true)
     .config("spark.memory.offHeap.size","16g")   
     .appName("sampleCodeForReference")
     .getOrCreate()`

Donnez à la mémoire du pilote et de la mémoire de l'exécuteur la disponibilité de vos machines RAM. Vous pouvez augmenter la taille offHeap si vous êtes toujours confronté au problème OutofMemory .

6
pavan.vn101

De manière générale, la mémoire JVM spark Executor peut être divisée en deux parties. Mémoire Spark et mémoire utilisateur. Ceci est contrôlé par la propriété spark.memory.fraction - la valeur est comprise entre 0 et 1. Lorsque vous travaillez avec des images ou effectuez un traitement gourmand en mémoire dans les applications spark, envisagez de réduire le spark.memory.fraction. Cela rendra plus de mémoire disponible pour votre travail d'application. Spark peut se répandre, donc il fonctionnera toujours avec moins de partage de mémoire. 

La deuxième partie du problème est la division du travail. Si possible, partitionnez vos données en morceaux plus petits. De plus petites données nécessitent éventuellement moins de mémoire. Mais si cela n’est pas possible, vous devez faire des sacrifices pour la mémoire. Généralement, un seul exécutant exécutera plusieurs cœurs. La mémoire totale des exécutants doit être suffisante pour gérer les besoins en mémoire de toutes les tâches simultanées. Si l'augmentation de la mémoire de l'exécutant n'est pas une option, vous pouvez réduire le nombre de cœurs par exécuteur afin que chaque tâche utilise davantage de mémoire. Testez avec 1 exécuteur principal qui dispose de la plus grande mémoire possible, puis continuez à augmenter le nombre de cœurs jusqu’à ce que vous trouviez le meilleur nombre de cœurs. 

3
Rohit Karlupia

L'emplacement dans lequel définir la taille de la mémoire mémoire (au moins dans spark-1.0.0) se trouve dans conf/spark-env . Les variables appropriées sont SPARK_EXECUTOR_MEMORY & SPARK_DRIVER_MEMORY. D'autres documents sont disponibles dans guide de déploiement

De plus, n'oubliez pas de copier le fichier de configuration sur tous les nœuds esclaves.

2
Amnon

Avez-vous vidé votre journal maître gc? J'ai donc rencontré un problème similaire et j'ai découvert que SPARK_DRIVER_MEMORY ne définissait que le tas Xmx. La taille de segment initiale reste 1G et la taille de segment n'augmente jamais jusqu'au segment Xmx.

Passer "--conf" spark.driver.extraJavaOptions = -Xms20g "résout mon problème.

ps aux | grep Java et vous verrez le journal suivant: =

24501 30.7 1.7 41782944 2318184 pts/0 Sl + 18:49 0:33/usr/Java/dernières/bin/Java -cp/opt/spark/conf /:/opt/spark/jars/* - Xmx30g -Xms20g

2
Yunzhao Yang

J'ai beaucoup souffert de ce problème, nous utilisons l'allocation de ressources dynamique et je pensais qu'il utiliserait les ressources de mon cluster pour s'adapter au mieux à l'application.

Mais la vérité est que l’allocation dynamique des ressources ne définit pas la mémoire du pilote et la maintient à sa valeur par défaut, 1g.

Je l'ai résolu en définissant spark.driver.memory sur un nombre qui convient à la mémoire de mon pilote (pour 32 Go de RAM, je l'ai réglé à 18 Go).

vous pouvez le définir en utilisant la commande spark submit comme suit: 

spark-submit --conf spark.driver.memory=18gb ....cont

Note très importante, cette propriété ne sera pas prise en compte si vous la définissez à partir du code, selon la documentation de spark: 

Les propriétés Spark peuvent principalement être divisées en deux types: l'une liée au déploiement, comme «spark.driver.memory», «spark.executor.instances», ce type de propriétés peut ne pas être affecté lors de la définition par programme de SparkConf à l'exécution Le comportement dépend du gestionnaire de grappe et du mode de déploiement que vous choisissez. Il est donc suggéré de définir les options via un fichier de configuration ou une ligne de commande spark-submit; une autre est principalement liée au contrôle d'exécution Spark, comme «spark.task.maxFailures», ce type de propriétés peut être défini de l'une ou l'autre manière.

1
Abdulhafeth Sartawi

J'ai peu de suggestions pour l'erreur mentionnée ci-dessus.

● Vérifiez que la mémoire de l'exécuteur attribuée en tant qu'exécuteur peut avoir à traiter avec des partitions nécessitant plus de mémoire que celle affectée.

● Essayez de voir s'il y a plus de shuffles en direct, ce qui coûte cher, car ils impliquent des E/S de disque, une sérialisation des données et des E/S réseau 

● Utiliser les jointures de diffusion 

● Évitez d’utiliser GroupByKeys et essayez de le remplacer par RéductionByKey. 

● Évitez d'utiliser d'énormes objets Java chaque fois que vous mélangez 

0
Unmesha SreeVeni