web-dev-qa-db-fra.com

Spark partitionnement de parquet: grand nombre de fichiers

J'essaie de tirer parti de spark partitionnement. J'essayais de faire quelque chose comme

data.write.partitionBy("key").parquet("/location")

Le problème ici, chaque partition crée un nombre énorme de fichiers de parquet, ce qui entraîne une lecture lente si je tente de lire à partir du répertoire racine.

Pour éviter que j'ai essayé

data.coalese(numPart).write.partitionBy("key").parquet("/location")

Cela crée toutefois un nombre numPart de fichiers de parquet dans chaque partition. Maintenant, ma taille de partition est différente. SO J'aimerais idéalement avoir une fusion distincte par partition. Toutefois, cela ne ressemble pas à une chose facile. Je dois visiter toutes les partitions se fusionner jusqu'à un certain nombre et les stocker dans un emplacement.

Comment utiliser le partitionnement pour éviter de nombreux fichiers après l'écriture?

26

Premièrement, j’éviterais vraiment d’utiliser coalesce, car cela est souvent poussé plus loin dans la chaîne de transformation et risque de détruire le parallélisme de votre travail (j’ai posé la question à ce sujet ici: Comment prévenir Spark optimisation )

Écrire un fichier par parquet est vraiment facile (voir Méthode d'écriture Spark Dataframe écrivant beaucoup de petits fichiers ):

data.repartition($"key").write.partitionBy("key").parquet("/location")

Si vous souhaitez définir un nombre arbitraire de fichiers (ou de fichiers ayant la même taille), vous devez repartitionner davantage vos données en utilisant un autre attribut qui pourrait être utilisé (je ne peux pas vous dire ce que cela pourrait être dans votre cas):

data.repartition($"key",$"another_key").write.partitionBy("key").parquet("/location")

another_key Pourrait être un autre attribut de votre ensemble de données, ou un attribut dérivé utilisant des opérations modulo ou d'arrondi sur des attributs existants. Vous pouvez même utiliser des fonctions de fenêtre avec row_number Sur key, puis arrondissez ceci avec quelque chose comme:

data.repartition($"key",floor($"row_number"/N)*N).write.partitionBy("key").parquet("/location")

Cela vous mettrait N enregistre dans 1 fichier de parquet

en utilisant orderBy

Vous pouvez également contrôler le nombre de fichiers sans repartitionner en commandant votre base de données en conséquence:

data.orderBy($"key").write.partitionBy("key").parquet("/location")

Cela donnera un total de spark.sql.shuffle.partitions Sur toutes les partitions (200 par défaut). Il est même avantageux d'ajouter une deuxième colonne de commande après $key, Car le parquet se souviendra de la commande du bloc de données et écrira les statistiques en conséquence. Par exemple, vous pouvez commander par identifiant:

data.orderBy($"key",$"id").write.partitionBy("key").parquet("/location")

Cela ne changera pas le nombre de fichiers, mais cela améliorera les performances lorsque vous interrogerez votre fichier de parquet sur un key et id donnés. Voir par exemple https://www.slideshare.net/RyanBlue3/parquet-performance-tuning-the-missing-guide et https://db-blog.web.cern.ch/blog/luca-canali/2017-06-plongee-et-plonge-et-bois-exemple de charge

Spark 2.2 +

À partir de Spark 2.2, vous pouvez également jouer avec la nouvelle option maxRecordsPerFile pour limiter le nombre d'enregistrements par fichier. Vous aurez toujours au moins N fichiers si vous avez N partitions, mais vous pouvez diviser le fichier écrit par 1 partition (tâche) en morceaux plus petits:

df.write
.option("maxRecordsPerFile", 10000)
...

Voir par exemple http://www.gatorsmile.io/anticipated-feature-in-spark-2-2-max-records-written-per-file/ et déclenche l'écriture sur le disque avec N fichiers moins que N partitions

38
Raphael Roth