メモリリークの発見と固定
このポストは、私がどのようにメモリリークを見つけたか、どのように私はそれを修正したか、Googleのサンプル・ゴー・コードの類似した問題をどのように修正したか、そして、将来これを防ぐためにどのようにライブラリを改善しているかをレビューします.
The Google Cloud Client Libraries for Go 一般的にGoogleのクラウドAPIと接続するためにフードの下にGRPCを使用します.APIクライアントを作成すると、ライブラリはAPIへの接続を初期化し、呼び出しを開始するまで接続を開放します
あなたはメモリリークを取得します.根底にある接続は決してクリーンアップされません.
Googleはgithub reposの何百もの管理に役立つgithubオートメーションボットの束を持っています.我々のボットのいくつかは、彼らの要求をAGo server 走るCloud Run . 我々の記憶用法は古典的なのこぎりのメモリリークのように見えました:
追加してデバッグを開始
それから、ローカルでサーバーを構築して起動しました.
メモリが使用されているベースラインを取得するには
500リクエストを送信する
ウーホー!🎉🎉🎉
同じ頃、ユーザーは私たちの問題を提起したGo sample repo for Cloud , これはdocs用のgoサンプルのほとんどを含んでいますcloud.google.com . ユーザーは私たちが忘れたことに気づいた
私は同じことがいくつかの他の時代にポップを見ていたので、私は全体のレポを調査することを決めた.
私はどのように多くの影響を受けたファイルがあったの概算を始めた.使用
問題の規模を考えれば、オートメーションがいくつかあると思いましたworth it ラフスタートを得る.私はファイルを編集するために完全なオン・ゴー・プログラムを書きたくなかったので、bashにはまりました.
最初まで
そして、そのファイルのリストを
理解する
The
次に、私は
しかし、私はすべてを交換する必要はありません
アドレス範囲には、次のコマンドが適用される前にマッチする開始パターンと終了パターンが含まれます.この場合、スタートは
上記のエラー処理パターンを知ることから
一度自動的にすべてのサンプルを編集した、私は走った サーバアプリケーションでは、クライアントを実際に閉じる必要があります. は 複数以上あるか 一旦それが済んだら、私は去った180 files edited .
ビジネスの最後の順序は、これがユーザーにもう発生しないようにしようとしています.いくつかの方法があります. より良いサンプル.上記参照. より良いGodoc.私たちはライブラリジェネレータを更新するには より良いライブラリ.自動的にできる方法はありますか 私はあなたがGO、メモリリークについて少し学んだことを願っています.
The Google Cloud Client Libraries for Go 一般的にGoogleのクラウドAPIと接続するためにフードの下にGRPCを使用します.APIクライアントを作成すると、ライブラリはAPIへの接続を初期化し、呼び出しを開始するまで接続を開放します
Close
でClient
.client, err := api.NewClient()
// Check err.
defer client.Close()
クライアントは同時に使用するのが安全ですClient
閉じるこの動画はお気に入りから削除されています.しかし、あなたがそうしないならば、何が起こりますかClose
あなたがそうするべきクライアント?あなたはメモリリークを取得します.根底にある接続は決してクリーンアップされません.
Googleはgithub reposの何百もの管理に役立つgithubオートメーションボットの束を持っています.我々のボットのいくつかは、彼らの要求をAGo server 走るCloud Run . 我々の記憶用法は古典的なのこぎりのメモリリークのように見えました:
追加してデバッグを開始
pprof.Index
サーバへのハンドラmux.HandleFunc("/debug/pprof/", pprof.Index)
pprof
メモリ使用のような実行時プロファイリングデータを提供します.参照Profiling Go Programs 詳細については、ブログを移動します.それから、ローカルでサーバーを構築して起動しました.
$ go build
$ PROJECT_ID=my-project PORT=8080 ./serverless-scheduler-proxy
次に、リクエストをサーバに送りました.for i in {1..5}; do
curl --header "Content-Type: application/json" --request POST --data '{"name": "HelloHTTP", "type": "testing", "location": "us-central1"}' localhost:8080/v0/cron
echo " -- $i"
done
正確なペイロードと終点は、我々のサーバーに特有で、このポストに無関係です.メモリが使用されているベースラインを取得するには
pprof
データcurl http://localhost:8080/debug/pprof/heap > heap.0.pprof
出力を調べて、いくつかのメモリ使用量を見ることができますが、何もすぐに大きな問題として表示されます.$ go tool pprof heap.0.pprof
File: serverless-scheduler-proxy
Type: inuse_space
Time: May 4, 2021 at 9:33am (EDT)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top10
Showing nodes accounting for 2129.67kB, 100% of 2129.67kB total
Showing top 10 nodes out of 30
flat flat% sum% cum cum%
1089.33kB 51.15% 51.15% 1089.33kB 51.15% google.golang.org/grpc/internal/transport.newBufWriter (inline)
528.17kB 24.80% 75.95% 528.17kB 24.80% bufio.NewReaderSize (inline)
512.17kB 24.05% 100% 512.17kB 24.05% google.golang.org/grpc/metadata.Join
0 0% 100% 512.17kB 24.05% cloud.google.com/go/secretmanager/apiv1.(*Client).AccessSecretVersion
0 0% 100% 512.17kB 24.05% cloud.google.com/go/secretmanager/apiv1.(*Client).AccessSecretVersion.func1
0 0% 100% 512.17kB 24.05% github.com/googleapis/gax-go/v2.Invoke
0 0% 100% 512.17kB 24.05% github.com/googleapis/gax-go/v2.invoke
0 0% 100% 512.17kB 24.05% google.golang.org/genproto/googleapis/cloud/secretmanager/v1.(*secretManagerServiceClient).AccessSecretVersion
0 0% 100% 512.17kB 24.05% google.golang.org/grpc.(*ClientConn).Invoke
0 0% 100% 1617.50kB 75.95% google.golang.org/grpc.(*addrConn).createTransport
次のステップは、サーバーに要求の束を送信し、我々(1)は、メモリリークを再現し、(2)リークが何かを識別するかどうかを見ていた.500リクエストを送信する
for i in {1..500}; do
curl --header "Content-Type: application/json" --request POST --data '{"name": "HelloHTTP", "type": "testing", "location": "us-central1"}' localhost:8080/v0/cron
echo " -- $i"
done
収集と分析pprof
データ$ curl http://localhost:8080/debug/pprof/heap > heap.6.pprof
$ go tool pprof heap.6.pprof
File: serverless-scheduler-proxy
Type: inuse_space
Time: May 4, 2021 at 9:50am (EDT)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top10
Showing nodes accounting for 94.74MB, 94.49% of 100.26MB total
Dropped 26 nodes (cum <= 0.50MB)
Showing top 10 nodes out of 101
flat flat% sum% cum cum%
51.59MB 51.46% 51.46% 51.59MB 51.46% google.golang.org/grpc/internal/transport.newBufWriter
19.60MB 19.55% 71.01% 19.60MB 19.55% bufio.NewReaderSize
6.02MB 6.01% 77.02% 6.02MB 6.01% bytes.makeSlice
4.51MB 4.50% 81.52% 10.53MB 10.51% crypto/tls.(*Conn).readHandshake
4MB 3.99% 85.51% 4.50MB 4.49% crypto/x509.parseCertificate
3MB 2.99% 88.51% 3MB 2.99% crypto/tls.Client
2.50MB 2.49% 91.00% 2.50MB 2.49% golang.org/x/net/http2/hpack.(*headerFieldTable).addEntry
1.50MB 1.50% 92.50% 1.50MB 1.50% google.golang.org/grpc/internal/grpcsync.NewEvent
1MB 1% 93.50% 1MB 1% runtime.malg
1MB 1% 94.49% 1MB 1% encoding/json.(*decodeState).literalStore
google.golang.org/grpc/internal/transport.newBufWriter
本当にメモリのトンを使用して目立つ!それはリークが関係しているものの最初の表示です.我々のアプリケーションソースコードを見て、我々がGRPCを使用していた唯一の場所はGoogle Cloud Secret Manager :client, err := secretmanager.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create secretmanager client: %v", err)
}
私たちは決してclient.Close()
を作成し、Client
すべてのリクエストで!だから、私はClose
呼び出しと問題は消えました:defer client.Close()
私は修正を提出したautomatically deployed , そして、鋸歯はすぐに去りました!ウーホー!🎉🎉🎉
同じ頃、ユーザーは私たちの問題を提起したGo sample repo for Cloud , これはdocs用のgoサンプルのほとんどを含んでいますcloud.google.com . ユーザーは私たちが忘れたことに気づいた
Close
the Client
我々のサンプルの1つで!私は同じことがいくつかの他の時代にポップを見ていたので、私は全体のレポを調査することを決めた.
私はどのように多くの影響を受けたファイルがあったの概算を始めた.使用
grep
, を含むすべてのファイルのリストを得ることができますNewClient
スタイルの呼び出し、次に、そのリストを別の呼び出しに渡すgrep
含まないファイルをリストするにはClose
, テストファイルの無視$ grep -L Close $(grep -El 'New[^(]*Client' **/*.go) | grep -v test
おっ!コンテキスト用に207ファイルがありました.go
中のファイルGoogleCloudPlatform/golang-samples レポ.問題の規模を考えれば、オートメーションがいくつかあると思いましたworth it ラフスタートを得る.私はファイルを編集するために完全なオン・ゴー・プログラムを書きたくなかったので、bashにはまりました.
$ grep -L Close $(grep -El 'New[^(]*Client' **/*.go) | grep -v test | xargs sed -i '/New[^(]*Client/,/}/s/}/}\ndefer client.Close()/'
完璧?いいえ、それは仕事の量で巨大なデントを作るか?はい!最初まで
test
) 上記と全く同じです-おそらく、影響を受けたファイルの全てのリストを取得しますClient
でも決して電話しないClose
).そして、そのファイルのリストを
sed
実際の編集のために.xargs
コマンドを呼び出します.stdin
指定したコマンドに引数として渡されます.理解する
sed
コマンドは、通常、サンプルのようなものを見るのに役立ちますgolang-samples
repo(クライアント初期化後のインポートとすべての削除)// accessSecretVersion accesses the payload for the given secret version if one
// exists. The version can be a version number as a string (e.g. "5") or an
// alias (e.g. "latest").
func accessSecretVersion(w io.Writer, name string) error {
// name := "projects/my-project/secrets/my-secret/versions/5"
// name := "projects/my-project/secrets/my-secret/versions/latest"
// Create the client.
ctx := context.Background()
client, err := secretmanager.NewClient(ctx)
if err != nil {
return fmt.Errorf("failed to create secretmanager client: %v", err)
}
// ...
}
高いレベルで、クライアントを初期化し、エラーがあるかどうかを確認します.あなたがエラーをチェックするときはいつでも、閉じているカーリーブレースがあります}
). 私はその情報を使って編集を自動化した.The
sed
コマンドはまだダメです.sed -i '/New[^(]*Client/,/}/s/}/}\ndefer client.Close()/'
The -i
代わりにファイルを編集します.私はこれで大丈夫git
私が混乱するならば、私を救うことができます.次に、私は
s
挿入コマンドdefer client.Close()
推定クローズカーリーブレース直後}
) エラーをチェックすることから.しかし、私はすべてを交換する必要はありません
}
, 私は、呼び出しの後、最初のものが欲しいだけですNewClient
. そのためには、address range for sed
探す.アドレス範囲には、次のコマンドが適用される前にマッチする開始パターンと終了パターンが含まれます.この場合、スタートは
/New[^(]*Client/
, マッチングNewClient
呼び出しをタイプし、最後に,
) is /}/
, 次のカーリーブレースをマッチング.つまり、私たちの検索と置換はNewClient
閉じるこの動画はお気に入りから削除されています.上記のエラー処理パターンを知ることから
if err != nil
条件は正確に我々が挿入する場所ですClose
コール.一度自動的にすべてのサンプルを編集した、私は走った
goimports
書式を修正するにはそれから、それぞれの編集ファイルを通して正しいことを確認しました.Client
実際にclient
またはそれは何か他のですか?Client
to Close
? ビジネスの最後の順序は、これがユーザーにもう発生しないようにしようとしています.いくつかの方法があります.
Close
the Client
あなたがそれを終えたとき.参照https://github.com/googleapis/google-cloud-go/issues/3031 . Close
クライアント?フィナライザ?どのように我々はこれを行うことができますのアイデアがありますか?お知らせくださいhttps://github.com/googleapis/google-cloud-go/issues/4498 . pprof
, grpcとbash私はあなたが発見したメモリリークについてのあなたの話を聞くのが大好きだし、それを修正するために取ったもの!あなたが我々が我々を改善することができる方法についてのアイデアがあるならばlibraries or samples , 私たちは問題をファイリングしてお知らせください.Reference
この問題について(メモリリークの発見と固定), 我々は、より多くの情報をここで見つけました https://dev.to/googlecloud/finding-and-fixing-memory-leaks-in-go-1k1hテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol