現在JavaScriptの設定が無効になっています。このページを利用するにはJavaScriptの設定を有効にしてください。言語のプロファイル機能とネットワークコネクションにまつわるトラブルシューティングインフラエンジニア見習いの森本です。これまで数年ほどサーバーサイドエンジニアとして開発ばかりをしてきた人が最近インフラエンジニアになりました。ついての発表がありました。運用を担うチームに所属しており、本稿ではその業務の中で発生したトラブルシューティングについて紹介したいと思います。リークが起きているようなので調査するという課題の担当になりました。言語で開発されていてランタイムの状態情報をメトリクスとして取得しています。以下はその状態情報を時系列に取得してグラフ化したものの一部になります。グラフに着目してください。リークが発生しているようにみえます。この課題をどのように調査して解決したかについて本稿で紹介します。メモリ使用量が増えています。終了が行われず、その分のメモリ使用量が徐々に増えているのでは?と推測するのが普通ではないでしょうか。私もそのように考えて調査を始めました。いうランタイムのプロファイルを行うためのツールが標準ライブラリとして提供されています。言語で開発したアプリケーションのプロファイリングが簡単に行えます。メモリのプロファイルを取得できます。ここではメモリのプロファイルを取得して調査を進めます。サーバーを起動することで任意のタイミングでリモートからでもプロファイル情報を取得できます。コマンドを実行します。いった処理が数百KBのメモリを使っていることが分かります。このコマンドを定期的に実行するだけでもどの処理のメモリ使用量が増えているか、つまりメモリリークが起きている可能性がある場所を推測する手掛かりになります。メモリ使用量が徐々に増えていることが分かりました。言語の標準ライブラリの処理なのでこの処理の呼び出し元を調べることにしましょう。コールグラフを出力するのが最も直感的に分かりやすいのではないかと私は思います。フォーマットのコールグラフが出力されます。ファイルメモリプロファイルの精度を小さくしているのでコールグラフが複雑になっていますが、最もメモリ使用量の多いパスを辿ると良いでしょう。いう関数から呼び出されていることが分かります。このようにして、自分たちが開発しているアプリケーションのどの処理が起点となってメモリ使用量が増えているかを調べられます。プロファイル結果を取得することを考慮しても数日もあれば見分けがつくでしょう。ここまでは一般的なプラクティスとして広範囲のアプリケーションで適用できます。言語は標準のプロファイルツールが必要十分な機能を提供しています。そのため、こういったリソースの利用状況に関する調査を行う敷居が低いです。紹介されている記事が分かりやすいです。私もこれらの記事を参考にしながらプロファイリングを行いました。リーク、ひいてはメモリの浪費につながっているかが分かりました。いった呼び出しとなっていました。後ほど再現コードを紹介します。その現象が発生しているのでしょうか?ここでは私の考えたことや試したことをなるべく時系列に書いていきます。自分たちのアプリケーションコードを疑うまずは自分たちが開発しているアプリケーションのコードになにかしら問題があるのではないかと疑います。言語ほどの多くの開発者に使われている処理系や標準ライブラリに不具合があるとは考え難いです。そこでアプリケーションの該当処理のコードを読んでみました。チャンネルを使っている箇所などを注意深く調べましたが、特に問題なさそうです。開発環境を構築しました。コードを読んでも分からないので実際に動かしながらデバッグしてみることにしました。使って調査ツールを作りました。そのときに作ったツールが以下になります。使ったクライアントアプリこういうリクエストを送ったらどうなるか、を調べるために検証しながら機能を追加していきます。実際に私がリクエストを送って調べたのは以下のような内容です。プロファイル情報を監視しました。リークの再現条件が分かりません。大量にリクエストしているあいだはメモリ使用量が増えるといったことしか分かりません。リクエストを送っているのだからメモリ使用量が増えるのは当たり前の話です。ここではリクエスト中にメモリ使用量が増えて、リクエストが完了した後でもランタイムのガベージコレクションによって開放されない現象を再現したいわけです。上記のことをツール作ったり条件を変えたりしながら2-3日試しましたが、全く再現条件が分かりません。使って調査ツールを作りました。使うこと自体を疑い始めます。リクエスト終了後にクライアントがコネクションを保持したままクローズしなかったらどうなるんだろう?と思いつきました。した状態でコネクションを保持するように調査ツールを実装しました。数とメモリ使用量が解放されない現象を再現できました。メモリ使用量が増えることが分かりました。処理に必要なバッファ領域を確保しているようにみえます。クライアントとのコネクションの増加に伴い、バッファ領域が増えるのは処理として正しいように思えます。クライアントが実際にデータを通信していなくてもコネクションをクローズしない限り、バッファ領域を開放しないかどうかを検証するために以下の再現コードを書きました。この現象に興味がある方は試してみてください。issue_tls_block_reserveここまでの調査結果をまとめると、なんらかの理由によりクライアントとのコネクションがクローズされずに日々増えていっているのではないか、という推測がつきました。本質的な原因を追求するコネクション数の増加に気づかなかった理由の1つとして、サーバーのコネクション数がメトリクス監視の項目に含まれていなかったことが挙げられます。その後、日々のコネクション数を計測していくと、1つのサーバーで百数十程度のコネクション数が日々増えていることが分かりました。これが数千といった数であれば実運用にも影響があるため、もっと早くに気付いたことでしょう。このぐらいの数だと1-2ヶ月ほどサーバーを連続稼働した状態でないと実運用には影響が出ません。開発が活発なため、1ヶ月もまったく新バージョンをリリースしないというのは稀でした。そのため、新バージョンをリリースするときにサーバーを再起動してコネクション数がリセットされるため、この現象に気付くのが遅れてしまった次第です。ユーザー別リクエスト数や通信量が日ごとに増えているわけではありません。そのため、毎日ずっと通信しているクライアントが増えているとは考え難いです。ネットワークの背景知識も必要になっていきます。コネクションは誰がいつクローズするのかまずサーバーアプリケーションのコードからみていきましょう。メソッドがありません。これはサーバーアプリケーションのハンドラー処理ではレスポンスを書き込んだ後にコネクションのクローズをしないことを表しています。パケットを送ることでコネクションのクローズ処理が行われることが示されています。それでは今回のようにクライアントがクローズしないときにサーバーはどうすれば良いのでしょうか。keepaliveサーバーでコネクションを確立した状態からクローズする方法を調べていきます。調べると次の2種類がみつかると思うので簡単に説明します。使った調査過程でも出てきました。リクエスト/レスポンスをやり取りする仕組みです。