その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

mcrouterはget-multiリクエストを個別のgetリクエストに分割する

mcrouterという,Facebookが作っているmemcachedの為のルータがあります.「ミクルーター」と発音するようです *1

mcrouterは多機能なルータであり,シンプルなルーティング (例えば乱択やhash basedなど) からfailoverを前提とした大規模なクラスタのルーティングまで様々なルーティングを行うことが可能です (Facebook内の数千台規模のmemcachedクラスタでも運用されているようです.参照: Facebookの数千台規模のmemcached運用について - ゆううきブログ).

さて,mcrouterの詳細や使い方の説明については他の資料に譲るとして,本記事ではmcrouterがmemcachedget-multi リクエストを各々複数の get リクエストに分解して宛先のmemcachedに送る挙動をするという話をします.

どういうことなのか

Multi-key GET Behavior · Issue #40 · facebook/mcrouter · GitHub

このissueそのままではあるんですが,検証してみましょう.

{
    "pools": {
        "sample": {
            "servers": [
                "localhost:11211"
            ]
        }
    },
    "route": {
        "type": "AllAsyncRoute",
        "children": [
            "PoolRoute|sample"
        ]
    }
}

この設定ファイルを食わせてmcrouterを起動して (e.g. mcrouter -f ./config.json -p 22122),更に宛先のmemcachedを起動します.memcachedmemcached -vvというふうに起動すると検証が楽 (Ref: memcached おすすめ起動オプションまとめ - blog.nomadscafe.jp).

しかる後に以下のようなコードを流し込んでみる.

use IO::Socket;

# mcrouter に get-multi
my $socket = IO::Socket::INET->new(
    PeerAddr => 'localhost',
    PeerPort => 22122,
    Proto    => 'tcp',
);

my $keys = join " ", map { $_ } 1..5; # 5件multi-get
$socket->print("get $keys\r\nquit\r\n");
my @lines = $socket->getlines;
$socket->close;

さてこの時にmemcachedのログを確認してみると

<30 get 1
>30 END
<30 get 2
>30 END
<30 get 3
>30 END
<30 get 4
>30 END
<30 get 5
>30 END

という感じで,get-multiリクエストではなく個別のgetリクエストが来ていることがわかります.

一方,memcachedに直接繋いだ時は当然get-multiでリクエストが飛びます.

use IO::Socket;

# memcached に get-multi
my $socket = IO::Socket::INET->new(
    PeerAddr => 'localhost',
    PeerPort => 11211,
    Proto    => 'tcp',
);

my $keys = join " ", map { $_ } 1..5; # 5件multi-get
$socket->print("get $keys\r\nquit\r\n");
my @lines = $socket->getlines;
$socket->close;
<31 get 1 2 3 4 5
>31 END

つまりmcrouterはget-multiリクエストを個別のgetリクエストにバラしてから宛先memcachedに送りつけているのだ!! (正確にはget-multiに限らず,multi系のオペレーションは全部分解される)

内部的にどうなっているのか?

主に関連する部分

詳細な実装に踏み込んで説明すると大変なので端折りますが

  • multi operationなリクエストはそれぞれバラしてcontextという単位にする (get-multiだったら個別のget contextにする)
  • 1リクエストあたりのcontext群はシリアライズする
  • シリアライズしたコマンド列をソケットに流し込む

という挙動をしているようです.

つまり先程の例 (1から5のkeyをget-multiする場合) では get 1\r\nget 2\r\nget 3\r\nget 4\r\nget 5\r\n というコマンドの塊をソケット越しで送るという動きになるようです.

懸念

memcachedのconn_yieldsはどういう時に増えるのか - その手の平は尻もつかめるさ

前回の記事で書いたように,1回のソケットで大量のコマンドを発行するとしきい値を超えた場合にはconn_yieldsが発生します.conn_yieldsが発生するとそのバッチリクエストの性能が劣化するという懸念があります.
従ってmemcached client側がget-multiで送っていると思い込んでいたコマンドは,実はmcrouter側で個別のgetリクエストに変換されており,key数が多い場合はconn_yieldsが多発するというような予期せぬ事態に陥ることがあります.




さてconn_yieldsによって本当に性能劣化するのか? という検証については次回行うこととします.