在Spark中加入倾斜的数据集?

时间:2016-11-02 06:18:20

标签: join apache-spark

我正在使用Spark RDD加入两个大数据集。一个数据集非常偏斜,因此很少有执行程序任务需要很长时间才能完成工作。我该如何解决这个问题?

6 个答案:

答案 0 :(得分:19)

关于如何做到的非常好的文章:https://datarus.wordpress.com/2015/05/04/fighting-the-skew-in-spark/

简短版本:

  • 将随机元素添加到大型RDD并使用它创建新的连接键
  • 使用explode / flatMap将随机元素添加到小型RDD以增加条目数并创建新的连接键
  • 在新的加入密钥上加入RDD,现在可以通过随机播种更好地分发

答案 1 :(得分:10)

根据您遇到的特定偏差类型,可能有不同的方法来解决它。基本思路是:

  • 修改您的加入列,或创建一个新的加入列,该列不会偏斜但仍保留足够的信息来进行加入
  • 在非倾斜列上进行连接 - 生成的分区不会倾斜
  • 在加入之后,您可以将加入列更新回您的首选格式,或者如果您创建了新列,则将其删除

如果偏斜的数据参与了连接,那么LiMuBei的回答中引用的“反对火花中的偏见”一文是一种很好的技巧。在我的例子中,偏差是由连接列中的大量空值引起的。空值没有参与连接,但是由于连接列上的Spark分区,后连接分区非常偏斜,因为有一个包含所有空值的巨大分区。

我通过添加一个新列来解决它,该列将所有空值更改为分布均匀的临时值,例如“NULL_VALUE_X”,其中X被替换为介于1和10,000之间的随机数,例如, (在Java中):

// Before the join, create a join column with well-distributed temporary values for null swids.  This column
// will be dropped after the join.  We need to do this so the post-join partitions will be well-distributed,
// and not have a giant partition with all null swids.
String swidWithDistributedNulls = "swid_with_distributed_nulls";
int numNullValues = 10000; // Just use a number that will always be bigger than number of partitions
Column swidWithDistributedNullsCol =
    when(csDataset.col(CS_COL_SWID).isNull(), functions.concat(
        functions.lit("NULL_SWID_"),
        functions.round(functions.rand().multiply(numNullValues)))
    )
    .otherwise(csDataset.col(CS_COL_SWID));
csDataset = csDataset.withColumn(swidWithDistributedNulls, swidWithDistributedNullsCol);

然后加入这个新列,然后加入:

outputDataset.drop(swidWithDistributedNullsCol);

答案 2 :(得分:9)

假设您必须在A.id = B.id上联接两个表A和B。假设表A在id = 1上有偏斜。

从A加入B的A.id = B.id

有两种基本方法可以解决倾斜连接问题:

方法1:

将查询/数据集分为两部分-一部分仅包含倾斜,而另一部分包含非倾斜的数据。 在上面的例子中。查询将变为-

 1. select A.id from A join B on A.id = B.id where A.id <> 1;
 2. select A.id from A join B on A.id = B.id where A.id = 1 and B.id = 1;

第一个查询不会有任何偏斜,因此ResultStage的所有任务将大致同时完成。

如果我们假设B只有很少的行且B.id = 1,那么它将适合内存。因此,第二查询将转换为广播联接。在Hive中,这也称为Map-side join。

参考:https://cwiki.apache.org/confluence/display/Hive/Skewed+Join+Optimization

然后可以将两个查询的部分结果合并以获得最终结果。

方法2:

以上在LeMuBei中也提到过,第二种方法试图通过附加额外的列来随机化连接键。 步骤:

  1. 在较大的表(A)中添加一列,例如skewLeft,并为所有行填充0到N-1之间的随机数。

  2. 在较小的表(B)中添加一列,例如skewRight。将较小的表复制N次。因此,对于每个原始数据副本,新的 skewRight 列中的值将在0到N-1之间变化。为此,您可以使用explode sql / dataset运算符。

在1和2之后,将2个数据集/表连接,连接条件更新为-

                *A.id = B.id && A.skewLeft = B.skewRight*

参考:https://datarus.wordpress.com/2015/05/04/fighting-the-skew-in-spark/

答案 3 :(得分:2)

Apache DataFu 有两种方法可以进行倾斜连接,它们实现了前面答案中的一些建议。

joinSkewed 方法进行加盐(添加随机数列以拆分偏斜值)。

broadcastJoinSkewed 方法适用于将数据帧划分为倾斜部分和规则部分的情况,如 Approach 2 的答案中的 moriarty007 所述。

DataFu 中的这些方法对于使用 Spark 2.x 的项目很有用。如果您已经在使用 Spark 3,则有 dedicated methods for doing skewed joins

完全披露 - 我是 Apache DataFu 的成员。

答案 4 :(得分:1)

https://datarus.wordpress.com/2015/05/04/fighting-the-skew-in-spark/获取参考 以下是使用Pyspark数据框API消除火花中的偏斜的代码

创建2个数据框:

from math import exp
from random import randint
from datetime import datetime

def count_elements(splitIndex, iterator):
    n = sum(1 for _ in iterator)
    yield (splitIndex, n)

def get_part_index(splitIndex, iterator):
    for it in iterator:
        yield (splitIndex, it)

num_parts = 18
# create the large skewed rdd
skewed_large_rdd = sc.parallelize(range(0,num_parts), num_parts).flatMap(lambda x: range(0, int(exp(x))))
skewed_large_rdd = skewed_large_rdd.mapPartitionsWithIndex(lambda ind, x: get_part_index(ind, x))

skewed_large_df = spark.createDataFrame(skewed_large_rdd,['x','y'])

small_rdd = sc.parallelize(range(0,num_parts), num_parts).map(lambda x: (x, x))

small_df = spark.createDataFrame(small_rdd,['a','b'])

对于大型df,将数据分为100个bin,对小型df进行100次复制

salt_bins = 100
from pyspark.sql import functions as F

skewed_transformed_df = skewed_large_df.withColumn('salt', (F.rand()*salt_bins).cast('int')).cache()

small_transformed_df = small_df.withColumn('replicate', F.array([F.lit(i) for i in range(salt_bins)]))

small_transformed_df = small_transformed_df.select('*', F.explode('replicate').alias('salt')).drop('replicate').cache()

最后,连接避免了偏斜

t0 = datetime.now()
result2 = skewed_transformed_df.join(small_transformed_df, (skewed_transformed_df['x'] == small_transformed_df['a']) & (skewed_transformed_df['salt'] == small_transformed_df['salt']) )
result2.count() 
print "The direct join takes %s"%(str(datetime.now() - t0))

答案 5 :(得分:-1)

您可以尝试将“倾斜的”RDD重新分区到更多分区,或尝试增加spark.sql.shuffle.partitions(默认情况下为200)。

在您的情况下,我会尝试将分区数设置为远高于执行程序的数量。