Skip to content

Webアプリの入口を見渡そう

スロークエリログで、データベースの大渋滞はかなり解消できました。次は視点を一歩引いて、nginxの**access log(アクセスログ)**を見ます。

nginxは、ブラウザから来たリクエストを最初に受け取る案内係です。アクセスログには、どのURLが何回呼ばれ、返すまで何秒かかり、何byte送ったかを残せます。データベースを使わない処理も含めて、サービス全体を同じ物差しで比べられます。

今回見る数字

  • COUNT: そのURLが呼ばれた回数
  • SUM: 全リクエストの待ち時間を足した値
  • AVG: 1回あたりの平均時間
  • P90P99: 遅いほうから10%、1%付近の時間
  • SUM(BODY): 送ったデータ量の合計

平均だけを見ると、ごく一部の遅いリクエストを見落とします。反対に最大値だけを見ると、たまたま一度だけ遅かったものに振り回されます。回数、合計、平均、遅い側の値を並べて眺めましょう。

Agentへのプロンプト

text
次はnginxのaccess logを導入し、その情報だけから説明できる改善をしてください。

- 現在読み込まれているnginx設定と既存ログ形式を確認する
- method、URI、status、response bodyのbyte数、request_time、upstream_response_timeを1行JSONで記録する
- nginx -tで設定を検証してからreloadし、実際の1行がJSONとして読めることを確認する
- 集計ツールは既存のものを探し、必要なら公式配布元からchecksumを検証して導入する
- ベンチ前にaccess logを空にする
- UUIDやquery parameterの違いをまとめ、URLの役割ごとに集計する
- COUNT、SUM、AVG、P90、P99、SUM(BODY)を比較する
- まず変更前を計測し、観測と直接つながる変更だけを入れ、もう一度計測する
- status codeとレスポンス内容を壊していないか確認する
- スコアと、変更前後の回数・時間・転送量を記録する

ログだけでは原因を断定できないものは、推測で直さず「pprofで調べる候補」として残してください。
次はnginxのaccess logを導入し、その情報だけから説明できる改善をしてください。

- 現在読み込まれているnginx設定と既存ログ形式を確認する
- method、URI、status、response bodyのbyte数、request_time、upstream_response_timeを1行JSONで記録する
- nginx -tで設定を検証してからreloadし、実際の1行がJSONとして読めることを確認する
- 集計ツールは既存のものを探し、必要なら公式配布元からchecksumを検証して導入する
- ベンチ前にaccess logを空にする
- UUIDやquery parameterの違いをまとめ、URLの役割ごとに集計する
- COUNT、SUM、AVG、P90、P99、SUM(BODY)を比較する
- まず変更前を計測し、観測と直接つながる変更だけを入れ、もう一度計測する
- status codeとレスポンス内容を壊していないか確認する
- スコアと、変更前後の回数・時間・転送量を記録する

ログだけでは原因を断定できないものは、推測で直さず「pprofで調べる候補」として残してください。

今回のAgentは、アクセスログ集計ツールalpを公式リリースから導入しました。alpは、同じ役目のURLをまとめ、上の数字を表にしてくれます。ISUのUUIDが違うURLをそのまま数えると別々の行になるため、/api/isu/<UUID>/iconのようにまとめる設定も大切です。

入口から見えたもの

最初のアクセスログ計測は18,812でした。合計待ち時間順に並べると、上位はこうなりました。

URLの役目回数合計平均転送量
状態を送るPOST約70,0001,911秒27ms70byte
trend8,628307秒36ms38.8MB
ISUのアイコン4,341207秒48ms111MB
JavaScript、CSS、SVGなど各約8,700主要ファイルだけで1,100秒超21〜23ms-

状態POSTが圧倒的です。ただし、アクセスログから分かるのは「入口から出口まで待った」ことだけ。Goのどの処理が時間を使ったかはまだ分かりません。これはpprofで調べる候補に残しました。

一方、JavaScriptやCSSは、ベンチ中に内容が変わりません。それなのにGoアプリを通してファイルを読んでいます。ここはアクセスログだけでも、仕事を減らせる理由がはっきりしています。

静的ファイルはnginxへ

JavaScript、CSS、SVGとindex.htmlを、nginxから直接返すようにしました。変更前後でファイルのハッシュが同じことも確認しています。さらに、変わらないassetには1時間の有効期限を付けました。

再計測は19,628。assetの待ち時間は集計上0.000秒になりました。面白いのは、状態POSTのコードを変えていないのに平均が27msから11msへ下がったことです。同じサーバーのGoアプリが静的ファイルを返さなくなり、本来のAPIへCPUを使えるようになったためだと考えられます。

304は「中身を送り直さなくてよい」の合図

ETagは、ファイルにつける版番号のようなものです。ブラウザが持っている版と同じなら、サーバーは304 Not Modifiedだけを返せます。今回の/は、約1万回中9,655回が304でした。速いレスポンスを作るより、レスポンスを作らないほうが強い場面もあります。

次は、大きな画像を送らない

再計測したアクセスログでは、ISUのアイコンが4,763回、117MB送られていました。アイコンは登録後に変更するAPIがなく、同じ利用者へ同じ画像を何度も返しています。

そこで、認証が必要な画像であることを保つためprivateCache-ControlETagを付けました。サーバー全体で他人と共有するcacheにはしていません。

段階スコアアイコン取得アイコン転送量合計時間
client cache前19,6284,763回117MB118秒
client cache後20,3901,265回30.5MB26秒

約4分の1まで減り、スコアも伸びました。これでアクセスログから素直に選べる変更は一区切りです。残った最大の山、状態POSTの中身をpprofでのぞきに行きましょう。

WARNING

「読み取りだから全部cache」は危険です。利用者ごとに違うデータや、更新直後に見えなければならないデータもあります。公式マニュアルの整合性条件を確認し、何がいつ変わるかを説明できるものだけをcacheします。