10 年前、Google は非常に長い C++ コンパイル時間による重大なボトルネックに直面し、それを解決するためにまったく新しい方法を必要としていました。 Google のエンジニアは、Go (別名 Golang) と呼ばれる新しい言語を作成することで、この難題に取り組みました。 この新しい言語 Go は、C++ の最良の部分 (特にパフォーマンスとセキュリティ機能) を借用し、それを Python の速度と組み合わせて、同時実行を実装しながらマルチコアを迅速に使用できるようにしました。 これを行うには、解析段階が非常に複雑で、各ログラインサービスに対して大量のルールがロードされているため、非常に高速でなければなりません。 これが、私たちが Go 言語を使用することに決めた理由の 1 つです。

新しいサービスは現在、本番環境でフル稼働しており、素晴らしい結果が出ていますが、高性能のマシンで実行する必要があります。 この Go サービスは、8 つの CPU と 36 GB のメモリを備えた AWS m4.2xlarge インスタンスで実行され、毎日数百億以上のログが解析されます。

この段階では、すべてがうまく動いていることに満足して一日を終えることができましたが、それは Coralogix では適切な方法ではありません。 私たちは、より少ないもの (AWS インスタンス) を使って、より多くの機能 (パフォーマンスなど) を求めていました。 改善するためには、まず、ボトルネックの性質を理解し、どのようにしたらそれらを完全に削減または排除できるかを理解する必要がありました。

Golang プロファイリングを私たちのサービス上で実行し、CPU 消費量が高い原因を正確にチェックして、最適化できるかどうかを確認することにしました。 私たちは Go のバージョン v1.12.4 を使用していましたが、最新は 1.13.8 でした。 ドキュメントによると、1.13リリースでは、ランタイムライブラリと、主にメモリ使用量を利用する他のいくつかのコンポーネントが大きく改善されていました。 結論として、最新の安定版で作業することは有用であり、かなりの労力を節約できました →

したがって、メモリ消費量は約 ~800MB から ~180MB に改善しました。

次に、プロセスをより理解し、どこに時間とリソースがかかっているかを把握するために、プロファイリングを開始しました。 Go には ‘pprof’ という専用のツールがあり、アプリでルート (デフォルトのポート 6060) をリッスンして有効にし、http 接続を管理する Go パッケージを使用します:

import _ "net/http/pprof"

次に、メイン関数内またはルート パッケージで以下を初期化します:

go func() {log.Println(http.ListenAndServe("localhost:6060", nil))}()

ここでサービスを開始し

http://localhost:6060/debug/pprof

に接続できます。

pprof のデフォルトのプロファイリングは、CPU 使用率の 30 秒サンプリングになります。 CPU 使用率、ヒープ使用率およびその他のサンプリングを有効にするいくつかの異なるパスがあります。

私たちは CPU 使用率に焦点を当て、実稼働環境で 30 秒間のプロファイリングを行い、以下の画像に示すものを発見しました (注意: これは、Go のバージョンをアップグレードし、Go の内部パーツを最小にした後のことです)。

Go profiling – Coralogix

ご覧のように、多くの実行時パッケージ活動があり、それは特に GC 活動を示していました → 我々の CPU (最も消費された上位 20 オブジェクトのみ)のほぼ 29% は GC により使用されています。 Go GC は非常に高速でかなり最適化されているので、ベスト プラクティスはそれを変更または修正しないことであり、メモリ消費が非常に少なかったので (以前の Go バージョンと比較して)、主な疑いは高いオブジェクト割り当て率でした。 これは、より多くのメモリで補うことを余儀なくされます。

  • コードの中で、あまりにも多くのオブジェクトを割り当てる関数、領域、または行を見つけます。
  • インスタンス タイプを見ると、多くのメモリに余裕があることは明らかで、現在はマシン CPU に拘束されています。 そこで、その比率を入れ替えただけです。 Golangはその初期から、ほとんどの開発者が知らないGOGCと呼ばれるフラグを持っています。 このフラグはデフォルトが100で、単純にいつGCを起動するかをシステムに指示します。 デフォルトでは、ヒープがその初期サイズの100%に達したときにGCプロセスを起動します。 この値をより大きな値に変更すると、GCのトリガーを遅らせることができ、値を下げると、より早くGCがトリガーされます。 いくつかの異なる値でベンチマークを開始し、我々の目的のために最高のパフォーマンスは、使用時に達成されました。 GOGC=2000.

    この結果、メモリ使用量が ~200MB から ~2.7GB に増加し (Go バージョンの更新によりメモリ使用量が減少した後です)、CPU 使用率が ~10% 減少しました。
    以下のスクリーン ショットは、ベンチマーク結果を示しています:

    GOGC =2000 results – Coralogix benchmark

    CPU 消費のトップ 4 関数がサービスの関数は納得のいくものです。 GC の合計使用率は現在 ~13% で、以前の半分以下です (!)

    そこで停止することもできましたが、どこで、なぜそんなに多くのオブジェクトを割り当てているかを明らかにすることにしました。 多くの場合、それには正当な理由がありますが (たとえば、ストリーム処理の場合、メッセージを受け取るたびに新しいオブジェクトをたくさん作成し、次のメッセージには無関係なのでそれを取り除く必要があります)、簡単に最適化できる方法があり、オブジェクトの作成を劇的に減らせる場合もあります。

    手始めに、ヒープ ダンプを取るために、1 つの小さな変更で前と同じコマンドを実行してみましょう:

    http://localhost:6060/debug/pprof/heap

    結果ファイルを照会するために、ダンプを分析するためにコード フォルダー内で次のコマンドを実行することができます。

    go tool pprof -alloc_objects <HEAP.PROFILE.FILE>

    our snapshot looks like this:

    3 行目を除いてはすべて妥当なようです。 より深く理解するために、次のコマンドを実行しました:

    list <FunctionName>

    たとえば:

    list reportRuleExecution

    そして、次のようになりました:

    Leave a comment

    メールアドレスが公開されることはありません。