En mission chez un client, je suis tombé sur cette erreur aussi mystérieuse que complexe à analyser en faisant tourner un job SPARK sur du PARQUET : org.apache.hadoop.hdfs.BlockMissingException: Could not obtain block: ... Voici la solution (et donc comment gagner un mois).

Le contexte est le suivant : j'ai un job spark, réparti, qui consiste à manipuler BEAUCOUP de fichiers PARQUET, et à en écrire une version modifiée sur le même cluster. Donc, en entrée comme en sortie, du PARQUET, et au milieu, du SPARK. De manière aléatoire, ceci revient :

 org.apache.hadoop.hdfs.BlockMissingException: Could not obtain block: BP-... file= ...
	at org.apache.hadoop.hdfs.DFSInputStream.chooseDataNode(DFSInputStream.java )
	at org.apache.hadoop.hdfs.DFSInputStream.blockSeekTo(DFSInputStream.java )
	at org.apache.hadoop.hdfs.DFSInputStream.readWithStrategy(DFSInputStream.java )
	at org.apache.hadoop.hdfs.DFSInputStream.read(DFSInputStream.java )
	at org.apache.hadoop.hdfs.DFSInputStream.read(DFSInputStream.java )
	at java.io.FilterInputStream.read(FilterInputStream.java )

A l'analyse, on apprend que : ça ne dépend pas du fichier lu, mais en général ça arrive à une même période dans l'exécution. Ca ne dépend pas non plus de l'exécuteur qui lève l'erreur. Si on répartit nos datasets avec des valeurs proches de 1, ça empire le phénomène. On a le problème sur des fichiers parquet qui sont lus.

La solution est en fait déroutante de facilité : c'est que parquet est un format compressé, qu'il va donc utiliser les ressources du working node pour créer des fichiers temporaires, et donc ouvrir des fichiers. Qui dit ouvrir des fichiers dit augmenter le nombre de file descriptors. Et un ulimit nous donne une hard limit et une soft limit, en l'occurence, assez faibles : 2^16. La cause première était simple : l'usage de parquet impliquait qu'on ouvrait sur le même noeud trop de fichiers, donc on avait une erreur système qui ne permettait plus d'accéder aux fichiers locaux, donc aux blocs pour HDFS. Comme notre cluster était calme (merci l'été), on avait l'erreur à peu près au même moment, puisque le nombre de file descriptors ouverts suivait à peu près la même logique. Comme les noeuds ont tous la même configuration pour les ulimit, on ne dépendait pas du noeud. Comme on forçait la répartition avec des valeurs proches de 1, on avait beaucoup d'IO sur le même noeud, donc beaucoup plus de malchances d'avoir la même erreur.

Par contre, cela a une conséquence majeure : nous avons tenté un retry : quand on a une erreur de blocs, on attend un peu, et on recommence l'opération. Le temps d'attente est de 500ms + un nombre aléatoire de millisecondes prises entre 0 et 1000. Et c'est pire : si on va dans le code source de notre version d'HDFS, dans la classe org.apache.hadoop.hdfs.DFSInputStream spécifiquement, on constate que le code va blacklister des noeuds qui ne répondent pas sur une demande de bloc. Conséquence : les autres noeuds vont être encore plus sollicités. Et comme les ulimit sont les mêmes chez tout ce petit monde, les noeuds sollicités vont tomber, donc être blacklistés, donc... Exactement, un effet domino, qui provoque peu à peu l'exclusion de tous les noeuds pour le job SPARK.

 

Comments are closed.

Set your Twitter account name in your settings to use the TwitterBar Section.