如何通过Spark中其他DataFrame中的条件过滤DataFrame

时间:2018-12-05 08:02:04

标签: apache-spark

我有一个DataFrame source,并且想根据另一个称为blacklist的DataFrame中的条件过滤出条目。 source必须匹配至少一个blacklist条目才能被过滤掉。 blacklist中的列条件/条目由AND链接。 NULL中的blacklist值保留为通配符,这意味着相应的属性可以具有任何值以匹配条件。

一个简化的示例:

source

| id | age | color |
|----|-----|-------|
| 1  | 28  | blue  |
| 2  | 25  | blue  |
| 3  | 15  | red   |
| 4  | 20  | red   |
| 5  | 27  | green |
| 6  | 30  | green |

blacklist

| age  | color |
|------|-------|
| 25   | blue  |
| NULL | red   |
| 30   | NULL  |

output

| id | age | color |
|----|-----|-------|
| 1  | 28  | blue  |
| 5  | 27  | green |

相应的数据框:

val source = Seq((1, 28, "blue"), (2, 25, "blue"), (3, 15, "red"), (4, 20, "red"), (5, 27, "green"), (6, 30, "green")).toDF("id", "age", "color")
val blacklist = Seq((Some(25), Some("blue")), (None, Some("red")), (Some(30), None)).toDF("age", "color")

有关真实数据的更多信息:

  • 数据存储在Hive表中(ORC格式)
  • source包含100亿个条目
  • blacklist包含20万条5列的条目

我的方法(使用Spark 2.3):

val joinCondition = (source("age") <=> blacklist("age") || blacklist("age").isNull) && (source("color") <=> blacklist("color") || blacklist("color").isNull)
val dataToRemove = source.join(broadcast(blacklist), joinCondition).select(source("id"), source("age"), source("color"))                                                                                    
val output = source.except(dataToRemove)

问题与疑问

上面的代码段正常工作。但是,对于真正的大量数据,我在运行时间方面存在性能问题。您是否看到解决此黑名单问题的更好方法?

我还考虑在驱动程序中创建一个较大的过滤条件,然后执行source.filter(theBigCondition)。这样做的好处是不需要连接。但是,即使黑名单较小,我也遇到了有关Catalyst Optimizer的问题。

您有什么想法?

2 个答案:

答案 0 :(得分:1)

您加入广播的方法可能是解决此问题的最佳方法。

首先尝试了解哪个部分花费了这么长时间。 可能是这部分:

val joinedDf = data.join(broadcast(blacklist))

因此,我的第一个嫌疑犯将是10 B数据框中的偏斜数据。而且由于您的黑色DF很小,在这种情况下,“ Salty Join”将非常有用。

算法基础:

通过选择数字1-N来进行咸加入。比将较小DF中的每一行乘以N。对于N=3

黑名单之前:

| age  | color |
|------|-------|
| 25   | blue  |
| 30   | NULL  |

黑名单之后:

| salt | age  | color |
|------|------|-------|
|   1  | 25   | blue  |
|   2  | 25   | blue  |
|   3  | 25   | blue  |
|   1  | 25   | red   |
|   2  | 25   | red   |
|   3  | 25   | red   |

对于每行较大的DF,请在1-N之间添加一个随机数:

    | salt | age  | color |
    |------|------|-------|
    |   3  | 25   | blue  |
    |   2  | 27   | green |
    |   1  | 25   | blue  |
    |   3  | 45   | red   |

比添加盐列成为联接的一部分

saltedData.join(brodcast(saltedBlacklist), Seq("salt","age","color"))

现在我们可以看到,在大型DF中,我们有重复项(25,蓝色),但是由于它们的盐分不同,它们将分配给更多机器。

咸连接的想法是获得更大的熵。如果我们的联接列中的数据确实存在偏差,那么工作人员之间的分配将很差。通过添加盐分,我们可以使小df倍N的数据膨胀,但是通过在现在包含“ salt”列的新联接列中获得更好的熵,我们可以获得更好的分布。

答案 1 :(得分:0)

让我回答我自己的问题。

在本地测试中,我发现except非常昂贵。在source数据中添加一种标记,然后在该接缝处进行筛选以加快速度。

val blacklistWithFlag = blacklist.withColumn("remove", lit(true))
val markedSource = source.join(broadcast(blacklistWithFlag), joinCondition, "left_outer").select(source("id"), source("age"), source("color"), blacklistWithFlag("remove"))
val output = markedSource.filter(col("remove").isNull).drop("remove")

此方法仅需要1个阶段,而不需要上述4个阶段。