Google cloud dataflow 数据流批处理作业卡在GroupByKey.create()中

Google cloud dataflow 数据流批处理作业卡在GroupByKey.create()中,google-cloud-dataflow,Google Cloud Dataflow,我有一个批处理数据流管道,我在数据子集上运行了很多次,没有问题,大约有150k行输入。我现在尝试在大约3亿行的完整数据集上运行。管道的一个关键部分执行输入记录的GroupByKey,从而产生(我相信)大约100M个键 管道的相关部分如下所示: // Results in map of ~10k elements, 160MB in size PCollectionView<Map<String, Iterable<Z>>> sideData = ... ..

我有一个批处理数据流管道,我在数据子集上运行了很多次,没有问题,大约有150k行输入。我现在尝试在大约3亿行的完整数据集上运行。管道的一个关键部分执行输入记录的GroupByKey,从而产生(我相信)大约100M个键

管道的相关部分如下所示:

// Results in map of ~10k elements, 160MB in size
PCollectionView<Map<String, Iterable<Z>>> sideData = ...

...

.apply(ParDo.named("Group into KV").of(
    new DoFn<T, KV<String, T>>() { ... }
))
.apply("GBK", GroupByKey.create())
.apply(ParDo.named("Merge Values").withSideInputs(sideData).of(
    new DoFn<KV<String, Iterable<T>>, V>() { ... }
))
private static class MyDoFn extends KV<String, Iterable<T>>, V> {

  private static final Logger LOG =
    LoggerFactory.getLogger(FilterTextFn.class);
  private final Aggregator<Long, Long> maxIterableSize =
      createAggregator("maxIterableSize", new Max.MaxLongFn());

  @Override
  public void processElement(ProcessContext c) {
    long numElements = 0;
    for (T value : c.element().getValue()) {
      maxIterableSize.addValue(numElements++);
      if (numElements == 100) {
        LOG.warning("Key {} has > 100 values", c.element().getKey());
      }
      ... // the rest of your code inside the loop
    }
  }
}
//结果是约10k个元素的映射,160MB大小
PCollectionView侧数据=。。。
...
.适用于(ParDo.命名(“组别为KV”)(
新的DoFn(){…}
))
.apply(“GBK”,GroupByKey.create())
.apply(ParDo.named)(“合并值”)。带有SideInputs(sideData)。的(
新的DoFn(){…}
))
我两次运行这个管道,每次作业在正常运行超过16小时后停止。第一次使用10个
n1-highmem-8
运行,第二次使用6个
n1-highmem-16
实例运行

我可以从Dataflow作业控制台看出,将
分组到KV
ParDo完成得很好,输出了大小为153.67 GB的101730100个元素。
GBK
转换的步骤细节说明在第一次和第二次尝试中分别添加了72091846和72495353个元素。此时,
GBK
转换仍处于运行阶段,但所有机器上的CPU均降至零,管道实际上已停止运行。管道中的所有未来阶段都将停止增加元素计数。我可以通过ssh连接到机器中查看/var/log/dataflow/下的各种日志,但似乎没有任何异常。云控制台中没有错误,并且GC日志似乎没有表明内存问题

现在我有点不知所措,不知道下一步该怎么办。我已经读到,使用
组合器
而不是使用
GroupByKey
可以产生更好的可伸缩性。通过一点重构,我可以使代码是可交换的,因此组合器是一种选择。我有点犹豫是否尝试这样做,因为每次我尝试运行这个管道时,都会浪费大约250美元的云计算时间

我的问题是:

  • 当管道似乎停止运行时,建议采用哪些方法来尝试了解管道正在做什么?我应该在java进程上执行
    kill-QUIT
    ,以获得堆栈跟踪吗?如果是,它会去哪里

  • 有没有人对这条管道为什么会突然停止而没有任何错误或警告有任何理论

上述工作的ID:

  • 2016-09-02_10_56_46-679417604641749922
  • 2016-09-03_15_52_32-6019142684328835224

一名工人可能被卡住,或者在
GroupByKey
之后运行
DoFn
代码需要很长时间。最有可能的原因是“热键”(具有比其他键多得多的值)。您可以在
DoFn
中添加聚合器,并在运行时报告Iterable的大小,如下所示:

// Results in map of ~10k elements, 160MB in size
PCollectionView<Map<String, Iterable<Z>>> sideData = ...

...

.apply(ParDo.named("Group into KV").of(
    new DoFn<T, KV<String, T>>() { ... }
))
.apply("GBK", GroupByKey.create())
.apply(ParDo.named("Merge Values").withSideInputs(sideData).of(
    new DoFn<KV<String, Iterable<T>>, V>() { ... }
))
private static class MyDoFn extends KV<String, Iterable<T>>, V> {

  private static final Logger LOG =
    LoggerFactory.getLogger(FilterTextFn.class);
  private final Aggregator<Long, Long> maxIterableSize =
      createAggregator("maxIterableSize", new Max.MaxLongFn());

  @Override
  public void processElement(ProcessContext c) {
    long numElements = 0;
    for (T value : c.element().getValue()) {
      maxIterableSize.addValue(numElements++);
      if (numElements == 100) {
        LOG.warning("Key {} has > 100 values", c.element().getKey());
      }
      ... // the rest of your code inside the loop
    }
  }
}
专用静态类MyDoFn扩展KV,V>{
专用静态最终记录器日志=
LoggerFactory.getLogger(FilterTextFn.class);
专用最终聚合器maxIterableSize=
createAggregator(“maxIterableSize”,新的Max.MaxLongFn());
@凌驾
公共void processElement(ProcessContext c){
长数值=0;
对于(T值:c.element().getValue()){
addValue(numElements++);
如果(数值==100){
LOG.warning(“键{}的值大于100”,c.element().getKey());
}
…//循环中的其余代码
}
}
}
上面将添加一个计数器,显示单个键上的最大元素数,并向云日志记录报告任何超过100个值的键(请随意调整阈值,因为这似乎是合理的——单个热键可能比任何其他键具有更多的元素)


另一种可能性是,代码中的某个
DoFn
在某些特定数据集上挂起或非常慢。您可以尝试连接到正在处理这一项的工作人员,并查看它正在处理什么(使用您提到的
kill-QUIT

您是否检查了计算引擎日志以查看工作人员是否报告了任何问题@弗朗西斯:日志中没有错误。许多工作人员报告“在nnnn.nn秒内完成处理状态s20,0个错误”,这一切似乎都在作业空闲的同一时间结束——这似乎高度相关。然而,我不知道我的管道的哪一部分是s20阶段。尽管如此,仍有部分管道处于运行阶段,部分管道仍处于等待阶段。肯定还有工作要做。我想你可能已经找到了。我的大多数密钥(都在我使用的样本集中)分布非常均匀,每个密钥最多有15-20条记录。我对完整的数据集进行了查询,找到了一个命中率约为1.6MM的键!但是,我仍然想知道为什么CPU没有在其中一个节点上运行(表示有攻击性的GC),并且日志中没有提到任何关于OOM的内容。CPU和内存的使用将取决于
DoFn
中的代码。元素可以流式输入,因此除非
DoFn
尝试将它们全部存储在内存中,否则不会出现OOM。至于CPU——这些是多核机器吗?由于处理是在单个线程中进行的,因此最坏情况下它可能会使用1/#个内核,这可能非常小。