过滤然后合并

时间:2018-05-03 21:12:27

标签: apache-spark pyspark rdd data-partitioning

我正在pyspark中的(x,y)点的RDD上实现范围查询。我将xy空间划分为16 * 16网格(256个单元格),并将RDD中的每个点分配给其中一个单元格。 gridMappedRDD是PairRDD:(cell_id, Point object)

我使用以下命令将此RDD分区为256个分区:

gridMappedRDD.partitionBy(256)

范围查询是一个矩形框。我有一个Grid对象的方法,它可以返回与查询范围重叠的单元格id列表。因此,我使用它作为过滤器来修剪不相关的细胞:

filteredRDD = gridMappedRDD.filter(lambda x: x[0] in candidateCells)

但问题是,在运行查询然后收集结果时,将评估所有256个分区;为每个分区创建一个任务。

为了避免这个问题,我尝试将filteredRDD合并到candidateCell列表的长度,我希望这可以解决问题。

filteredRDD.coalesce(len(candidateCells))

实际上,生成的RDD具有len(candidateCells)个分区,但分区与gridMappedRDD不同。

正如coalesce文档中所述,shuffle参数为False,并且不应在分区之间执行shuffle,但我可以看到(在glom()的帮助下)情况并非如此。

例如,在coalesce(4) candidateCells=[62, 63, 78, 79]之后,分区是这样的:

[[(62, P), (62, P) .... , (63, P)],
 [(78, P), (78, P) .... , (79, P)],
 [], []
]

实际上,通过合并,我有一个随机读取,它等于我的每个任务的整个数据集的大小,这需要很长的时间。我需要的是一个RDD,只有与candidateCells中的单元格相关的分区,没有任何改组。 所以,我的问题是,是否有可能只过滤一些分区而不进行重新洗牌?对于上面的示例,我的filteredRDD将具有4个分区,其具有与原始RDD的62,63,78,79个分区完全相同的数据。这样做,可以将查询定向到仅影响分区。

1 个答案:

答案 0 :(得分:3)

你在这里做了一些不正确的假设:

  • 随机播放与coalesce无关(此处coalesce也不常用)。它是由partitionBy引起的。根据定义进行分区需要随机播放。
  • 分区不能用于优化filter。 Spark对你使用的功能一无所知(它是一个黑盒子)。
  • 分区不会将密钥唯一映射到分区。多个密钥可以放在同一个分区上 - How does HashPartitioner work?

你能做什么:

  • 如果生成的子集是小的重新分区,则为每个密钥应用lookup

    from itertools import chain
    
    partitionedRDD = gridMappedRDD.partitionBy(256)
    
    chain.from_iterable(
        ((c, x) for x in partitionedRDD.lookup(c)) 
        for c in candidateCells
    )
    
  • 如果数据很大,您可以尝试跳过扫描分区(赢得的任务数量不会改变,但某些任务可能会被短路):

    candidatePartitions = [
        partitionedRDD.partitioner.partitionFunc(c) for c in candidateCells
    ]
    
    partitionedRDD.mapPartitionsWithIndex(
        lambda i, xs: (x for x in xs if x[0] in candidateCells) if i in candidatePartitions else []
    )
    

这两种方法只有在执行多次"查找"时才有意义。如果是一次性操作,最好执行线性滤波器:

  • 它比洗牌和重新分区便宜。
  • 如果初始数据均匀分布在下游,处理将能够更好地利用可用资源。