上下文
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!
让我补充一点,userid
中1 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.mapfiles
或hive.merge.mapredfiles
等选项合并map reduce作业后的所有小文件。 Spark中有这方面的选项吗?
答案 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
拉维
答案 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,就像我们可以解决不了一样。按分区划分分区,只要有可用资源,我们就可以限制创建的文件。