これは この質問 に最も似ています。
Pubsubキューからストリーミング入力を受け取るDataflow2.xでパイプラインを作成しています。着信するすべてのメッセージは、データベースに書き込まれる前に、Google BigQueryからの非常に大きなデータセットを介してストリーミングされ、関連するすべての値が(キーに基づいて)添付されている必要があります。
問題は、BigQueryからのマッピングデータセットが非常に大きいことです。サイド入力として使用しようとすると、データフローランナーが「Java.lang.IllegalArgumentException:ByteStringが長すぎます」というエラーをスローして失敗します。私は次の戦略を試みました:
1)サイド入力
2)キーと値のペアのマッピング
3)ParDo(DoFn)で直接BQを呼び出す
このタスクは「恥ずかしいほど並列化可能な」モデルに実際には適合していないようです。そこで、ここで間違ったツリーを吠えていますか?
編集:
データフローで高メモリマシンを使用していて、サイド入力をマップビューにしようとしても、エラーJava.lang.IllegalArgumentException: ByteString would be too long
が発生します。
これが私が使用しているコードの例(疑似)です:
Pipeline pipeline = Pipeline.create(options);
PCollectionView<Map<String, TableRow>> mapData = pipeline
.apply("ReadMapData", BigQueryIO.read().fromQuery("SELECT whatever FROM ...").usingStandardSql())
.apply("BQToKeyValPairs", ParDo.of(new BQToKeyValueDoFn()))
.apply(View.asMap());
PCollection<PubsubMessage> messages = pipeline.apply(PubsubIO.readMessages()
.fromSubscription(String.format("projects/%1$s/subscriptions/%2$s", projectId, pubsubSubscription)));
messages.apply(ParDo.of(new DoFn<PubsubMessage, TableRow>() {
@ProcessElement
public void processElement(ProcessContext c) {
JSONObject data = new JSONObject(new String(c.element().getPayload()));
String key = getKeyFromData(data);
TableRow sideInputData = c.sideInput(mapData).get(key);
if (sideInputData != null) {
LOG.info("holyWowItWOrked");
c.output(new TableRow());
} else {
LOG.info("noSideInputDataHere");
}
}
}).withSideInputs(mapData));
パイプラインは例外をスローし、ParDo
内から何かをログに記録する前に失敗します。
スタックトレース:
Java.lang.IllegalArgumentException: ByteString would be too long: 644959474+1551393497
com.google.cloud.dataflow.worker.repackaged.com.google.protobuf.ByteString.concat(ByteString.Java:524)
com.google.cloud.dataflow.worker.repackaged.com.google.protobuf.ByteString.balancedConcat(ByteString.Java:576)
com.google.cloud.dataflow.worker.repackaged.com.google.protobuf.ByteString.balancedConcat(ByteString.Java:575)
com.google.cloud.dataflow.worker.repackaged.com.google.protobuf.ByteString.balancedConcat(ByteString.Java:575)
com.google.cloud.dataflow.worker.repackaged.com.google.protobuf.ByteString.balancedConcat(ByteString.Java:575)
com.google.cloud.dataflow.worker.repackaged.com.google.protobuf.ByteString.copyFrom(ByteString.Java:559)
com.google.cloud.dataflow.worker.repackaged.com.google.protobuf.ByteString$Output.toByteString(ByteString.Java:1006)
com.google.cloud.dataflow.worker.WindmillStateInternals$WindmillBag.persistDirectly(WindmillStateInternals.Java:575)
com.google.cloud.dataflow.worker.WindmillStateInternals$SimpleWindmillState.persist(WindmillStateInternals.Java:320)
com.google.cloud.dataflow.worker.WindmillStateInternals$WindmillCombiningState.persist(WindmillStateInternals.Java:951)
com.google.cloud.dataflow.worker.WindmillStateInternals.persist(WindmillStateInternals.Java:216)
com.google.cloud.dataflow.worker.StreamingModeExecutionContext$StepContext.flushState(StreamingModeExecutionContext.Java:513)
com.google.cloud.dataflow.worker.StreamingModeExecutionContext.flushState(StreamingModeExecutionContext.Java:363)
com.google.cloud.dataflow.worker.StreamingDataflowWorker.process(StreamingDataflowWorker.Java:1000)
com.google.cloud.dataflow.worker.StreamingDataflowWorker.access$800(StreamingDataflowWorker.Java:133)
com.google.cloud.dataflow.worker.StreamingDataflowWorker$7.run(StreamingDataflowWorker.Java:771)
Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142)
Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617)
Java.lang.Thread.run(Thread.Java:745)
この記事の「パターン:ストリーミングモードの大規模なルックアップテーブル」というセクションをご覧ください https://cloud.google.com/blog/products/gcp/guide-to-common-cloud-dataflow-use-case -patterns-part-2 (サイド入力がメモリに収まらないため、これが唯一の実行可能なソリューションである可能性があります):
説明:
大きな(GB単位の)ルックアップテーブルは正確である必要があり、頻繁に変更されるか、メモリに収まりません。
例:
小売業者からのPOS情報があり、製品アイテムの名前をproductIDを含むデータレコードに関連付ける必要があります。外部データベースには数十万のアイテムが保存されており、絶えず変化する可能性があります。また、すべての要素は正しい値を使用して処理する必要があります。
解決策:
" データエンリッチメントのために外部サービスを呼び出す "パターンを使用しますが、マイクロサービスを呼び出すのではなく、読み取りに最適化されたNoSQLデータベース(CloudDatastoreやCloudBigtableなど)を直接呼び出します。
検索する値ごとに、KVユーティリティクラスを使用してKeyValueペアを作成します。 GroupByKeyを実行して同じキータイプのバッチを作成し、データベースに対して呼び出しを行います。 DoFnで、そのキーのデータベースを呼び出してから、イテラブルをウォークスルーしてすべての値に値を適用します。 「データ強化のための外部サービスの呼び出し」で説明されているように、クライアントのインスタンス化に関するベストプラクティスに従います。
その他の関連パターンについては、この記事で説明しています: https://cloud.google.com/blog/products/gcp/guide-to-common-cloud-dataflow-use-case-patterns-part-1 =: