我已在Spark用户论坛上发布了此问题,但未收到任何答复,因此请在此处再次提问。
我们有一个用例,需要进行笛卡尔连接,由于某种原因,我们无法使其与Dataset API一起使用。
我们有两个数据集:
- 一个具有2个字符串列的数据集,例如c1,c2。这是一个小型数据集,具有约100万条记录。两列都是32个字符的字符串,因此应小于500 mb。
我们广播了这个数据集
- 其他数据集稍大一点,大约有1000万条记录
val ds1 = spark.read.format("csv").option("header", "true").load(<s3-location>).select("c1", "c2")
ds1.count
val ds2 = spark.read.format("csv").load(<s3-location>).toDF("c11", "c12", "c13", "c14", "c15", "ts")
ds2.count
ds2.crossJoin(broadcast(ds1)).filter($"c1" <= $"c11" && $"c11" <= $"c2").count
如果我使用RDD api实现它,我在ds1中广播数据,然后在ds2中过滤数据,则效果很好。
我已经确认广播成功。
2019-02-14 23:11:55 INFO CodeGenerator:54-代码在10.469136 ms中生成 2019-02-14 23:11:55 INFO TorrentBroadcast:54-开始读取广播变量29 2019-02-14 23:11:55 INFO TorrentBroadcast:54-读取广播变量29需要6毫秒 2019-02-14 23:11:56 INFO CodeGenerator:54-代码在11.280087 ms中生成
查询计划:
==身体计划==
BroadcastNestedLoopJoin BuildRight,Cross,(((c1#68 <= c11#13)&&(c11#13 <= c2#69))
:-*项目[]
:+-*过滤器isnotnull(_c0#0)
:+-* FileScan csv [_c0#0,_c1#1,_c2#2,_c3#3,_c4#4,_c5#5]批处理:false,格式:CSV,位置:InMemoryFileIndex [],PartitionFilters:[], PushedFilters:[IsNotNull(_c0)],ReadSchema:struct <_c0:string,_c1:string,_c2:string,_c3:string,_c4:string,_c5:string>
+-BroadcastExchange IdentityBroadcastMode
+-*项目[c1#68,c2#69]
+-*过滤器(isnotnull(c1#68)&& isnotnull(c2#69))
+-* FileScan csv [c1#68,c2#69]批处理:false,格式:CSV,位置:InMemoryFileIndex [],PartitionFilters:[],PushedFilters:[IsNotNull(c1),IsNotNull(c2)],ReadSchema:结构
然后阶段就不会进行。
我更新了代码以使用广播ds1,然后在ds2的mapPartitions中加入了连接。
val ranges = spark.read.format("csv").option("header", "true").load(<s3-location>).select("c1", "c2").collect
val rangesBC = sc.broadcast(ranges)
然后在mapPartitions方法中使用此rangeBC来确定ds2中每一行所属的范围,此作业将在3小时内完成,而其他作业甚至在24小时后也不会完成。这种暗示意味着查询优化器没有按照我想要的去做。
我在做什么错?任何指针都会有所帮助。谢谢!
答案 0 :(得分:1)
我不知道您是使用裸机还是使用具有现货,按需或专用的AWS或具有AZURE等的VM。我的看法:
然后:
因此,要回答您的问题: -你没做错什么。
答案 1 :(得分:1)
我最近遇到了这个问题,发现当交叉连接大型数据帧时,Spark具有奇怪的分区行为。如果您的输入数据框包含几百万条记录,那么交叉连接的数据框的分区等于输入数据框分区的乘积,即
crossJoinDF的分区=(ds1的分区)*(ds2的分区)。
如果ds1或ds2包含大约几百个分区,则交叉连接数据帧的分区范围约为10,000。这些分区太多了,在管理许多小任务时会导致过多的开销,使得交叉连接数据帧上的任何计算(在您的情况下为-过滤器)都非常慢地运行。
那么您如何使计算更快?首先检查这是否确实是您遇到的问题:
scala> val crossJoinDF = ds2.crossJoin(ds1)
# This should return immediately because of spark lazy evaluation
scala> val crossJoinDFPartitions = crossJoinDF.rdd.partitions.size
检查交叉连接数据帧上的分区数。如果crossJoinDFPartitions> 10,000,则确实存在相同的问题,即交叉联接数据框的分区过多。
要使对交叉联接的数据框的操作更快,请减少输入数据框上的分区数。例如:
scala> val ds1 = ds1.repartition(40)
scala> ds1.rdd.partitions.size
res80: Int = 40
scala> val ds2 = ds2.repartition(40)
scala> ds2.rdd.partitions.size
res81: Int = 40
scala> val crossJoinDF = ds1.crossJoin(ds2)
scala> crossJoinDF.rdd.partitions.size
res82: Int = 1600
scala> crossJoinDF.count()
count()
操作应导致执行交叉连接。现在,计数应该在合理的时间内返回。您选择的确切分区数将取决于群集中可用的内核数。
这里的关键要点是确保交叉连接的数据框具有合理数量的分区(<< 10,000)。您可能还会发现this post有用,它可以更详细地说明此问题。