web-dev-qa-db-ja.com

Spark最終タスクは最初の199の100倍の時間がかかります。改善方法

データフレームを使用してクエリを実行すると、パフォーマンスの問題が発生します。私の調査で、finallyタスクが長時間実行されていることは、データが最適に乱されていないことを示している可能性がありますが、この問題を解決するための詳細なプロセスが見つかりませんでした。

2つのテーブルをデータフレームとしてロードすることから始めて、それらのテーブルを1つのフィールドに結合します。パフォーマンスを向上させるために、distribute by(repartition)とsort byを追加しようとしましたが、この単一の長時間実行の最終タスクがまだ表示されています。これが私のコードの単純なバージョンです。クエリ1と2は実際にはそれほど単純ではなく、UDFを使用していくつかの値を計算していることに注意してください。

spark.sql.shuffleの設定をいくつか試しました。私は100を試しましたが、失敗しました(正直に言うと、あまりデバッグしていません)。 300、4000、8000を試してみました。パフォーマンスは増加するごとに低下しました。各ファイルが1時間のデータの1日を選択しています。

val df1 = sqlContext.sql("Select * from Table1")
val df2 = sqlContext.sql("Select * from Table2")

val distributeDf1 = df1
    .repartition(df1("userId"))
    .sortWithinPartitions(df1("userId"))

val distributeDf2 = df2
    .repartition(df2("userId"))
    .sortWithinPartitions(df2("userId"))

distributeDf1.registerTempTable("df1")
distributeDf2.registerTempTable("df2")

val df3 = sqlContext
  .sql("""
    Select 
      df1.* 
    from 
      df1 
    left outer join df2 on 
      df1.userId = df2.userId""")

UserIdによるパーティション化は理想的ではないようなので、代わりにタイムスタンプでパーティション化することもできます。これを行う場合、日付と時間だけを実行する必要がありますか?これに対して200未満のユニークなコンボがある場合、空のエグゼキューターはいますか?

27

あなたは明らかに巨大な正しいデータスキューに問題があります。見てみましょう 提供した統計

df1 = [mean=4.989209978967438, stddev=2255.654165352454, count=2400088] 
df2 = [mean=1.0, stddev=0.0, count=18408194]

平均約5と2000を超える標準偏差で、ロングテールになります。

一部のキーは、パーティションを分割した後、他のキーよりも頻繁に使用されるため、一部のエグゼキューターは、残りのキーよりも多くの作業を行うことになります。

さらに、あなたの説明は、問題が同じパーティションにハッシュする単一またはいくつかのキーにある可能性があることを示唆しています。

そこで、まず外れ値(疑似コード)を特定しましょう。

val mean = 4.989209978967438 
val sd = 2255.654165352454

val df1 = sqlContext.sql("Select * from Table1")
val counts = df.groupBy("userId").count.cache

val frequent = counts
  .where($"count" > mean + 2 * sd)  // Adjust threshold based on actual dist.
  .alias("frequent")
  .join(df1, Seq("userId"))

そして残り:

val infrequent = counts
  .where($"count" <= mean + 2 * sd)
  .alias("infrequent")
  .join(df1, Seq("userId"))

本当に期待されるものなのでしょうか?そうでない場合は、上流の問題の原因を特定してください。

それが期待されている場合、試すことができます

  • 小さいテーブルをブロードキャストする:

    val df2 = sqlContext.sql("Select * from Table2")
    df2.join(broadcast(df1), Seq("userId"), "rightouter")
    
  • 分割、統合(union)および頻繁にのみブロードキャスト:

    df2.join(broadcast(frequent), Seq("userId"), "rightouter")
      .union(df2.join(infrequent, Seq("userId"), "rightouter"))
    
  • userIdをランダムなデータでソルトする

すべきではない

  • すべてのデータを再分割してローカルで並べ替えます(ローカルでの並べ替えだけでは問題になりません)
  • 完全なデータに対して標準のハッシュ結合を実行します。
19
zero323