为什么Spark saveAsTable with bucketBy创建了数千个文件?

时间:2018-02-02 15:20:55

标签: apache-spark hive

上下文

Spark 2.0.1,群集模式下的spark-submit。我正在从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")

我将df保存到一个包含50个按userid分组的存储桶的hive表中:

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

现在,当我查看hdfs /user/hive/warehouse的hive仓库时,有一个名为myHiveTable的文件夹。里面是一堆part-*.parquet个文件。我希望有50个文件。但不,有 3201 文件!!!!每个分区有64个文件,为什么?对于我保存为hive表的不同文件,每个分区有不同数量的文件。所有的文件都非常小,每个只有几十Kb!

让我补充一点,userid1 000 000的{​​{1}}数量大约为myParquetFile

问题

为什么文件夹中有3201个文件而不是50个!这些是什么?

当我将此表读回DataFrame并打印分区数时:

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

分区数isIt正确为50,我确认数据已被userid正确分区。

对于我的一个大型数据集3Tb,我创建了一个包含1000个分区的表,这些分区创建了大约数百万个文件!这超出了目录项限制1048576并给出了org.apache.hadoop.hdfs.protocol.FSLimitException$MaxDirectoryItemsExceededException

问题

创建的文件数量取决于什么?

问题

有没有办法限制创建的文件数量?

问题

我应该担心这些文件吗?拥有所有这些文件会对df2的性能造成影响吗?总是说我们不应该创建太多分区,因为它有问题。

问题

我发现此信息HIVE Dynamic Partitioning tips,文件数可能与地图集的数量有关。建议在插入hive表时使用distribute by。我怎么能在Spark中做到这一点?

问题

如果问题确实与上面的链接相同,那么How to control the file numbers of hive table after inserting data on MapR-FS他们建议使用hive.merge.mapfileshive.merge.mapredfiles等选项合并map reduce作业后的所有小文件。 Spark中有这方面的选项吗?

3 个答案:

答案 0 :(得分:9)

请使用spark sql,它将使用HiveContext将数据写入Hive表,因此它将使用您在表模式中配置的桶数。

 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")

spark的分段实现并不符合指定数量的桶大小。每个分区都写入一个单独的文件,因此每个桶最终会有大量文件。

请参阅此链接https://www.slideshare.net/databricks/hive-bucketing-in-apache-spark-with-tejas-patil

enter image description here 希望这会有所帮助。

拉​​维

答案 1 :(得分:8)

我能够找到解决方法(在Spark 2.1上)。它解决了文件数量问题,但可能会影响性能。

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")

我认为spark的存储区算法对存储区列值的MurmurHash3做一个正调制。这只是复制该逻辑并重新分区数据,以便每个分区都包含存储桶的所有数据。

您可以对分区+存储桶进行同样的操作。

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")

使用csv格式在本地对3个分区和5个存储桶进行了测试(分区列和存储桶列都只是数字):

$ 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

这是所有3个分区的存储桶= 0(您可以看到它们都是相同的值):

$ 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

我实际上很喜欢额外的存储桶索引。但是,如果不这样做,则可以在写入之前将bucket列删除,这样您将获得每个分区的numBuckets文件数。

答案 2 :(得分:2)

在我看来,当我看到太多文件进行搜索并找到此文件时,还会弹出这些问题

与Apache Hive中的存储区不同,Spark SQL按存储区和分区数创建存储区文件。换句话说,存储区文件数是存储区数乘以任务编写器数(一个每个分区)。

来源:https://jaceklaskowski.gitbooks.io/mastering-spark-sql/spark-sql-bucketing.html

我认为这可以回答您的问题,为什么不可以。文件数量

您的问题号。可以回答2,就像我们可以解决不了一样。按分区划分分区,只要有可用资源,我们就可以限制创建的文件。