memcachedのconn_yieldsはどういう時に増えるのか
memcachedの conn_yields
はどのような時に増えるのかという話です.なおmemcachedはテキストプロトコルかつデフォルトの設定で実行しているものとします (なお,この記事で重要なのは -R
がデフォルト,つまり20ということです).
memcached の conn_yields について - tokuhirom's blog
1つのコネクションでコマンドを発行しまくっている場合にここに到達するようだ
と,上記記事にあるとおりなんですが,実際にmemcachedのコードを読みながら試してみたという記録です.
TL;DR
1回のソケットで大量のコマンドを発行している場合にincrementされる.
getコマンドを何度も発行してみる
get 1
,get 2
,get 3
とクエリを何度も発行していくパターン.
use IO::Socket; my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 11211, Proto => 'tcp', ); $socket->print("get $_\r\nquit\r\n") for 1..100; my @lines = $socket->getlines; $socket->close;
この時は conn_yields
がincrementされない.
multi-getで大量のエントリを取得してみる
get 1 2 3...
と1つのgetで大量のエントリをひいてくるパターン.
use IO::Socket; my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 11211, Proto => 'tcp', ); my $keys = join ' ', map { $_ } 1..100; $socket->print("get $keys\r\nquit\r\n"); my @lines = $socket->getlines; $socket->close;
この時も conn_yields
はincrementされない.
1回のソケットで大量のコマンドを発行してみる
get 1\r\nget 2\r\nget 3\r\n...
と1回のリクエストに複数コマンドを詰めるパターン.
use IO::Socket; my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 11211, Proto => 'tcp', ); my $gets = join "\r\n", map {"get $_"} 1..100; $socket->print("$gets\r\nquit\r\n"); my @lines = $socket->getlines; $socket->close;
この場合は conn_yields
はincrementされる (memcachedのデフォルトの最大コマンド数の閾値は20なので,その場合は (100 - 20)/20 = 4
ということで conn_yields
は4増える).
コードを読んでみる
基本的に memcached.c
を読むとわかる.
memcached/memcached.c at 12ea2e4b50a3222412e9e1cffb1253d907c56cd5 · memcached/memcached · GitHub
注目すべきはこのwhileループで conn_yields
はこのループ内のこの部分でしかincrementされない.
で,どういう時に conn_yields
がincrementされるかというと,
nreqs
という変数が0を下回っていて- なおかつstateが
conn_new_cmd
の時にループが回った
という時にされる (このnreqs
というのは「1つのコネクションで何個のコマンドを許容するか」という閾値.memcachedコマンドの -R
オプション相当).
nreqs
はstateが conn_new_cmd
の時にループが回るとdecrement される(実装はこの辺).
つまりざっくり言うと,stateが conn_new_cmd
の時に何度もループが回ると conn_yields
が増加するという事になる.
stateが conn_new_cmd
になるのはどういう時かというと色々あるのだけれど,頻度が多いものとしては「コマンド実行後 (正確には結果出力後)」がある.
1コネクション中で大量のコマンドが送られてきた場合,
1. 送られてきた文字列をバッファしつつ読み込みparseする
2. コマンドを実行
3. stateがconn_new_cmdになる (nreqsのdecrement,条件を満足している時は conn_yields
のincrement)
4. まだ読み込んでいない文字列がある場合は1に戻る
という挙動をする (実装はこの辺).
例として get 1\r\nget2\r\n
という文字列が送られてきた時は get 1\r\n
を読み込み・実行した後に get 2\r\n
を読み込み,実行する.この時コマンドは2度実行されるので, nreqs
は -2
されている.
上記の不発だった例のように「getコマンドを何度も発行してみる」という場合はそれぞれのコマンド実行が別のコネクションになっているので,conn_new_cmd
時のループは高々1度しか回らない.よってnreqs
は-1しかされないので conn_yields
はincrementされない.
また「multi-getで大量のエントリを取得してみる」という場合は少し特殊で,送られてきた文字列を読み込む時にバッファ内に収まるのであればそれは通常のコマンド1発のように扱われる.バッファ内に収まらない時は一回読み込んでからstateを conn_waiting
に遷移し,更にそこから conn_read
に遷移することで続きの文字列を読み込む (読み込み切るまでこれを繰り返す).いくら文字列が長くてもコマンド1発としてみなされるので nreqs
は-1しかされない.よって conn_yields
はincrementされないということになる (実装はこの辺).
認識が足りなくて,getコマンドを何度も発行してみたり,multi-getで大量のエントリを取得してみたりした時でも conn_yields
がincrementされると勘違いしていて,動作検証時にハマったので調べてみました.
所感
Perlの場合,Cache::MemcachedやCache::Memcached::Fastを使っていれば1つのコネクションで複数コマンドを発行するケースは無さそうなので, conn_yields
のことは考えなくても良さそう?