strace実践編──Webアプリケーションは思ったとおりに動かない
最後はstraceの実践編です。
Perlで書いたWebアプリケーションの動作の妥当性は、
これらはいずれも有効なのですが、
こういった問題は得てして、
開発環境などであらかじめさらっとstraceの結果を見ておくと、
Perlで作られたWebアプリケーションで陥りがちなパターン
昨今の現場はさまざまなミドルウェアが使われているので一概には言えませんが、
- 接続先のノード数をあまり気にせず多数のkeyに対してget_
multiやset_ multiを実行するコードがデプロイされた結果、 リクエスト中で全ノードへの接続・ 再接続が発生するようになり応答が劣化する - アプリケーション側で生成しているkeyの値が間違っている、
あるいはアプリケーション間でずれており、 キャッシュを活用しているつもりが実は常にデータベースにフォールバックしている - 思ったよりキャッシュヒット率が低く、
実はデータベースにフォールバックしている
「俺ならそんなことはやらかさない」
これはあくまで憶測ですが、
- ライブラリ側でしれっとコンシステントハッシュ法による分割が行われるため、
利用者側であまり接続先のノード数 (Cache::Memcached::Fastの場合はnewで渡すserversの数) を意識しないことが多い - 同様の理由で、
それぞれのノードに対してどういうタイミングでTCPコネクションが張られるかを意識しないことが多い
以降では、
実例1:epollを使っているつもりが、select(2)を使っていた
AnyEventはPerlでイベント駆動なアプリケーションを書く場合によく使われるモジュールです。通常、
これはいささか極端な例ですが、
実例2:memcachedへのgetが連続で空振りしていた
あるWeb APIのレスポンスが、
22:45:34.517703 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[
{"get", 3}, {" cache_local:", 13}, {"keyword_list:1", 14},
{"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL
) = 32
22:45:34.537967 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[
{"get", 3}, {" cache_local:", 13}, {"keyword_list:1", 14},
{"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL
) = 32
22:45:34.558936 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[
{"get", 3}, {" cache_local:", 13}, {"keyword_list:1", 14},
{"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL
) = 32
22:45:34.579275 sendmsg(6, {msg_name(0)=NULL, msg_iov(4)=[
{"get", 3}, {" cache_local:", 13}, {"keyword_list:1", 14},
{"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL
) = 32
22:45:34.587428 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=
[{"get", 3}, {" cache_remote1:", 14}, {"keyword_list:1",
14}, {"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOS
IGNAL) = 33
22:45:34.608296 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=
[{"get", 3}, {" cache_remote1:", 14}, {"keyword_list:1", 1
4}, {"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOSI
GNAL) = 33
22:45:34.628673 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=
[{"get", 3}, {" cache_remote1:", 14}, {"keyword_list:1", 1
4}, {"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOS
IGNAL) = 33
22:45:34.649146 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=
[{"get", 3}, {" cache_remote1:", 14}, {"keyword_list:1", 1
4}, {"\r\n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOSI
GNAL) = 33
22:45:35.135129 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=
[{"get", 3}, {" cache_remote2:", 16}, {"bsf_err", 7}, {"\r\
n", 2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 28
22:45:35.155516 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{
"get", 3}, {" cache_remote2:", 16}, {"bsf_err", 7}, {"\r\n",
2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 28
22:45:35.176027 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{
"get", 3}, {" cache_remote2:", 16}, {"bsf_err", 7}, {"\r\n",
2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 28
22:45:35.196453 sendmsg(11, {msg_name(0)=NULL, msg_iov(4)=[{
"get", 3}, {" cache_remote2:", 16}, {"bsf_err", 7}, {"\r\n",
2}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 28
見たところ、
ここで種明かしをすると、
そのため、
このアプリケーションおよびモジュールについては、
- ほぼ毎回キャッシュミスになっているようなキャッシュはそもそも意味がない
- データベースのデータのキャッシュとして使われているデータならば、
getをリトライする意味はそもそもない (低確率で起こるエラーなら、 普通のキャッシュミスと同様に単にデータベースにフォールバックすればよいため)
という問題の合わせ技が起こっていたのですが、
以下に、
package Memcached::Retry;
use Sub::Retry qw/retry/;
for my $method (qw/get set add delete remove replace
prepand append cas gets incr decr/) {
no strict 'refs';
*{$method} = sub {
my ($self, @args) = @_;
# $self->{max_retry} の値はデフォルトでは3
my $ret = retry(
$self->{max_retry} + 1,
$self->{retry_interval},
sub {
$self->{memcached_client}->$method(@args);
},
sub {
my $do_retry = defined $_[0] ? 0 : 1;
return $do_retry;
}
);
return $ret;
};
}
package Memcached::Retry;
use Sub::Retry qw/retry/;
for my $method (qw/get set add delete remove replace
prepand append cas gets incr decr/) {
no strict 'refs';
*{$method} = sub {
my ($self, @args) = @_;
# $self->{max_retry_write} の値はデフォルトでは3
# $self->{max_retry_read} の値はデフォルトでは0
my $max_retry =
($method eq "get" || $method eq "gets") ?
$self->{max_retry_read} :
$self->{max_retry_write};
my $ret = retry(
$self->{max_retry} + 1,
$self->{retry_interval},
sub {
$self->{memcached_client}->$method(@args);
},
sub {
my $do_retry = defined $_[0] ? 0 : 1;
return $do_retry;
}
);
return $ret;
};
}
まとめ
今回は、
さて、