pyspark:有效地进行分区通过写入与原始表相同数量的总分区 [英] pyspark: Efficiently have partitionBy write to same number of total partitions as original table

查看:440
本文介绍了pyspark:有效地进行分区通过写入与原始表相同数量的总分区的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个与pyspark的repartitionBy()函数相关的问题,我最初在

I had a question that is related to pyspark's repartitionBy() function which I originally posted in a comment on this question. I was asked to post it as a separate question, so here it is:

我知道df.partitionBy(COL)会将每个值为COL的所有行写入其各自的文件夹,并且每个文件夹(假设这些行先前是通过其他某个键分布在所有分区上的)将大致具有与整个表中以前的文件数相同.我觉得这种行为很烦人.如果我有一个包含500个分区的大表,并且在某些属性列上使用partitionBy(COL),则现在有100个文件夹,每个 包含500个(现在很小)文件.

I understand that df.partitionBy(COL) will write all the rows with each value of COL to their own folder, and that each folder will (assuming the rows were previously distributed across all the partitions by some other key) have roughly the same number of files as were previously in the entire table. I find this behavior annoying. If I have a large table with 500 partitions, and I use partitionBy(COL) on some attribute columns, I now have for example 100 folders which each contain 500 (now very small) files.

我想要的是partitionBy(COL)行为,但是文件大小和文件数量与我原来的大致相同.

What I would like is the partitionBy(COL) behavior, but with roughly the same file size and number of files as I had originally.

作为演示,上一个问题共享一个玩具示例,其中您有一个包含10个分区的表并执行partitionBy(dayOfWeek),现在您有70个文件,因为每个文件夹中有10个文件.我想要约10个文件,每天一个,或者可能需要2到3个每天有更多数据的文件.

As demonstration, the previous question shares a toy example where you have a table with 10 partitions and do partitionBy(dayOfWeek) and now you have 70 files because there are 10 in each folder. I would want ~10 files, one for each day, and maybe 2 or 3 for days that have more data.

这可以轻松完成吗?像df.write().repartition(COL).partitionBy(COL)这样的东西似乎可能有用,但是我担心(在一个非常大的表中,该表将被划分为多个文件夹)必须先将其合并到少数几个分区 before 进行partitionBy(COL)似乎是个坏主意.

Can this be easily accomplished? Something like df.write().repartition(COL).partitionBy(COL) seems like it might work, but I worry that (in the case of a very large table which is about to be partitioned into many folders) having to first combine it to some small number of partitions before doing the partitionBy(COL) seems like a bad idea.

任何建议都将不胜感激!

Any suggestions are greatly appreciated!

推荐答案

您有几种选择.在下面的代码中,我假设您要用实木复合地板书写,但是您当然可以更改它.

You've got several options. In my code below I'll assume you want to write in parquet, but of course you can change that.

这将首先使用基于哈希的分区,以确保有限数量的COL值进入每个分区.根据您为numPartitions选择的值,某些分区可能是空的,而另一些分区可能会挤满值-对于不确定原因的人,请阅读

This will first use hash-based partitioning to ensure that a limited number of values from COL make their way into each partition. Depending on the value you choose for numPartitions, some partitions may be empty while others may be crowded with values -- for anyone not sure why, read this. Then, when you call partitionBy on the DataFrameWriter, each unique value in each partition will be placed in its own individual file.

警告:这种方法可能导致分区大小偏斜和任务执行时间偏斜.当您的列中的值与许多行相关联时(例如,城市列-新建文件),就会发生这种情况约克市可能有很多行),而其他值则较少(例如,小城镇的值).

Warning: this approach can lead to lopsided partition sizes and lopsided task execution times. This happens when values in your column are associated with many rows (e.g., a city column -- the file for New York City might have lots of rows), whereas other values are less numerous (e.g., values for small towns).

当您希望(1)所写文件的大小几乎相等(2)完全控制所写文件的数量时,此选项非常有用.此方法首先对数据进行全局排序,然后找到将数据分解为均匀大小的k分区的拆分,其中在火花配置spark.sql.shuffle.partitions中指定了k.这意味着排序键具有相同值的所有值都彼此相邻,但有时它们会跨越一个拆分,并位于不同的文件中.这样,如果您的用例要求所有具有相同键的行都位于同一分区中,则不要使用这种方法.

This options works great when you want (1) the files you write to be of nearly equal sizes (2) exact control over the number of files written. This approach first globally sorts your data and then finds splits that break up the data into k evenly-sized partitions, where k is specified in the spark config spark.sql.shuffle.partitions. This means that all values with the same values of your sort key are adjacent to each other, but sometimes they'll span a split, and be in different files. This, if your use-case requires all rows with the same key to be in the same partition, then don't use this approach.

还有两个额外的好处:(1)通过对数据进行排序,通常可以减少其在磁盘上的大小(例如,按user_id对所有事件进行排序,然后按时间排序将导致列值重复很多,这有助于压缩)和(2)如果您以支持的格式写入文件格式(例如Parquet),则后续读取器可以通过谓词下推来最佳地读取数据,因为Parquet编写器将在元数据中写入每一列的MAX和MIN值,如果查询指定的值超出分区的(最小,最大)范围,则允许读者跳过行.

There are two extra bonuses: (1) by sorting your data its size on disk can often be reduced (e.g., sorting all events by user_id and then by time will lead to lots of repetition in column values, which aids compression) and (2) if you write to a file format the supports it (like Parquet) then subsequent readers can read data in optimally by using predicate push-down, because the parquet writer will write the MAX and MIN values of each column in the metadata, allowing the reader to skip rows if the query specifies values outside of the partition's (min, max) range.

请注意,在Spark中进行排序比重新分区要贵得多,并且需要额外的步骤.在后台,Spark将首先在一个阶段中确定拆分,然后在另一阶段将数据重新整理为这些拆分.

Note that sorting in Spark is more expensive than just repartitioning and requires an extra stage. Behind the scenes Spark will first determine the splits in one stage, and then shuffle the data into those splits in another stage.

如果在Scala上使用spark,则可以编写一个客户分区程序,该程序可以克服基于散列的分区程序令人讨厌的麻烦.不幸的是,这不是pySpark中的选项.如果您真的想在pySpark中编写自定义分区程序,我发现可以使用rdd.repartitionAndSortWithinPartitions:

If you're using spark on Scala, then you can write a customer partitioner, which can get over the annoying gotchas of the hash-based partitioner. Not an option in pySpark, unfortunately. If you really want to write a custom partitioner in pySpark, I've found this is possible, albeit a bit awkward, by using rdd.repartitionAndSortWithinPartitions:

df.rdd \
  .keyBy(sort_key_function) \  # Convert to key-value pairs
  .repartitionAndSortWithinPartitions(numPartitions=N_WRITE_PARTITIONS, 
                                      partitionFunc=part_func) \
  .values() # get rid of keys \
.toDF().write.parquet(writePath)

也许其他人知道一种在pyspark的数据帧上使用自定义分区程序的简便方法吗?

Maybe someone else knows an easier way to use a custom partitioner on a dataframe in pyspark?

这篇关于pyspark:有效地进行分区通过写入与原始表相同数量的总分区的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆