Apache Spark:Facebookの60 TB +本番ユースケース

Apache Spark:Facebookの60 TB +本番ユースケース

SitalKediaがビッグデータについて語る

この記事では主に、FacebookがSparkを拡張してHiveに取って代わる過程で蓄積された経験と教訓について説明します。
ウェーブチップ仕上げの翻訳https://databricks.com/blog/2016/08/31/apache-spark-scale-a-60-tb-production-use-case.html。

ユースケース:エンティティランキングの機能準備

リアルタイムのエンティティランキングは、Facebookでさまざまな方法で使用されます。これらのオンラインサービスプラットフォームの元の機能値の一部はHiveによってオフラインで生成され、データはリアルタイムクエリシステムに読み込まれます。何年も前に構築された古いHiveベースのインフラストラクチャは、リソースを大量に消費するコンピューティングアーキテクチャであり、パイプラインが数百の小さなHiveジョブに分割されているため、保守が困難です。更新された機能データを実現し、管理性を向上させるために、既存のパイプラインが選択され、Sparkへの移行が試みられました。

オリジナルのHiveの実装

Hiveベースのパイプラインは3つの論理ステージで構成されます。各ステージで大きなHiveジョブを実行することは信頼性が低く、ジョブごとのタスクの最大数が制限されるため、各ステージはentity_idを共有する数百の小さなHiveジョブに対応します。

Apache Spark:Facebookの60 TB +本番ユースケース

これらの3つの論理的な手順は、次のように要約できます。

  1. 非実稼働機能とノイズを除外します。

  2. 各グループ(entity_id、target_id)を集約します。

  3. テーブルをN個のシャードに分割し、各シャードをカスタムバイナリを介してパイプラインの形式で実行し、オンラインクエリ用のカスタムインデックスファイルを生成します。

Hiveパイプラインに基づいてインデックスを作成するのに約3日かかります。パイプラインには何百ものシャーディングされたジョブが含まれているため、管理も困難であり、監視が困難です。パイプラインの全体的な進捗状況を測定したり、ETAを計算したりする簡単な方法はありません。既存のHiveパイプラインの上記の制限を考慮したとき、私はSparkを使用してより高速で管理しやすいパイプラインを構築しようと決心しました。

Sparkの実装

フルフローデバッグは、困難でリソースを大量に消費する可能性があります。最初に、Hiveベースのパイプラインの最もリソースを消費する部分である第2段階を変換します。最初は50GBの圧縮入力サンプルから始め、その後徐々に300 GB、1 TB、そして20TBに拡張しました。サイズを増やすたびに、パフォーマンスと安定性の問題を解決しましたが、20 TBをテストすることで、改善の最大の機会を見つけることができました。

20 TBの入力を実行すると、大量のタスクのために、生成される出力ファイルが多すぎることがわかりました(それぞれのサイズは約100 MB)。10時間のジョブ実行時間のうち3時間は、ステージングディレクターからHDFSの最終ディレクトリにファイルを移動するために使用されます。最初に、2つのオプションを検討しました。このケースをサポートするためにHDFSでのバッチの名前変更を改善するか、生成する出力ファイルが少なくなるようにSparkを構成します(この段階ではタスクの数が多い(70,000)ため)。私たちは質問から脱落し、3番目のオプションを検討しました。パイプラインの2番目のステップで生成したtmp_table2テーブルは一時的なものであり、パイプラインの中間出力を格納するためにのみ使用されるため、基本的に3つのコピーを圧縮、シリアル化、コピーして、数テラバイトのデータワークロードで1回の読み取りを行います。さらに一歩進んでみましょう。2つの一時テーブルを削除し、3つのHiveステージすべてを1つのSparkジョブにマージします。このジョブは、60 TBの圧縮データを読み取り、90TBのランダムおよび並べ替えを実行します。最終的なSparkの作業は次のとおりです。

Apache Spark:Facebookの60 TB +本番ユースケース

この仕事のためにSparkをどのように拡張しましたか?

もちろん、このような大規模なパイプラインに対して単一のSparkジョブを実行しても、最初の試行でも10回目の試行でも正しく実行されませんでした。私たちが知る限り、これはシャッフルデータサイズの観点から試みられた最大のSparkジョブです(Databricksのペタバイトソートは合成データで行われます)。このジョブを実行するために、コアSparkインフラストラクチャとアプリケーションに多くの改善と最適化が行われました。このジョブの利点は、多くの改善がSparkの他の大規模なワークロードに適用され、すべての作業をオープンソースのApache Sparkプロジェクトに戻すことができることです。詳細については、JIRAを参照してください。以下では、エンティティランキングパイプラインの1つを本番環境にデプロイできる主な改善点について説明します。

信頼性の修復

頻繁なノードの再起動の処理

長時間実行されるジョブを確実に実行するために、システムにフォールトトレラントを設定し、障害から回復する必要があります(主に、通常のメンテナンスまたはソフトウェアエラーによるマシンの再起動による)。Sparkはマシンの再起動を許容することを目的としていますが、解決すべきあらゆる種類のエラー/問題により、一般的なエラーを十分に処理できます。

PipedRDDをフェッチの失敗に対してより堅牢にする(SPARK-13​​793)PipedRDDの以前の実装は、ノードの再起動によって引き起こされるフェッチの失敗を処理するほど強力ではなく、フェッチの失敗が発生する限り、ジョブは失敗します。PipedRDDに変更を加えて、取得の失敗を適切に処理し、ジョブがこのタイプの取得の失敗から回復できるようにしました。

構成可能な取得失敗の最大数(SPARK-13​​369):このような長時間実行ジョブの場合、マシンの再起動による取得失敗の可能性が大幅に増加します。Sparkの各ステージでのフェッチ失敗の最大許容数はハードコーディングされているため、最大数に達するとジョブは失敗します。構成可能にするために変更を加え、このユースケースではジョブをより堅牢にするために4から20に増やしました。

中断の少ないクラスターの再起動:長時間実行されるジョブは、クラスターの再起動後も存続できるはずです。Sparkの再起動可能なシャッフルサービス機能を使用すると、ノードの再起動後にファイルをシャッフルしておくことができます。最も重要なことは、クラスターの再起動によるタスクの失敗が多すぎてジョブの失敗が発生しないように、タスクのスケジューリングを一時停止できる機能をSparkドライバーに実装したことです。

その他の信頼性の修正

応答しないドライバー(SPARK-13​​279):O(N ^ 2)操作が原因でタスクを追加すると、Sparkドライバーがスタックし、最終的にジョブがスタックして終了しました。不要なO(N ^ 2)演算を削除することで問題を解決します。

過度のドライバーの推測: Sparkドライバーは、多数のタスクを管理するときに推測に多くの時間を費やしていることがわかりました。短期的には、ジョブの投機的実行は禁止されています。現在、投機時間を短縮するためにSparkドライバーを変更するための取り組みが行われています。

大きなバッファの整数オーバーフローによって引き起こされるTimSortの問題(SPARK-13​​850):テストでは、Sparkの安全でないメモリ操作にTimSortメモリの破損を引き起こすバグがあることがわかりました。この問題を解決してくれたDatabricksのスタッフに感謝します。これにより、大きなメモリバッファで実行できるようになります。

多数の接続を処理するようにシャッフルサービスを調整します。シャッフルフェーズ中に、シャッフルサービスに接続しようとすると、多くのエグゼキュータがタイムアウトしました。Nettyサーバースレッド(spark.shuffle.io.serverThreads)とバックログ(spark.shuffle.io.backLog)の数を増やすと、この問題が解決します。

Spark executor OOM(SPARK-13​​958)の修正:最初に各ホストに4つを超えるレデューサータスクをパックするのは困難です。ポインタ配列が無限に大きくなる原因となるソータにエラーがあるため、Sparkエグゼキュータのメモリが不足しています。ポインタ配列の拡張に使用できるメモリがなくなったときに、データを強制的にディスクにオーバーフローさせることで、この問題を解決します。したがって、メモリが不足することなく、24個のタスク/ホストを実行できるようになりました。

パフォーマンスの向上

上記の信頼性の向上を達成した後、Sparkジョブを確実に実行できます。この時点で、Sparkを最大限に活用するためにパフォーマンス関連のプロジェクトに目を向けるよう努めます。Sparkメトリックといくつかのアナライザーを使用して、パフォーマンスのボトルネックを見つけます。

パフォーマンスのボトルネックを見つけるために使用するツール

Spark UIメトリック: Spark UIは、特定のステージで費やされた時間に関する洞察を得ることができます各タスクの実行時間は、ジョブのボトルネックを見つけやすくするためにサブフェーズに分割されています。

Jstack:Spark UIは、実行プログラムプロセスでオンデマンドのjstack関数も提供します。これは、コード内のホットスポットを見つけるために使用できます。

Spark Linux Perf / Flame Graphのサポート:上記の2つのツールは非常に便利ですが、数百台のコンピューターで同時に実行されているジョブのCPUプロファイリングの集約ビューを提供することはできません。各ジョブに基づいて、(Javaシンボルのlibperfagentを介して)Perfプロファイリングを有効にするためのサポートを追加し、サンプリングの期間/頻度をカスタマイズできます。内部メトリック収集フレームワークを使用して、分析サンプルが集約され、実行プログラムでフレームグラフとして表示されます。

パフォーマンスの最適化

サブソーターのメモリリークを修正(SPARK-14363)(30%アクセラレーション):タスクがすべてのメモリページを解放するが、ポインター配列が解放されない場合、問題が見つかりました。その結果、メモリの大きなブロックは使用されず、頻繁なオーバーフローとエグゼキュータOOMが発生します。この修正により、メモリが正しく解放され、大規模な並べ替えが効率的に実行されるようになりました。この修正後、CPUのパフォーマンスが30%向上したことがわかりました。

Snappy最適化(SPARK-14277)(10%アクセラレーション):各行の読み取り/書き込みに対してJNIメソッドが呼び出されています-(Snappy.ArrayCopy)。この質問をし、JNIベースではないSystem.ArrayCopyを使用するようにSnappyの動作を変更しました。この変更だけで、CPUが約10%向上します。

ランダム書き込みレイテンシーの削減(SPARK-5581)(最大50%高速化):マップ側では、ランダムデータがディスクに書き込まれるとき、マップタスクは各パーティションで同じファイルを開いたり閉じたりすることです。不必要な開閉を回避するための修正を行い、多数のシャッフルパーティションに書き込むジョブのCPUが最大50%向上することを確認しました。

フェッチの失敗が原因でタスクが繰り返し実行される問題を修正しました(SPARK-14649):Sparkドライバーは、フェッチが失敗したときにすでに実行されているタスクを再送信し、パフォーマンスを低下させます。実行中のタスクの再実行を回避することで問題を修正し、取得が失敗した場合でもジョブがより安定していることを確認しました。

PipedRDDの構成可能なバッファーサイズ(SPARK-14542)(10%アクセラレーション):PipedRDDを使用する場合、ソーターからパイプラインプロセスにデータを転送するためのデフォルトのバッファーサイズが小さすぎて、作業コストが10%を超えることがわかりました。データをコピーします。このボトルネックを回避するために、バッファーサイズを構成可能にします。

シャッフルフェッチアクセラレーション用のキャッシュインデックスファイル(SPARK-15074):シャッフルサービスがボトルネックになることが多く、レデューサーは時間の10%から15%をマップデータの待機に費やしています。この問題を詳しく調べたところ、シャッフルサービスがシャッフルフェッチごとにシャッフルインデックスファイルを開いたり閉じたりしていることがわかりました。ファイルの開閉を回避し、インデックス情報を後で抽出するために再利用できるように、キャッシュインデックス情報に変更を加えました。この変更により、合計シャッフル時間が50%短縮されます。

シャッフルバイト書き込みインジケーターの更新頻度を減らす(SPARK-15569)(最大20%のアクセラレーション):Spark Linux Perf統合を使用すると、CPU時間の約20%が、書き込まれたシャッフルバイトインジケーターの検出と更新に使用されることがわかりました。

構成可能なソーターの初期バッファーサイズ(SPARK-15958)(最大5%の加速):ソーターのデフォルトの初期バッファーサイズが小さすぎる(4 KB)ため、大きなワークロードに対しては非常に小さいことがわかりました-その結果、バッファを拡張して内容をコピーするために多くの時間を無駄にしました。バッファサイズを設定できるように変更しました。大きなバッファサイズは64MBであるため、大量のデータコピーを回避し、作業速度を約5%向上させることができます。

タスク数を構成します。入力サイズが60Tで、各HDFSブロックサイズが256 Mであるため、このジョブに対して250,000を超えるタスクを生成しました。非常に多くのタスクでSparkジョブを実行できましたが、タスクが多すぎると、パフォーマンスが大幅に低下することがわかりました。マップの入力サイズを構成可能にする構成パラメーターを導入したため、入力分割サイズを2 GBに設定することで、この数を8分の1に減らすことができます。

これらの信頼性とパフォーマンスの改善をすべて完了した後、エンティティランキングシステムの1つに、より高速で管理しやすいパイプラインを構築して展開し、Sparkで他の同様のジョブを実行する機能を提供したことをお知らせします。

SparkパイプラインとHiveパイプラインのパフォーマンス比較

以下のパフォーマンスメトリックを使用して、SparkパイプラインとHiveパイプラインを比較します。これらの数値は、クエリまたはジョブレベルでのSparkとHiveの直接の比較ではなく、コンピューティングエンジンのクエリ/ジョブレベルだけでなく、柔軟なコンピューティングエンジン(Sparkなど)を使用して最適化されたパイプラインを構築することの比較であることに注意してください。次の操作(たとえば、Hive)。

CPU時間:オペレーティングシステムの観点から、これはCPU使用率です。たとえば、32コアコンピューターのCPUの50%のみを使用してジョブを10秒間実行すると、CPU時間は32 0.5 10 = 160CPU秒になります。

Apache Spark:Facebookの60 TB +本番ユースケース

CPU予約時間:リソース管理フレームワークの観点から見たCPU予約です。たとえば、ジョブを実行するために32コアマシンを10秒間予約すると、CPU予約時間は32 * 10 = 320CPU秒になります。CPU時間とCPU予約時間の比率は、クラスターで予約されたCPUリソースをどのように使用するかを反映しています。正確な場合、CPU時間と比較して、同じワークロードを実行している場合、予約された時間は実行エンジンをよりよく比較できます。たとえば、プロセスの実行に1 CPU秒が必要であるが、100 CPU秒を予約する必要がある場合、このインジケーターの効率は、10 CPU秒を必要とするが、同じ量の作業を実行するために10CPU秒しか予約しないプロセスよりも低くなります。メモリ予約時間も計算しますが、含まれていません。実験は同じハードウェアで実行されるため、数値はCPU予約時間と同様であり、SparkとHiveの場合はデータをキャッシュしません。メモリ。Sparkはデータをメモリにキャッシュできますが、クラスターのメモリ制限のため、Hiveと同様のオフコア作業を使用することにしました。

Apache Spark:Facebookの60 TB +本番ユースケース

レイテンシー:ジョブのエンドツーエンドの経過時間。

Apache Spark:Facebookの60 TB +本番ユースケース

結論と今後の作業

Facebookは、高性能でスケーラブルな分析を使用して、製品開発を支援します。Apache Sparkは、さまざまな分析のユースケースを単一のAPIと効率的な計算エンジンに統合する独自の機能を提供します。数百のHiveジョブに分割されたパイプラインを1つのSparkジョブに置き換えました。一連のパフォーマンスと信頼性の向上により、Sparkを拡張して、本番環境でのエンティティランキングデータ処理のユースケースの1つを処理することができました。この特定のユースケースでは、Sparkが90 TB +の中間データを確実にシャッフルおよびソートし、1つのジョブで250,000のタスクを実行できることを示しました。古いHiveベースのパイプラインと比較して、Sparkベースのパイプラインはパフォーマンスが大幅に向上し(CPUが4.5〜6倍、リソース予約が3〜4倍、レイテンシが約5倍)、数か月間本番環境で実行されています。

おすすめ

転載: blog.51cto.com/15127544/2664901