strace for Perl Mongers
-
Upload
naosuke-yokoe -
Category
Technology
-
view
1.167 -
download
1
description
Transcript of strace for Perl Mongers
strace for PerlMongers
zentooo @ DeNA
自己紹介
@zentoo or @zentooo
「愛と勇気と缶ビール」
モバゲーオープンプラットフォーム
元・落語研究部
※トークにオチはありません
straceとはプロセス内で発行されているシステムコールをtraceするため
のツール
その前に、システムコールって何よ?
システムコールは、アプリケーションが叩くOSの "API"
File I/O (open, lseek, write, read, close)Network (socket, bind, listen, accept, connect)Memory (brk, sbrk, mmap)Process (fork, wait, kill)Thread (pthread_create)
システムコールを経由せずPerlでファイルI/Oできるか?
システムコール抜きでPerlで新しいプロセスを作れるか?
システムコール抜きでPerlでネットワークプログラミングが出来るか?
アプリケーションはOSの手の平の上
突然ですが
Q. Webアプリケーションの仕事って何よ?
A. HTTPリクエストを受けてHTTPレスポンスを返すことだ
よ!
Q. HTTPリクエストを受けてHTTPレスポンスを返すってど
ういうことだよ!
A. acceptしてreadしてwriteしてcloseだよバカヤロー!
まあ落ち着け
Webアプリのお仕事1. HTTPリクエストを受ける2. <この間に僕らのドラマがある>
MySQLへのアクセスmemcachedへのアクセス(データの整形、logの書き込みetc)
3. </この間に僕らのドラマがある>4. HTTPレスポンスを返す
システムコール語に翻訳すると...1. int fd = accept(...); // リクエスト受けて2. read(fd, ...); // リクエストの内容読んで3. <この間に僕らのドラマがある>
socket, connect, write, read, closeopen, write, read, close
4. </この間に僕らのドラマがある>5. write(fd, ...); // レスポンス返す
さらに省略すると1. int fd = accept(...); // リクエスト受けて2. read(fd, ...); // リクエストの内容読んで3. <僕らのドラマ />4. write(fd, ...); // レスポンス返す
DEMO #1 - just to strace
straceを読む時のキホン (1)↓システムコール名 返り値↓read(5, "GET / HTTP/1.0\r\nHost: xxx-xxxxxx"..., 131072) = 308 ↑引数(バッファなどの場合はよしなに展開される)
straceを読む時のキホン (2)fd (ファイルディスクリプタ) に注目する
↓コレread(5, "GET / HTTP/1.0\r\nHost: xxx-xxxxxx"..., 131072) = 308
fd (ファイルディスクリプタ)ファイルやネットワークソケットなどを抽象化した構造体へのポインタ(0, 1, 2) = (stdin, stdout, stderr)それ以降は昇順に振られる
fd with fileint fd = open("/tmp/yapc2014", ...);read(fd, buffer);write(fd, buffer);close(fd);
fd with client socketint fd = socket();connect(fd, addr, ...);read(fd, buffer);write(fd, buffer);close(fd);
fdを追うのが捜査の基本ファイルの生存期間
openで返ってきたfdがcloseに渡されるまでクライアントサイドでのソケットの生存期間
socketで返ってきたfdがcloseに渡されるまでサーバサイドにおけるクライアントソケットの生存期間
acceptで返ってきたfdがcloseに渡されるまで
Webアプリのstraceを読む時のキホン// こっからaccept(4, {sa_family=AF_INET, ...)}, [16]) = 5(中略)read(5, "GET / HTTP/1.0\r\nHost: xxx-xxxxxx"..., 131072) = 308(中略)write(5, "HTTP/1.1 200 OK\r\nDate: Sun, 10 A"..., 2218) = 2218// ここまでが一つのリクエスト/レスポンス// keepaliveしてないならここで close
DEMO #2 - strace web app
あくまで基本なので例外もTwiggy, Twiggy::Preforkなどevent-drivenなサーバ(epoll_wait)multi socket listenしてる時のStarlet (select)
Webアプリのお仕事(再掲)だいたいの場合 赤字の部分 で問題になる
HTTPリクエストを受ける<この間に僕らのドラマがある>
MySQLへのアクセスmemcachedへのアクセス(データの整形、logの書き込みetc)
</この間に僕らのドラマがある>HTTPレスポンスを返す
注目ポイント - MySQLsocket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 10fcntl(10, F_SETFL, O_RDONLY) = 0fcntl(10, F_GETFL) = 0x2 (flags O_RDWR)fcntl(10, F_GETFL) = 0x2 (flags O_RDWR)fcntl(10, F_SETFL, O_RDWR|O_NONBLOCK) = 0connect(10, {sa_family=AF_INET, sin_port=htons(3306), sin_addr=inet_addr("10.0.0.1"fcntl(10, F_SETFL, O_RDWR) = 0poll([{fd=10, events=POLLIN|POLLPRI}], 1, 4000) = 1 ([{fd=10, revents=POLLIN}setsockopt(10, SOL_SOCKET, SO_RCVTIMEO, "\2003\341\1\0\0\0\0\0\0\0\0\0\0\0\0"setsockopt(10, SOL_SOCKET, SO_SNDTIMEO, "\2003\341\1\0\0\0\0\0\0\0\0\0\0\0\0"setsockopt(10, SOL_IP, IP_TOS, [8], 4) = 0setsockopt(10, SOL_TCP, TCP_NODELAY, [1], 4) = 0setsockopt(10, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
注目ポイント - memcachedsocket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 8fcntl(8, F_GETFL) = 0x2 (flags O_RDWR)fcntl(8, F_SETFL, O_RDWR|O_NONBLOCK) = 0connect(8, {sa_family=AF_INET, sin_port=htons(11211), sin_addr=inet_addr("10.0.0.1"poll([{fd=8, events=POLLOUT}], 1, 250) = 1 ([{fd=8, revents=POLLOUT}])getsockopt(8, SOL_SOCKET, SO_ERROR, [-511598409602301952], [4]) = 0setsockopt(8, SOL_TCP, TCP_NODELAY, [1], 4) = 0sendmsg(8, {msg_name(0)=NULL, msg_iov(6)=[{"set", 3}, {" csrf_token:", 20}, read(8, 0x59f9f50, 1536) = -1 EAGAIN (Resource temporarily unavailable)poll([{fd=8, events=POLLIN}], 1, 300) = 1 ([{fd=8, revents=POLLIN}])read(8, "STORED\r\n", 1536)
DEMO #3 - strace web appwith DB and memcached
プログラムは思ったとおりに動かない
同じクエリを複数回発射してる思ってたよりcache missしてる無駄にretryしてるTCP Connection使いまわせてないTCP Connectionちゃんと切れてない
実例 #1 - memcached retryproblem
+180msとあるAPIのレスポンスが、リリース後に約180ms劣化50ms or dieなら4回くらい死なないといけないレベル
容疑者Cache::Memcached::Fastのラッパーモジュール
(更新差分があったので)
家宅捜索strace -p $pid -e 'trace=sendmsg'
(Cache::Memcached::Fastがsendmsgを使うのは知っていた)
物証sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 13}sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 13}sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 13}sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 13}sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote1:", sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote1:", sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote1:", sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote1:", sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote2:", sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote2:", sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote2:", sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_remote2:",
ネタばらしラッパーモジュールにretry機能 (maxで3回) が入っていたCache::Memcached::Fastのgetはcache missとその他のエラーを区別できないcache missの度に3回retryしてた
retry間隔は20msec20 msec × 3 (retry) × 3 = 180 msec
そのhit rateはどうなん?という話はありつつもget系はretryしない修正を入れて解決
物証 - with timestamp22:45:34.517703 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 22:45:34.537967 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 22:45:34.558936 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 22:45:34.579275 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, {" cache_local:", 22:45:34.587428 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, 22:45:34.608296 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, 22:45:34.628673 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, 22:45:34.649146 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, 22:45:35.135129 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, 22:45:35.155516 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, 22:45:35.176027 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3}, 22:45:35.196453 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{"get", 3},
straceしてみると一目瞭然嘘をつかないツールに頼る
嘘をつかないツールstracetcpdumpgdb
嘘をつくもの人の記憶アプリのログ時にはアプリのコード
実例 #2 - accept(2)thundering herd
What is thundering herd"The thundering herd problem occurs when a largenumber of processes waiting for an event are awokenwhen that event occurs, but only one process is able toproceed at a time." - Wikipedia多くのプロセスが特定のトリガーで同時に動き出すが、実際の処理は一個のプロセスでしか行われない (または行われるべき) 時にリソースが無駄になる問題よくあるのはデータのremote cacheがexpireした時に一気にDBへのクエリが走る、とか
accept(2) and prefork server複数プロセスが同時に一つのsocket fdをacceptし、それぞれブロックLinux kernelがよしなにqueueingしてくれるので、リクエストが来た時に起き上がるプロセスは一つだけ
(参考)http://d.hatena.ne.jp/naoya/20070311/1173629378
accept(2) and event-driven/multi-process server複数プロセスが同時に一つのsocket fdをepoll_waitで監視listen socketがread可能 = acceptが可能になった時点でepoll_waitしてる全てのプロセスが起き上がる一つのプロセスがacceptに成功他のプロセスは空振って失敗 (EAGAIN)
accept(2) thundering herdaccept(2)の空振りがプロセス数とリクエスト数の掛け算で発生
loadが上がるvmstatで見ると...
usとsyとcsの値が盛り上がる (50, 50, 100000とか)プロセス間のcontext switch増えすぎが起因かと思われる
DEMO #3 - EAGAIN and againand again
解決策そもそもがevent-drivenなんだしそんなに大量にプロセスあげなくていいよね深淵な理由により100プロセスくらいに (前は300くらいあった)
余談現状のアーキテクチャだと割とどうしようもない気がしている
Monocerosみたいにacceptするプロセスとリクエストを処理するプロセスを完全に分けるとか?
event-drivenとマルチプロセスは食い合せが悪い with PerlIO::AIO (AnyEvent::IOのbackend) とかも割とシングルプロセス前提な実装だったり...
まとめに入ります
straceのわるいところムダな情報がおおい (慣れればどうにでもなるが)たまに起こる系の事象には向いてない (Devel::KYTProfとかでログ出した方がよい?)
straceのよいところ事前設定が不要で、プロセスにattachするだけですぐ見れるWebアプリケーションの気持ちが分かる
straceだと分からないことシステムコールを伴わない純CPUバウンドな無駄な処理アプリケーションでの無駄loop, deep recursion効率の悪いアルゴリズムとか-d NYTProf
トランスポート層以下での問題tcpdumpとかtsharkとかngrepとか使いましょう
straceで分かることリクエスト受けて、レスポンス返すまでのフローデータアクセスの実態実際に何回memcachedからgetしてるか、何回DBみにいってるか実際にcacheされているdata実際に発行しているSELECT文
その他、開発者の予期していない挙動
結び当たり前だけどstraceは万能のツールじゃない使えば明日から捗るとかそんなこともないでも、使い方が分かっていればお手軽・強力Webアプリの気持ちが分かる
参考文献
安いし、電子書籍版(英語しかないけど...)おすすめ日本語版もあるけど1000ページ超、つまり書籍ではなく武器全部読む必要なし、リファレンスとしても便利
↑の本はおすすめだけど、突然読むのは敷居が高いのでとかの方が最初はいいかもし
れない
The Linux Programing Interface
ふつうのLinuxプログラミング
宣伝 (?)Web+DBの2014年10月号に記事(Perl Hackers Hub)を書かせ
て頂くことになりました
ご静聴ありがとうございました