Optimizing M3: How Uber Halved Our Metrics Ingestion Latency by (Briefly) Forking the Go Compiler — Part4
M3の最適化:UberがGoがコンパイをフォークすることでメトリック収集のレイテンシーを半減する方法とは — 第四章
今回の記事は長いので四回に分けて投稿しています。今回は第四回目(最終章)です。最後に重要なポイントをまとめています。
Finding the smoking gun
これらの結果を見た後でも、パフォーマンスの低下の根本原因を十分に証明できていないと我々は感じました。 例えば、パフォーマンスの節約が、新しいgoroutinesやまだ完全には理解していない他のプロセスを絶えず生成する必要がないという結果に過ぎない場合はどうでしょうか?
そのころ、CockroachDBチームのエンジニアが大きなスタックサイズに関連する同様のパフォーマンスの問題に遭遇し、私たちもこのgithubの問題に直面し、Goコンパイラーをフォークして追加のインストルメンテーションを追加することでスタックの成長が原因であることを証明しました(読み取り :ステートメントを印刷します)。
同じことをすると決めましたが、フォークしたコンパイラを使用して実稼働サービスを構築することを計画していたため、過剰なロギングがサービスを遅くしすぎないように、print文のサンプリングを導入しました。 具体的には、goroutineがスタックを成長する必要があるたびに呼び出されるnewstack関数を変更しました。これにより、呼び出される1000回ごとにスタックトレースが出力され、どのコードパスがスタックの成長を引き起こしたかを確認できます。
次に、フォークしたGoコンパイラーと、まだパフォーマンスのレグレッションがあったコミットを使用して、サービスをコンパイルしました。 私たちはそれを本番環境に出荷し、それとほぼ同時にログを見始めました。このログは、問題のあるコードの周りでgoroutineスタックの成長が起こっていることを示しています。
問題のあるコードの周辺でスタックの増加が発生する傾向があることを示す証拠が得られました。この場合、goroutineスタックが4キビバイトから8キビバイトに増加しているように見えました。これは、リクエストごとに実行するための巨大な割り当てです。 しかし、それでもまだ十分ではありませんでした。 発生頻度と、レグレッションを導入したコードがスタックの増加を引き起こす可能性が高いかどうかを知る必要がありました。
今回は3つの異なるコミットを使用して、フォークされたコンパイラでサービスを再構築し、2分間で上記と同様のスタックの増加が発生した回数を測定しました。
|コミット |サンプリングされた平均発生回数|
|レグレッションを伴ったもの | 15,685 |
|レグレッションの修正を伴ったもの | 3,465 |
|新しいワーカープールを伴ったもの | 171 |
これらの測定値を手にしたことで、問題の原因が完全にあったこと、そして新しいワーカープールがこのような不正な問題が将来発生するのを防ぐことができると確信しました。 さらに重要なことは、問題を真に理解したので、ようやく夜安心して寝られるようになりました。
重要なポイント
M3でのエンドツーエンドのレイテンシー取り込みレグレッションの調査全体では、レグレッションを検出し、それを引き起こした根本原因を特定し、本番環境に修正を送るのに2人のエンジニアに調査してもらい、約1週間かかりました。 我々は、いくつかの重要な教訓を学びました。
- 困難な問題を特定しようとする場合、多くの場合、体系的なアプローチが必要です。 git bisectに従ったので、問題をコードの数行、3レベルのディペンデンシーの深さまで絞り込むことができました。
- 問題の原因となっている根本原因は、理解を深め、私たちの場合では、パフォーマンスを向上させます。 変更をロールバックして終わりにすることもできましたが、この場合、さらに進んで、エンドツーエンドの取り込みレイテンシーをレグレッションの前の半分に減らすことができました。 つまり、同じSLAを維持するために必要なハードウェアは半分で済みます。
- 自分のプログラミング言語の内部構造を深く理解することは、特にプロファイリングツールが不十分な場合(これは思っているよりも頻繁に行われます)、パフォーマンスの最適化を行うために重要です。
- Goでは、オブジェクトのプールは重要ですが、goroutinesをプールすることも重要です。
最後に、幸運なことに、Google GoエンジニアリングチームのメンバーがUberのNYC Go Meetupでこの問題に関する講演を見て、Go GitHubリポジトリーに問題をファイルするように頼みました。そして、ランタイムのプロファイリングを改善しました。 runtime.morestackで費やされた時間は、スタックの成長を引き起こした関数呼び出しに適切に起因するようになり、他のエンジニアが将来この問題をより簡単に診断できるようになります。
Orangesys.ioでは、kuberneteの運用、DevOps、監視のお手伝いをさせていただいています。ぜひ私たちにおまかせください。