大規模データ アプリケーションを構築するために Apache Spark などのコンピューティング フレームワークが広く採用されてきました。 Uberにとって、データは戦略的な意思決定や製品開発の中核をなすものです。 このデータをよりよく活用するために、私たちはグローバルなエンジニアリングオフィス全体でSparkの大規模なデプロイメントを管理しています。
Spark によってデータ テクノロジーがより身近になりましたが、Spark アプリケーションに割り当てられるリソースを適切に調整し、データ インフラストラクチャの運用効率を最適化するには、これらのシステムに関するより詳細な洞察、つまりリソースの使用パターンが必要です。 Spark 用に構築されたものの、その汎用的な実装により、Java 仮想マシン (JVM) ベースのあらゆるサービスまたはアプリケーションに適用できます。 Uberがこのツールを使ってどのようにSparkアプリケーションをプロファイリングしているか、また、自分のシステムでどのように使うかについては、この先をお読みください。
- プロファイリングの課題
- Correlate metrics across the large number of processes at the application level
- Make metrics collection non-intrusive for arbitrary user code
- Introducing JVM Profiler
- JVM Profiler は何をするか?
- Typical use cases
- 実装の詳細と拡張性
- How to extend the JVM Profiler to send metrics via a custom reporter
- Uber のデータ インフラストラクチャとの統合
- Using the JVM Profiler
- Metric query examples
- 結果と次のステップ
プロファイリングの課題
日常的に、Uber は何千ものマシンで実行される何万ものアプリケーションをサポートしています。 当社の技術スタックが大きくなるにつれて、既存のパフォーマンス プロファイリングおよび最適化ソリューションでは当社のニーズを満たすことができないことにすぐに気づきました。 特に、次のような機能が必要でした。
Correlate metrics across the large number of processes at the application level
分散環境では、複数の Spark アプリケーションが同じサーバー上で実行され、各 Spark アプリケーションには多数のサーバーにわたって実行されている多数のプロセス (たとえば、数千のエグゼキューター) があります (Figure 1)
私たちの既存のツールは、サーバー レベルのメトリックを監視することしかできず、個々のアプリケーションのメトリックを測定することはできませんでした。 私たちは、各プロセスのメトリックを収集し、各アプリケーションのプロセス間で相関させることができるソリューションが必要でした。 さらに、これらのプロセスがいつ起動し、どれくらいの時間がかかるかはわかりません。 この環境でメトリックを収集できるようにするには、各プロセスでプロファイラーを自動的に起動する必要があります。
Make metrics collection non-intrusive for arbitrary user code
Spark および Apache Hadoop ライブラリーは、現在の実装ではパフォーマンス メトリックをエクスポートしません。しかし、ユーザーまたはフレームワーク コードを変更せずにこれらのメトリックを収集しなければならない状況が頻繁に発生します。 たとえば、Hadoop Distributed File System (HDFS) NameNode で高いレイテンシーが発生した場合、各 Spark アプリケーションから観察されたレイテンシーをチェックして、これらの問題が複製されていないことを確認したいと思います。 NameNodeのクライアントコードはSparkライブラリに組み込まれているため、この指標を追加するためにソースコードを変更するのは面倒です。 データインフラの永続的な成長に対応するためには、いつでも、どんなアプリケーションでも、コードを変更することなく測定できる必要があります。 さらに、より非侵入型のメトリック収集プロセスを実装すると、ロード時に Java メソッドに動的にコードを注入できるようになります。
Introducing JVM Profiler
これら 2 つの課題に対処するため、私たちは JVM プロファイラを構築してオープンソース化しました。 Etsy の statsd-jvm-profiler のように、個々のアプリケーション レベルでメトリックを収集できる既存のオープン ソース ツールもありますが、既存の Java バイナリに動的にコードを注入してメトリックを収集する機能は提供されていません。 これらのツールのいくつかに触発されて、私たちは、任意の Java メソッド/引数プロファイリングなど、さらに多くの機能を備えたプロファイラーを構築しました。
JVM Profiler は何をするか?
JVM Profiler は、パフォーマンスおよびリソース使用メトリックを簡単に収集して、さらに分析するためにこれらのメトリック (Apache Kafka など ) を他のシステムへ提供する 3 つの主要機能からなります:
- Java Agent: Java エージェントを私たちのプロファイラーに組み込むことにより、ユーザーはさまざまなメトリック(例:CPU/メモリ使用量)および JVM プロセスのスタック・トレースを分散方式で収集することができます。
- 高度なプロファイリング機能。 当社の JVM プロファイラーでは、実際のコードに変更を加えることなく、ユーザー コード内の任意の Java メソッドおよび引数をトレースすることができます。 この機能は、Spark アプリケーションの HDFS NameNode RPC コールのレイテンシーをトレースし、遅いメソッド コールを特定するために使用できます。 また、各Sparkアプリケーションが読み書きするHDFSファイルのパスをトレースし、さらなる最適化のためにホットファイルを特定することができます。
- データ分析レポート。 Uber では、プロファイラーを使用して、Kafka トピックと Apache Hive テーブルにメトリックを報告し、データ分析をより迅速かつ容易にします。
Typical use cases
Our JVM Profiler は、特に任意の Java コードをインストルメントすることを可能にし、さまざまなユースケースをサポートします。 簡単な構成変更を使用して、JVM Profiler は Spark アプリケーションの各エグゼキュータにアタッチし、Java メソッドのランタイム メトリクスを収集できます。 以下では、これらのユースケースについていくつか触れていきます。
- 適正なサイズのエグゼキュータ。 JVM Profilerからのメモリメトリクスを使用して、各エグゼキュータの実際のメモリ使用量を追跡し、Sparkの「executor-memory」引数に適切な値を設定できるようにします。
- HDFS NameNode RPC レイテンシーを監視します。 Spark アプリケーションの org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB クラスのメソッドをプロファイルし、NameNode コールの長いレイテンシーを特定します。 私たちは毎日5万以上のSparkアプリケーションを監視し、数十億のこのようなRPCコールを監視しています。
- ドライバーのドロップ イベントを監視します。 org.apache.spark.scheduler.LiveListenerBus.onDropEvent のようなメソッドをプロファイルして、Spark ドライバー イベント キューが長くなりすぎてイベントをドロップする状況をトレースします。
- データの系統をトレースします。 org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getBlockLocations と org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.addBlock のメソッドのファイルパス引数をプロファイルして Spark アプリケーションがどのファイルを読み、書きしたかをトレースできるようになりました。
実装の詳細と拡張性
できるだけシームレスに実装するために、JVM Profiler は非常にシンプルで拡張可能な設計になっています。 より多くのメトリクスを収集するために、プロファイラーの実装を簡単に追加できます。また、データ解析のために異なるシステムにメトリクスを送信するための独自のカスタム レポーターを展開することも可能です。 私たちの JVM プロファイラーは、JVM の使用とパフォーマンスに関連する特定のメトリックを測定するいくつかの異なるプロファイラーで構成されています。
JVM プロファイラーのコードは、プロセスが開始すると Java のエージェント引数を介して Java プロセスにロードされます。 これは、3 つの主要な部分で構成されています。
- Class File Transformer: 任意のユーザー コードをプロファイルし、内部メトリック バッファにメトリックを保存するために、プロセス内部の Java メソッド バイトコードを計測します。
- Metric Profilers
- CPU/Memory Profiler: JMX を介して CPU/Memory 使用率のメトリクスを収集し、報告者に送信します。
- Method Duration Profiler: メトリック・バッファからメソッド期間 (レイテンシー) のメトリックを読み取り、レポーターへ送信します。
- Method Argument Profiler: メトリクス・バッファーからメソッド引数値を読み取り、レポーターへ送信します。
- Reporters
- Console Reporter: コンソール出力にメトリクスを書き込みます。
- Kafka Reporter:Kafkaトピックにメトリクスを送信します。
How to extend the JVM Profiler to send metrics via a custom reporter
User could implement their own reporters and specified them with -javaagent option, like:
java
-javaagent:jvm-profiler-0.JVM Profiler
-JVM Profiler-0.JVM Profiler
-JVM Profiler
-Javaagent:jvm-profiler-0.JVM Profiler
Users は、独自のレポーターを実装することができます。0.5.jar=reporter=com.uber.profiling.reporters.CustomReporter
Uber のデータ インフラストラクチャとの統合
私たちは、Uber の内部データ インフラストラクチャと JVM Profiler のメトリックを統合し、以下を可能にしました:
- クラスター全体のデータ解析。 メトリクスはまず Kafka に供給され、HDFS に取り込まれ、その後ユーザーは Hive/Presto/Spark でクエリーします。
- リアルタイムのSparkアプリケーションのデバッギング。 Flinkを使用して、1つのアプリケーションのデータをリアルタイムで集計し、MySQLデータベースに書き込み、その後、ユーザーはWebベースのインターフェイスを介してメトリックを表示することができます。
Using the JVM Profiler
以下では、簡単な Java アプリケーションをトレースするために JVM プロファイラーを使用する方法を説明します。
最初に、プロジェクトを git clone します:
次に、次のコマンドを実行してプロジェクトをビルドします:
次に、ビルド結果の JAR ファイル (例: target/jvm-profiler-0.0.5.jar) を呼び、JVM Profiler 内で次のコマンドを使ってアプリケーションを実行します:
コマンドがサンプル Java アプリケーションを実行すると、パフォーマンスとリソース使用率のメトリクスが出力コンソールに報告されます。 たとえば、次のようなコマンドを使用して、プロファイラーは Kafka トピックにメトリックを送信することもできます。
すでに HDFS クラスターがあると仮定して、JVM Profiler JAR ファイルを HDFS にアップロードします。
それから spark-submit コマンドラインを使用して、プロファイラー付きの Spark アプリケーションを起動します。
Metric query examples
Uber で、Kafka トランクにメトリクスを送って、それらを Hive テーブルに自動取り込みするバックグラウンド データ パイプラインをプログラムします。 ユーザーは、同様のパイプラインを設定し、SQL を使用して、プロファイラー メトリクスをクエリできます。 また、MySQLのようなSQLデータベースにメトリクスを送信し、そこからクエリを実行するための独自のレポーターを書くことも可能です。 以下は、Hiveテーブルのスキーマの例です。
以下は、以前のSQLクエリを実行したときの結果の例で、Sparkエグゼキュータの各プロセスのメモリとCPUのメトリクスを表示します。
role processUuid maxHeapMemoryMB avgProcessCpuLoad executor 6d3aa4ee-4233-4cc7-a628-657e1a87825d 2805.255325 7.61E-11 executor 21f418d1-6d4f-440d-b28a-4eba1a3bb53d 3993.969582 9.56E-11 executor a5da6e8c-149b-4722-8fa8-74a16baabcf8 3154.484474 8.18E-11 executor a1994e27-59bd-4cda-9de3-745ead954a27 2561.847374 8.58E-11 結果と次のステップ
私たちは、Uber の最大の Spark アプリケーション (1,000 以上の実行子を使用) の 1つに JVM Profiler を適用し、その過程で各実行子のメモリ割り当てを 7GB から 5GB へと 2GB 減少させました。 このSparkアプリケーションだけで、2TBのメモリを節約することができました。
また、Uber 内のすべての Hive on Spark アプリケーションに JVM Profiler を適用し、メモリ使用効率を改善する機会をいくつか発見しました。 下の図 3 は、私たちが発見した結果の 1 つで、アプリケーションの約 70% が割り当てられたメモリの 80% 未満しか使用していませんでした。 この結果から、これらのアプリケーションのほとんどで、より少ないメモリを割り当て、メモリ使用率を 20 パーセント向上できることがわかりました。 当社の JVM プロファイラーは、アプリケーションの 70% が割り当てられたメモリの 80% 未満しか使用していないことを確認しました。
当社のソリューションを成長させ続ける中で、当社の JVM 全体でさらにメモリを削減できることを期待しています。
JVM Profiler は自己完結型のオープン ソース プロジェクトであり、他の開発者がこのツールを使用し、同様に貢献 (例: プル リストの提出) することを歓迎します!
シアトル、パロアルト、サンフランシスコの当社の Big Data Engineering チームは、HDFS、Hive、Presto、および Spark など、Hadoop エコシステム全体を拡張するツールおよびシステムの開発に取り組んでいます。 私たちは、このオープンソースソフトウェアのファミリーの上に技術を構築し、より良い、データ駆動型のビジネス決定を行うために役立てています。 もしこれがあなたにとって魅力的に思えるなら、私たちの求人情報をチェックして、私たちのチームへの参加を検討してください!
Photo Credit Header:
Subscribe to our newsletter to keep up to the latest innovations from Uber Engineering.
Subscribe to our newsletter to keep up from Uber Engineering.
Seahorse, Roatan, Honduras by Conor Myhrvold.Subscribe to our newsletter to the latest innovations from Uber Engineering.