web-dev-qa-db-fra.com

Pourquoi Spark saveAsTable avec bucketBy crée-t-il des milliers de fichiers?

Contexte

Spark 2.0.1, spark-submit en mode cluster. Je lis un fichier parquet de hdfs:

val spark = SparkSession.builder
      .appName("myApp")
      .config("Hive.metastore.uris", "thrift://XXX.XXX.net:9083")
      .config("spark.sql.sources.bucketing.enabled", true)
      .enableHiveSupport()
      .getOrCreate()

val df = spark.read
              .format("parquet")
              .load("hdfs://XXX.XX.X.XX/myParquetFile")

J'enregistre le df dans une table Hive avec 50 compartiments regroupés par userid:

df0.write
   .bucketBy(50, "userid")
   .saveAsTable("myHiveTable")

Maintenant, quand je regarde dans l'entrepôt Hive sur mes hdfs /user/Hive/warehouse il y a un dossier nommé myHiveTable. À l'intérieur, il y a un tas de part-*.parquet des dossiers. Je m'attendrais à ce qu'il y ait 50 fichiers. Mais non, il y a 3201 fichiers !!!! Il y a 64 fichiers par partition, pourquoi? Il existe un nombre différent de fichiers par partition pour les différents fichiers que j'ai enregistrés en tant que table Hive. Tous les fichiers sont très petits, seulement des dizaines de Kb chacun!

Permettez-moi d'ajouter que ce nombre de userid différents est d'environ 1 000 000 dans myParquetFile.

Question

Pourquoi y a-t-il 3201 fichiers dans le dossier au lieu de 50! Que sont-ils?

Lorsque je relis ce tableau dans DataFrame et imprime le nombre de partitions:

val df2 = spark.sql("SELECT * FROM myHiveTable") 
println(df2.rdd.getNumPartitions)

Le nombre de partitions estIl est correctement de 50 et j'ai confirmé que les données sont correctement partitionnées par userid.

Pour l'un de mes grands ensembles de données 3 To, je crée une table avec 1000 partitions qui a créé littéralement ~ millions de fichiers! Ce qui dépasse une limite d'élément de répertoire de 1048576 et donne org.Apache.hadoop.hdfs.protocol.FSLimitException$MaxDirectoryItemsExceededException

Question

De quoi dépend le nombre de fichiers créés?

Question

Existe-t-il un moyen de limiter le nombre de fichiers créés?

Question

Dois-je m'inquiéter de ces fichiers? Cela nuit-il aux performances sur df2 en ayant tous ces fichiers? On dit toujours qu'il ne faut pas créer trop de partitions car c'est problématique.

Question

J'ai trouvé cette information Hive Dynamic Partitioning tips que le nombre de fichiers pourrait être lié au nombre de mappeurs. Il est suggéré d'utiliser distribute by lors de l'insertion dans la table Hive. Comment pourrais-je le faire dans Spark?

Question

Si le problème est en effet comme dans le lien ci-dessus, ici Comment contrôler les numéros de fichier de la table Hive après avoir inséré des données sur MapR-FS ils suggèrent d'utiliser des options telles que Hive.merge.mapfiles ou Hive.merge.mapredfiles pour fusionner tous les petits fichiers après que la carte ait réduit le travail. Y a-t-il des options pour cela dans Spark?

15
astro_asz

Veuillez utiliser spark sql qui utilisera HiveContext pour écrire des données dans la table Hive, donc il utilisera le nombre de compartiments que vous avez configuré dans le schéma de table.

 SparkSession.builder().
  config("Hive.exec.dynamic.partition", "true").
  config("Hive.exec.dynamic.partition.mode", "nonstrict").
  config("Hive.execution.engine","tez").
  config("Hive.exec.max.dynamic.partitions","400").
  config("Hive.exec.max.dynamic.partitions.pernode","400").
  config("Hive.enforce.bucketing","true").
  config("optimize.sort.dynamic.partitionining","true").
  config("Hive.vectorized.execution.enabled","true").
  config("Hive.enforce.sorting","true").
  enableHiveSupport().getOrCreate()

spark.sql(s"insert into hiveTableName partition (partition_column) select * from  myParquetFile")

L'implémentation de bucketing de spark n'honore pas le nombre spécifié de taille de bucket. Chaque partition écrit dans un fichier séparé, vous vous retrouvez donc avec beaucoup de fichiers pour chaque bucket.

Veuillez vous référer à ce lien https://www.slideshare.net/databricks/Hive-bucketing-in-Apache-spark-with-tejas-patil

enter image description here J'espère que cela vous aidera.

Ravi

12
Ravikumar

J'ai pu trouver une solution de contournement (sur Spark 2.1). Il résout le problème du nombre de fichiers mais pourrait avoir des implications en termes de performances.

dataframe
  .withColumn("bucket", pmod(hash($"bucketColumn"), lit(numBuckets)))
  .repartition(numBuckets, $"bucket")
  .write
  .format(fmt)
  .bucketBy(numBuckets, "bucketColumn")
  .sortBy("bucketColumn")
  .option("path", "/path/to/your/table")
  .saveAsTable("table_name")

Je pense que l'algorithme de bucketing de spark fait un mod positif de MurmurHash3 de la valeur de la colonne de bucket. Cela reproduit simplement cette logique et repartitionne les données afin que chaque partition contienne toutes les données d'un compartiment.

Vous pouvez faire de même avec le partitionnement + le regroupement.

dataframe
  .withColumn("bucket", pmod(hash($"bucketColumn"), lit(numBuckets)))
  .repartition(numBuckets, $"partitionColumn", $"bucket")
  .write
  .format(fmt)
  .partitionBy("partitionColumn")
  .bucketBy(numBuckets, "bucketColumn")
  .sortBy("bucketColumn")
  .option("path", "/path/to/your/table")
  .saveAsTable("table_name")

Testé avec 3 partitions et 5 compartiments localement en utilisant le format csv (les colonnes de partition et de compartiment ne sont que des nombres):

$ tree .
.
├── _SUCCESS
├── partitionColumn=0
│   ├── bucket=0
│   │   └── part-00004-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00000.csv
│   ├── bucket=1
│   │   └── part-00003-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00001.csv
│   ├── bucket=2
│   │   └── part-00002-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00002.csv
│   ├── bucket=3
│   │   └── part-00004-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00003.csv
│   └── bucket=4
│       └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00004.csv
├── partitionColumn=1
│   ├── bucket=0
│   │   └── part-00002-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00000.csv
│   ├── bucket=1
│   │   └── part-00004-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00001.csv
│   ├── bucket=2
│   │   └── part-00002-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00002.csv
│   ├── bucket=3
│   │   └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00003.csv
│   └── bucket=4
│       └── part-00003-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00004.csv
└── partitionColumn=2
    ├── bucket=0
    │   └── part-00000-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00000.csv
    ├── bucket=1
    │   └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00001.csv
    ├── bucket=2
    │   └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00002.csv
    ├── bucket=3
    │   └── part-00003-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00003.csv
    └── bucket=4
        └── part-00000-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00004.csv

Voici le bucket = 0 pour les 3 partitions (vous pouvez voir que ce sont toutes les mêmes valeurs):

$ paste partitionColumn=0/bucket=0/part-00004-5f860e5c-f2c2-4d52-8035-aa00e4432770_00000.csv partitionColumn=1/bucket=0/part-00002-5f860e5c-f2c2-4d52-8035-aa00e4432770_00000.csv partitionColumn=2/bucket=0/part-00000-5f860e5c-f2c2-4d52-8035-aa00e4432770_00000.csv | head
0   0   0
4   4   4
6   6   6
16  16  16
18  18  18
20  20  20
26  26  26
27  27  27
29  29  29
32  32  32

J'ai en fait aimé l'index supplémentaire du compartiment. Mais si vous ne le faites pas, vous pouvez supprimer la colonne de compartiment juste avant l'écriture et vous obtiendrez le nombre de fichiers numBuckets par partition.

10
Bill Kuang