Google cloud dataflow 创建多个带标记的输出时Google数据流出堆

Google cloud dataflow 创建多个带标记的输出时Google数据流出堆,google-cloud-dataflow,Google Cloud Dataflow,我有许多未分区的大型BigQuery表和文件,我希望以各种方式进行分区。所以我决定尝试编写一个数据流作业来实现这一点。我认为这工作很简单。我试着用泛型来编写代码,这样我就可以轻松地将它应用到TextIO和BigQueryIO源代码中。它在小表上运行良好,但在大表上运行时,我不断得到java.lang.OutOfMemoryError:java堆空间 在我的主类中,我要么读取带有目标键的文件(由另一个DF作业生成),要么对BigQuery表运行查询,以获取要切分的键列表。我的主要课程是这样的: P

我有许多未分区的大型BigQuery表和文件,我希望以各种方式进行分区。所以我决定尝试编写一个数据流作业来实现这一点。我认为这工作很简单。我试着用泛型来编写代码,这样我就可以轻松地将它应用到TextIO和BigQueryIO源代码中。它在小表上运行良好,但在大表上运行时,我不断得到
java.lang.OutOfMemoryError:java堆空间

在我的主类中,我要么读取带有目标键的文件(由另一个DF作业生成),要么对BigQuery表运行查询,以获取要切分的键列表。我的主要课程是这样的:

Pipeline sharder=Pipeline.create(opts);
//一个功能接口,显示如何获取元组标记的标记映射
KeySelector bqSelector=(表行)->(字符串)行。获取(“列”)!=无效的(字符串)row.get(“COLUMN”):“null”;
//用于存储元组标记列表和字符串TupleTag的哈希映射的实用程序类
TupleTagMap bqTags=新的TupleTagMap(新的ArrayList(inputKeys),bqSelector);
//定制变形金刚
ShardedTransform bqShard=新的ShardedTransform(bqTags,TableRowJsonCoder.of());
String source=“PROJECTID:ADATASET.A_BIG_TABLE”;
String destBase=“projectid:dataset.a\u big\u table\u sharded\u”;
TableSchema schema=bq.tables().get(“PROJECTID”、“ADATASET”、“A_BIG_TABLE”).execute().getSchema();
PCollectionList shards=sharder.apply(BigQueryIO.Read.from(source)).apply(bqShard);
for(PCollection shard:shards.getAll()){
String shardName=StringUtils.isNotEmpty(shard.getName())?shard.getName():“NULL”;
shard.apply(BigQueryIO.Write.to)(destBase+shardName)
.带WriteDisposition(WriteDisposition.WRITE_TRUNCATE)
.withCreateDisposition(CreateDisposition.CREATE_,如果需要)
.使用模式(schema));
System.out.println(destBase+shardName);
} 
sharder.run();
我生成一组
TupleTags
,用于自定义转换。我创建了一个实用程序类,用于存储
TupleTagList
HashMap
,以便我可以按键引用tuple标记:

公共类TupleTagMap实现可序列化{
私有静态最终长serialVersionUID=-8762959703864266959L;
最终私有TupleTagList tList;
最终私有地图;
最终私钥选择器;
公共TupleTagMap(列表t,键选择器){
map=新的HashMap();
for(键:t)
put(key,new-TupleTag());
this.tList=TupleTagList.of(新的ArrayList(map.values());
this.selector=选择器;
}
公共地图getMap(){
返回图;
}
公共元组列表getTagList(){
返回列表;
}
公共TupleTag getTag(t型){
返回map.get(selector.getKey(t));
}
然后我有一个自定义转换,它基本上有一个函数,使用元组映射输出
PCollectionTuple
,然后将其移动到
PCollectionList
以返回到主类:

公共类sharedTransform扩展
转移{
私有静态最终长SerialVersionId=33206267328032927323L;
私有最终TupleTagMap标记;
私人最终编码器;
公共sharedTransform(TupleTagMap标记、编码器){
this.tags=标签;
this.coder=编码器;
}
@凌驾
公共PCollection列表应用(PCollection in){
PCollectionTuple shards=in.apply(ParDo.of(
新的ShardFn(标签))。带输出标签(
新的TupleTag(),tags.getTagList());
List shardList=newarraylist(tags.getMap().size());
对于(条目e:tags.getMap().entrySet()){
PCollection shard=shards.get(e.getValue()).setName(e.getKey().toString()).setCoder(coder);
shardList.add(shard);
}
返回PCollectionList.of(shardList);
} 
}
实际的DoFn非常简单,它只使用main类中提供的lambda,在hash映射中查找匹配的tuple标记以进行side输出:

公共类ShardFn扩展DoFn{
私有静态最终长serialVersionUID=961325260858465105L;
私有最终TupleTagMap标记;
ShardFn(TupleTagMap标记){
this.tags=标签;
}
@凌驾
public void processElement(DoFn.ProcessContext c)
抛出异常{
类型元素=c.元素();
TupleTag tag=tags.getTag(元素);
如果(标记!=null)
c、 sideOutput(tags.getTag(element),element);
} 
}

Beam模型目前对动态分区/大量分区没有很好的支持。您的方法在图形构建时选择碎片的数量,然后生成的PARDO很可能都融合在一起,因此每个工作人员都试图同时写入80个不同的BQ表。每次写入都需要这是一些本地缓冲,所以可能太多了

有一种替代方法可以跨表(而不是跨元素)进行并行化。如果您有大量相对较小的输出表,这将非常有效。使用ParDo将每个元素与它应该去的表一起标记,然后执行GroupByKey。这将为您提供一个
PCollection
。然后通过将元素写入表来处理每个
KV


不幸的是,现在您必须手工编写BQ才能使用此选项。我们正在考虑使用内置的支持扩展Sink API。由于Dataflow SDK作为Apache Beam的一部分正在进一步开发,因此我们在这里跟踪该请求:

您的工作机类型是什么,在哪个阶段开始运行?还有多大
bqTags
可以获取,也许您应该使用side输入来分发
bqTags
(文档:)我试着使用n1-standard-4。看起来OOM是在ShardedTransform或ShardFn中发生的。据我所知,没有任何东西被发送到从那里输出的PCollections。在大数据集的标记映射中有80个标记。谢谢Frances。我在我的cas中看到了一些建议跨表并行化的SO帖子我有一个较小的数值