はじめに
子育てメディア「トモニテ」でバックエンドやフロントエンドの設計・開発を担当している桝村です。
2023年8月1日、MAMADAYSはトモニテに生まれかわりました。
アプリのメイン機能である「育児記録」「妊娠週数管理」「食材リスト」を軸として、家族やパートナー、家族以外の人や社会との接点を作るためのシェア機能やコミュニティ機能などの拡充をめざしていきます。
今回は、Continuous Profiling を実施することができる Pyroscope を使用して、トモニテで運用している Go サーバーのメモリリークを調査・改善した話をしたいと思います。
以前、トモニテでEKSからECSに移行した話という記事において、EKS on EC2 から ECS Fargate への移行に伴い、厳密なリソース管理の必要性が生じ、メモリリークを検出した件の対応になります。
Pyroscope について
Pyroscope とは
Continuous Profiling を実施することができるオープンソースのプラットフォームです。
Continuous Profiling とは、ソフトウェアやアプリケーションが実行されている間、リアルタイムでパフォーマンスデータや実行情報を収集し、CPUやメモリ等のリソースをどこで多く消費しているか分析・チューニングする手法です。
2023年3月にデータ可視化ツール Grafana などを開発している Grafana Labs が Pyroscope を買収し、 オープンソース Grafana Pyroscope として統合されました。
本記事では Grafana Pyroscope の情報にも触れつつ、Pyroscope で Go サーバーのメモリリークを調査・改善した話となります。
Pyroscope でできること
- コード内のパフォーマンスに関連する問題やボトルネックを見つけることができます。例えば、CPU使用率の上昇やメモリリークの発生等です。
- タグ機能
Tags
や時間指定により、UI上のプロファイリングデータを絞り込みできます。
- ビュー機能
Comparison View
により、2つの時間区間を並べて比較分析できます。
- ビュー機能
Diff View
により、2つの時間区間の差分を取得してヒートマップのように色付けして比較分析できます。
Pyroscope の仕組み
Pyroscope Agent
という言語ごとに用意されているプロセスが、アプリケーションの動作を定期的に記録・集計し、そのデータを Pyroscope Server
に送信します。
Pyroscope Server
がそのデータを処理・集約することで、ユーザーがプロファイリングの結果を WEB UI から Flame Graph (フレームグラフ)を閲覧することが可能になります。
なので、Pyroscope で Continuous Profiling を実施するには、Pyroscope Agent
と Pyroscope Server
の設定が必要になります。
参考: pyroscope.io
Grafana Pyroscope について
2023年3月にデータ可視化ツール Grafana などを開発している Grafana Labs が Pyroscope を買収し、 オープンソース Grafana Phlare と統合され、 Grafana Pyroscope になりました。
参考: grafana.com
また、2023年8月末に Grafana Pyroscope として version 1.0 が公開されました。
このバージョンでは、以下をはじめとした改善が行われました。
Grafana と完全に統合され、Grafana ダッシュボードでメトリクスやログ、トレースなど他の可観測性の指標と一緒に Profiling のデータを表示可能になりました。
様々なオブジェクトストレージサービス (ex. AWS S3, Google Cloud Storage) との統合がサポートされ、プロファイルデータをストレージサービスに保存可能になりました。
水平方向のスケールアウトがサポートされ、あらゆる規模のプロジェクトで最適なパフォーマンスを出すことが可能になりました。
参考: github.com
Pyroscope を活用したトラブルシューティング
問題だったこと
トモニテで利用している Go サーバーの基盤である ECS のメモリ使用率が時間の経過とともに上昇し続けるという問題がありました。
もしサーバーのメモリ解放せず長時間経過した場合、サーバーの一時停止といったリスクがあったため、調査・改善が求められていました。
Pyroscope の導入
① Pyroscope Server
の起動
Pyroscope Server
をサーバー上で Docker 経由で起動しました。
Pyroscope によるリソース消費の最適化のため、データの保持期間 retention
、exemplars-retention
を調整し、それ以外の設定値は、デフォルトの値を利用しました。
② Pyroscope Server
と Go サーバーのネットワーク設定
それぞれでAWSアカウントが異なる構成だったのもあり、ネットワークの疎通のため、VPCピアリング接続やルーティングテーブルへのルートの設定をしました。
③ Pyroscope Agent
の起動
Profiling したいサーバーは Go 製なので、同様に Go の Pyroscope Agent を利用しました。
Pyroscope Server 側のリソース消費の最適化のため、SampleRate
を調整しました。
また、アプリケーションサーバー側の基盤が ECS なのもあり、バージョン単位でより詳細に分析できるように、Tags
にタスク定義のバージョンを設定しました。
// Init initialize func Init() { address := conf.PyroscopeAddress() if address == "" { return } pyroscopeConfig := pyroscope.Config{ ApplicationName: "tomonite-server", ServerAddress: address, SampleRate: conf.PyroscopeSampleRate(), Tags: map[string]string{ "taskArn": ecs.TaskARN(), "taskDefinitionVersion": ecs.TaskDefinitionVersion(), }, } if _, err := pyroscope.Start(pyroscopeConfig); err != nil { log.Alert(fmt.Errorf("failed to start profiling for pyroscope. err: %w", err)) } }
Profiling の結果
時間の経過とともに Go サーバー全体に対してメモリの使用率が著しく上昇している処理を特定することができ、その処理は、grpc-go
というモジュールであることが分かりました。
- ある時点A
- ある時点Aから12時間後
さらなる調査・改修対応
grpc-go
は Go サーバー側で明示的に利用されている実装箇所を発見できなかったため、メインのモジュールとの依存関係を調査しました。
すると、Cloud Firestore データベースの読み取りと書き込みのためのクライアントを提供する cloud.google.com/go/firestore
が依存していたことが判明しました。
$ go mod why -m google.golang.org/grpc
github.com/everytv/tomonite-server/db
cloud.google.com/go/firestore
google.golang.org/grpc
実際に firestore
周りのコネクションが保持されてそのままになっている処理があったので、コネクションを解放するため、(*firestore.Client).Close()
を追記してリリースしました。
その結果、Go サーバー全体に対するメモリの使用率の増加が顕著に緩やかになり、無事にメモリリークの問題を解決することができました。
おわりに
今回は Pyroscope と Continuous Profiling 、Pyroscopeを活用したトラブルシューティングの事例について紹介しました。
Pyroscope を利用するメリットとして、WEB UIで直感的に操作できたり、ビュー機能である Comparison View
や Diff View
でより効率的な分析が可能なところだと考えてます。
Continuous Profilingをしておくことで、問題発生後に Profiling 結果を得て、即座に調査が可能になります。今後も Pyroscope をうまく活用してサービスの安定稼働を実現していきます。
また、Grafana Labs による買収・統合に伴い、さらなる機能開発が見込まれるため、今後の開発の動向を注視しつつ最新バージョンへのアップグレードも検討できればと考えています。