自分の経験をドキュメントに整理する目的で書き出し。
どうやって効率を良くするか、という観点ではなく、今のシステムのパフォーマンスはどの程度か、という事を把握する目的。
流れも関係なくざーっと書いているので随時構成は変更する。
内容も随時更新。
突っ込み歓迎。
Webサービスパフォーマンスとは
ここでは、以下に書くように狭義なものと広義なものを定義する。一般的な定義ではなくオレオレ定義なので注意。
狭義:ユーザーがアクセスしてからレスポンスが返ってくるまでの速度。
Webサイトやアプリが早い・遅いという時の一般的な指標。
そもそもこの段階でパフォーマンスが悪いとユーザー体験が非常に良くないもっさりとしたサイトとなる。
大体の目安として
数十ms以下 : 早い
100ms ~ 500ms : ちょっと遅いかな
500ms ~ 1s : もっさり
1s以上 : ヤバい
という感じ。ただし、モバイルアプリなどで非同期でAPIを叩く様なサービスの場合にはレスポンスが遅くてもユーザー体験が落ちない様に作る事は出来る。
遅くなる要因としてはいくつかあるが代表的な物を挙げる。
無駄な処理を多くしている
一度に必要以上の処理を行っている、キャッシュすれば良い部分でも毎回計算が走っている、などなど、余計な処理によってパフォーマンスが悪くなっている。
データベースを正しく利用出来ていない
大抵の場合、indexがうまく使えていなくクエリが遅い。サービスがグロースしてデータサイズが大きくなって来ると顕在化する場合がある。
ユーザーとサーバー間に物理的な距離がある
物理的にサーバーがアメリカにあって日本からアクセスする様な場合。往復するだけで100msくらいかかる。
ネットワークが遅い
ネットワークの品質の問題。モバイルの3G回線やネットワーク状況の悪い海外、地下など。
広義:サーバーのリソース使用状況も含めたパフォーマンス。少ないリソースで多くのリクエストをさばく事が目的。
処理が早くてもサーバーのリソースを必要以上に使っている場合にはパフォーマンスのチューニングが必要。
リソース
サーバーのリソースは代表的には以下の4つ
CPU
計算する人。脳みそ。
無駄にループが走っていて計算回数が多かったりするとここがネックになる。
基本的には、CPUが最終的なネックになって来る。
ハード的にはコア数がどんどん増えているのでマルチコアを考慮した使い方をしていく事が大事。
Memory
プログラムをロードしておくところ。机の広さに例えられる。
一つのプロセスを立ち上げるのに大量にメモリを使ったりするとコスト効率が悪くなる。
また、出来るだけプロセス間でメモリを共有出来るようにすると多くのプロセスが立ち上げられるようになる。
CPUにネックが無い場合、プロセス数の上限は大抵メモリで決まり、どのくらいリクエストがさばけるのかはプロセス数で決まるのでそこの見積もりは重要。
Disk IO
Diskの読み取り速度。
メモリに乗らない情報はDiskに書き出したり読み出したりするがそこの速度差は比較にならないほどDiskに書く方が遅いため、大量のIOが発生する様な処理を行うとそこで処理が詰まったりする。
Network
NetworkもDisk IOと同じ一面があり、Networkで詰まった結果、全体の処理が詰まったりする。
また、無駄に転送量が多い場合にはそこでも余計なコストが発生する。
パフォーマンス計測具体例
狭義なパフォーマンス
アクセスログからの計測
狭義な意味でのパフォーマンスに関しては、Webサーバーのアクセスログで計測可能。
nginx(ltsv形式)の例
time:2015-11-07T15:08:30+09:00 remote_addr:xx.xx.xx.xx request_method:GET request_length:1258 request_uri:/users/sample https: uri:/users/sample query_string:- status:200 bytes_sent:20628 body_bytes_sent:19686 referer:- useragent:Mozilla/5.0 (Linux; Android 4.4.4; SO-02G Build/23.0.B.1.59) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/34.0.0.0 Mobile Safari/537.36 forwardedfor:yy.yy.yy.yy request_time:0.150 upstream_response_time:0.150
upstream_response_time及び、request_timeがパフォーマンス。
request_timeがリクエスト全体の時間で、upstream_response_timeがnginxがリクエストを渡した先のレスポンスタイム。
これを計測する事により全体としてどの程度のユーザー体験になっているのかが分かる。
パフォーマンス毎に色づけしたアクセス数グラフ。
こうやって可視化する事によりパフォーマンスが悪いアクセスが全体のどのくらいを占めているのかが分かる。
引用(kibana4 nginxのアクセスログをレスポンスタイム別でグラフ化する) : http://qiita.com/kenjiszk/items/c3a5be34746d0f53ae97
ただし、むやみやたらにレスポンスの悪い物を潰す事が必ずしも効率が良いわけではなく、基本的には
レスポンスタイムの悪さ x リクエスト回数
によって改善対象を決めていくべき。
極端に言えば、深夜に一回だけバッチで叩かれる処理が30秒かかっていてもそれは運用上は問題ない場合が多い。
広義なパフォーマンス
広義なパフォーマンスを計測するにあたり以下の必要条件がある。
- CPU使用率を計測している
- メモリ使用率を計測している
- DiskIOの状況を計測している
- ネットワークの帯域を計測している
計測はしてしすぎる事は無いと思うので色々なメトリクスを取得しておくと良い。
(計測によってサービスのパフォーマンスに影響が出ない限り)
cpu使用率とサーバーの限界
単純計算によるおおざっぱな限界設定
以下の様な状況の場合。
1秒間に100リクエストをさばいた時のCPU使用率が25%だった。
CPUの上限を75%に設けた場合、1秒間に300リクエストまでさばけるだろうという試算が出来る。
当然この数字が多ければ多いほどCPUリソースを効率的に使っていると考えられる。
KPI化
限界を知る事と同時に、相対的にどの程度のパフォーマンスが出せているのかを知りたい場合には、1CPU当たりにさばいているアクセス数を追うと良い。
上の数字の例を使うと
100リクエスト ÷ 25% = 4
この数字を追う事によって、CPU効率の経年劣化、デプロイタイミングでのレスポンスの悪化などが検知出来るようになる。
メモリとプロセス数とパフォーマンス
前述したように、メモリはプロセスの数を決定する。
以下の様なunicornのプロセスを考える。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14227 ubuntu 20 0 723412 199568 11244 S 0.0 5.2 0:12.92 unicorn master -c /home/ubuntu/app/current/config/unicorn/production.rb -E production -D
14333 ubuntu 20 0 901884 508148 7372 S 0.0 13.2 5:41.44 unicorn worker[2] -c /home/ubuntu/app/current/config/unicorn/production.rb -E production -D
14324 ubuntu 20 0 895876 500848 7468 S 19.6 13.0 5:04.40 unicorn worker[0] -c /home/ubuntu/app/current/config/unicorn/production.rb -E production -D
14337 ubuntu 20 0 833188 368308 7376 S 0.0 9.6 5:23.65 unicorn worker[3] -c /home/ubuntu/app/current/config/unicorn/production.rb -E production -D
14329 ubuntu 20 0 745596 347348 7468 S 0.0 9.0 5:18.03 unicorn worker[1] -c /home/ubuntu/app/current/config/unicorn/production.rb -E production -D
RESが実際のメモリなので、全て足すと1.9G程度となる。
199568 + 508148 + 500848 + 368308 + 347348 = 1924220
上記の例だと、SHRが殆ど無視出来るほど小さいが、上のプロセスで言うと大体7Mくらい子プロセス間でメモリが共有されているので、1.9Gからそれを引くと本来のメモリ使用量となる。
単純計算をすると、サーバーにメモリが4G積んであれば残り2Gなので、あと4プロセスくらいは追加出来る計算となる。
プロセス数が決まるとさばけるアクセス数が決まる
アクセスログからレスポンスタイムが取得出来ていて、プロセス数をメモリ数に合わせて決めてあげると、トータルでどのくらいのリクエストがさばけるのか、が計算出来る。
以下の場合を考える。
レスポンスの平均時間 : 200ms
プロセス数 : 4
1秒間に1プロセスが処理出来るリクエスト数は
1000ms ÷ 200ms = 5リクエスト
プロセスが4つあるので、このシステムおいては1秒間に20リクエストを処理出来る計算となる。
ユーザーのクリック間隔を5秒だとすると同時に100人のユーザーのアクセスを受け付けられるという計算となる。
注意
この計算はレスポンスの平均値を使っているためある程度の誤差を含んでいる。
例えば、平均から大きく外れたパフォーマンスの悪いリクエストが一時的に大量に発行された場合にはさばけるユーザー数は少なくなる。
IOの限界
DiskIOは使用しているマシンによって上限が異なるので、自分が使っているマシンの限界を知っておく事が大事。
最近だとAWSを利用する事が多いと思うので、IOを気にする様なサーバーの場合には、IOPSを指定してEBSを利用すると良い。
手元のdockerにcapistranoでdeployしている時の様子をvmstatで観察する。
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 2846116 86716 592004 0 0 0 0 993 1530 12 14 74 0 0
1 0 0 2834712 86716 592004 0 0 0 0 975 1504 12 14 74 0 0
1 0 0 2824008 86716 592004 0 0 0 0 993 1531 13 13 74 0 0
1 0 0 2817452 86716 592004 0 0 0 0 1008 1518 12 14 74 0 0
1 0 0 2801844 86724 592004 0 0 0 1180 669 795 20 6 74 0 0
1 0 0 2761792 86724 592004 0 0 0 0 777 1020 21 4 75 0 0
0 1 0 2755536 87800 594472 0 0 1068 6504 2114 3666 5 8 75 13 0
1 0 0 2737108 88096 603800 0 0 9176 1824 2416 4018 10 10 74 7 0
1 0 0 2714664 88096 614592 0 0 10768 0 2433 4057 13 10 74 3 0
1 0 0 2700948 88096 615104 0 0 8 212 1675 2916 18 8 74 0 0
1 0 0 2694468 88096 615040 0 0 0 24 1356 2366 19 7 74 0 0
1 0 0 2692980 88096 615172 0 0 96 56 1335 2173 19 7 74 0 0
ディスクの書き込み量を見る場合には、bi boを見ると良い。
bi : デバイスから読み込み
bo : デバイスに書き込み
ディスクへの書き込みでcpuが待たされている状況を確認するにはcpuのwaをみる。
上の例だと一時的に処理が遅れている事が分かる。
理想型
- メモリを共有出来るだけ共有してプロセスを挙げられるだけ挙げる。
- CPUネックにする
基本的にチューニングを進めていくとCPUネックになっていく。
プロファイラ
実際に遅いリクエストが特定出来た場合に詳細を追う為のツール。
あとで詳細追記しようと思う。
可視化
td-agent, fluentd, kibana, elasticsearchで色々と可視化中。
再掲 : http://qiita.com/kenjiszk/items/c3a5be34746d0f53ae97
Tool達
よく使うツールとかまとめ
vmstat
cpu, memoryの状況確認するコマンド。
リソースが枯渇している様な状況では大体ここに変化が現れる。
top
起動しているプロセスを確認する。
サーバーが急に重くなったりした時にはとりあえずtopを打つ。
Shift + m でメモリ使用順に、
Shift + p でCPU仕様順にソート出来るので結構便利。
top自体も実はちょっと重かったりする。
pt-query-digest
MySQLで実際に発行されいているクエリを整形して出力してくれるツール。