web-dev-qa-db-ja.com

Dataflowラージサイド入力のApacheビーム

これは この質問 に最も似ています。

Pubsubキューからストリーミング入力を受け取るDataflow2.xでパイプラインを作成しています。着信するすべてのメッセージは、データベースに書き込まれる前に、Google BigQueryからの非常に大きなデータセットを介してストリーミングされ、関連するすべての値が(キーに基づいて)添付されている必要があります。

問題は、BigQueryからのマッピングデータセットが非常に大きいことです。サイド入力として使用しようとすると、データフローランナーが「Java.lang.IllegalArgumentException:ByteStringが長すぎます」というエラーをスローして失敗します。私は次の戦略を試みました:

1)サイド入力

  • 述べたように、マッピングデータは(明らかに)これを行うには大きすぎます。私がここで間違っているか、これに対する回避策がある場合は、これが最も簡単な解決策になるので、私に知らせてください。

2)キーと値のペアのマッピング

  • この戦略では、パイプラインの最初の部分でBigQueryデータとPubsubメッセージデータを読み取り、PCollectionsのすべての値をKeyValueペアに変更するParDo変換をそれぞれ実行します。次に、Merge.FlattenトランスフォームとGroupByKeyトランスフォームを実行して、関連するマッピングデータを各メッセージに添付します。
  • ここでの問題は、ストリーミングデータではウィンドウ処理を他のデータとマージする必要があるため、大きな境界のあるBigQueryデータにもウィンドウ処理を適用する必要があることです。また、ウィンドウ戦略が両方のデータセットで同じである必要があります。しかし、制限されたデータのウィンドウ戦略は意味がありません。私が行ったいくつかのウィンドウ試行では、すべてのBQデータを単一のウィンドウで送信し、それを再度送信することはありません。すべての着信pubsubメッセージと結合する必要があります。

3)ParDo(DoFn)で直接BQを呼び出す

  • これは良い考えのように思えました-各ワーカーにマップデータの静的インスタンスを宣言してもらいます。そこにない場合は、BigQueryを直接呼び出して取得します。残念ながら、これは毎回BigQueryから内部エラーをスローします(メッセージ全体が「内部エラー」とだけ言っているように)。グーグルにサポートチケットを提出した結果、彼らは基本的に「それはできない」と言った。

このタスクは「恥ずかしいほど並列化可能な」モデルに実際には適合していないようです。そこで、ここで間違ったツリーを吠えていますか?

編集:

データフローで高メモリマシンを使用していて、サイド入力をマップビューにしようとしても、エラー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)
10
Taylor

この記事の「パターン:ストリーミングモードの大規模なルックアップテーブル」というセクションをご覧ください 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 =:

  • パターン:ゆっくりと変化するルックアップキャッシュ
  • パターン:データ強化のために外部サービスを呼び出す
6
medvedev1088