Beam / Dataflow-大的CoGroupByKey结果导致流水线变慢

时间:2018-12-05 16:21:41

标签: parallel-processing google-cloud-dataflow apache-beam

我有2个PCollection<KV<String, String>>,一个是〜150M,第二个是〜2B。

我想做的是计算两个PCollection中每个唯一值对的出现次数。

所以我在这两个PCollections上做了一个CoGroupByKey,问题是CoGbkResult中的一些(〜5M)非常大(我在Dataflow中收到日志消息说CoGbkResult具有超过10K的结果),因为在两个集合中每个密钥都可能出现多次,并且这会导致获取这些密钥的工作程序中的运行时间非常长。

理想情况下,我希望CoGroupByKey返回一个PCollection,其中包含由密钥共同分组的两个PCollection的所有值对,因此我无法以某种方式对它们进行计数并行化效果更好。

我一直在阅读有关此问题的信息,但似乎没有适合我的解决方案(大多数解决方案包括使用Combine.WithHotKeyFanout),因为在合并之前,我需要一个额外的映射步骤,这将永远花费时间,因为CoGbkResult的大小。 有什么建议可以解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

您是否可以重新格式化数据,以便将CoGroupByKey替换为CombinePerKey

CoGroupByKeyGroupByKey正在建立所有匹配项的列表,这些列表可能会变得很大,但是您只关心计数正确吗?因此,您可以将CombinePerKeyCombineFn一起使用,以对它们的出现进行计数

通过以下方式重新格式化您的PCollections:

pcoll_a = [('abc','123'), ('abc', '456'), ...]
pcoll_b = [('abc','123'), ('xyz', '456'), ...]

变成这样:

pcoll_a = [('abc,123', 'A'), ('abc,456', 'A'), ...]
pcoll_b = [('abc,123', 'B'), ('xyz,456', 'B'), ...]

将这两个PCollection放在一起:

pcoll_combined = [('abc,123', 'A'), ('abc,456', 'A'), ('abc,123', 'B'), ('xyz,456', 'B'), ...]

将此数字与CombinePerKey一起传递到CombineFn,该数字将随您的需求加总。像这样:

class CountFn(apache_beam.core.CombineFn):
    def _add_inputs(self, elements, accumulator=None):
        accumulator = accumulator or self.create_accumulator()
        for obj in elements:
            if obj == 'A':
                accumulator['sum_A'] += 1
            if obj == 'B':
                accumulator['sum_B'] += 1
        return accumulator

    def create_accumulator(self):
        return {'sum_A': 0, 'sum_B': 0}

    def add_input(self, accumulator, element, *args, **kwargs):
        return self._add_inputs(elements=[element], accumulator=accumulator)

    def add_inputs(self, accumulator, elements, *args, **kwargs):
        return self._add_inputs(elements=elements, accumulator=accumulator)

    def merge_accumulators(self, accumulators, *args, **kwargs):
        return {
            'sum_A': sum([i['sum_A'] for i in accumulators]),
            'sum_B': sum([i['sum_B'] for i in accumulators])}

    def extract_output(self, accumulator, *args, **kwargs):
        return accumulator