memcachedのオプションにハマる

memcachedにアクセスするためのC言語APIには,オプションを設定する関数,memcached_behavior_set()があります.
memcached_behavior_get memcached_behavior_set

オプションの効果を検証しようと思い,ベンチマークをやってみました.
ベースとなるプログラムは,こんな感じ.

#include<libmemcached/memcached.h>
#include<stdio.h>
#include<string.h>
enum mode_type { SET, GET, DEL};
const int TOTAL_CONNECTIONS = 10;
int access_memcache(mode_type mode, int seed)
{
  memcached_return rc;
  memcached_st *memcached;
  memcached_server_st *memc_servers = NULL;
  uint32_t flags;
  char key[] = "keyXXXXX", val[] = "valXXXXX", *return_val;
  size_t value_length;
  memc_servers
        = memcached_server_list_append(memc_servers, "memcachedserver", 11211, &rc);
  memcached = memcached_create(NULL);
  rc = memcached_server_push(memcached, memc_servers);
  if(rc != MEMCACHED_SUCCESS)
  {
    printf("memcached_server_push error=[%s]\n",memcached_strerror(memcached, rc));
  }
  /* ここにあとからオプションをセットするコードを書く */
  switch(mode)
  {
    case SET:
      snprintf(key,sizeof(key),"key%05d",seed);
      snprintf(val,sizeof(val),"val%05d",seed);
      rc = memcached_set(memcached, key, sizeof(key),
             val, sizeof(val), (size_t)0, (uint32_t)0);
      if(rc != MEMCACHED_SUCCESS)
      {
        printf("memcached_set error=[%s] for KEY[%s] and VALUE[%s]\n",
                    memcached_strerror(memcached, rc), key, val);
      }else
      {
        printf("VALUE[%s] by KEY[%s] was set successfully\n",val, key);
      }
      break;
    case GET:
      snprintf(key,sizeof(key),"key%05d",seed);
      return_val = memcached_get(memcached, key, sizeof(key),
                              &value_length, &flags, &rc);
      if(rc != MEMCACHED_SUCCESS)
      {
        printf("memcached_get error=[%s] for KEY[%s]\n",
                         memcached_strerror(memcached, rc),key);
      }else
      {
        printf("VALUE[%s] by KEY[%s] was get successfully\n",return_val, key);
      }
      break;
    case DEL:
      snprintf(key,sizeof(key),"key%05d",seed);
      rc = memcached_delete(memcached, key, sizeof(key), (time_t)0);
      if(rc != MEMCACHED_SUCCESS)
      {
        printf("memcached_delete error=[%s] for KEY[%s]\n",
                          memcached_strerror(memcached, rc),key);
      }else
      {
        printf("VALUE by KEY[%s] was deleted successfully\n", key);
      }
      break;
  }
  memcached_server_list_free(memc_servers);
  memcached_free(memcached);
  return(0);
}
int main(int argc, char *argv[])
{
  int i = 1;
  mode_type mode;
  if(argc != 2)
  {
    printf("usage: %s SET|GET|DEL\n", argv[0]);
    exit(1);
  }else
  {
    if(strcmp(argv[1],"SET")==0) mode = SET;
    if(strcmp(argv[1],"GET")==0) mode = GET;
    if(strcmp(argv[1],"DEL")==0) mode = DEL;
  }
  for(i=0;i<TOTAL_CONNECTIONS;++i)
  {
    access_memcache(mode, i);
  }
  return(0);
}

コンパイルして,「実行ファイル名 SET」とやると値がセットされ,「実行ファイル名 GET」とするとキーと値のペアが表示され,「実行ファイル名 DEL」で削除してから「実行ファイル名 GET」とすると,NOT FOUNDのエラーが出ると思います.
TOTAL_CONNECTIONSが10くらいだと一瞬で終わってしまうので,試しに10000に設定し,表示は/dev/nullに飛ばしてtimeコマンドで時間を測定.
そしてネットワーク系のパフォーマンスに関係しそうなオプションは,MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS.
3つのオプションのオン・オフがあるので,全部で8パターンあります.がんばって全パターンをやってみます.
順次,こんな感じでオプションを設定し,プログラムを実行してみます.

  rc = memcached_behavior_set (memcached, 
             MEMCACHED_BEHAVIOR_NO_BLOCK, //memcached_behavior flag,
              1 //                  uint64_t data
             );

それでは,測定スタート.

(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(OFF,OFF,OFF)
# time ./実行ファイル SET > /dev/null
real    0m8.847s
# time ./実行ファイル GET > /dev/null
real    0m8.877s
# time ./実行ファイル DEL > /dev/null
real    0m8.819s
(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(ON,OFF,OFF)
# time ./実行ファイル SET > /dev/null
real    0m10.163s
# time ./実行ファイル GET > /dev/null
real    0m10.226s
# time ./実行ファイル DEL > /dev/null
real    0m10.173s
(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(OFF,ON,OFF)
# time ./実行ファイル SET > /dev/null
real    0m8.941s
# time ./実行ファイル GET > /dev/null
real    0m8.861s
# time ./実行ファイル DEL > /dev/null
real    0m8.899s
(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(OFF,OFF,ON)
# time ./実行ファイル SET > /dev/null
real    0m7.468s
# time ./実行ファイル GET > /dev/null
real    0m8.956s
# time ./実行ファイル DEL > /dev/null
real    0m7.595s
(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(ON,ON,OFF)
# time ./実行ファイル SET > /dev/null
real    0m10.250s
# time ./実行ファイル GET > /dev/null
real    0m10.140s
# time ./実行ファイル DEL > /dev/null
real    0m10.296s
(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(ON,OFF,ON)
# time ./実行ファイル SET > /dev/null
real    0m8.772s
# time ./実行ファイル GET > /dev/null
real    0m10.152s
# time ./実行ファイル DEL > /dev/null
real    0m8.675s
(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(OFF,ON,ON)
# time ./実行ファイル SET > /dev/null
real    0m7.579s
# time ./実行ファイル GET > /dev/null
real    0m9.030s
# time ./実行ファイル DEL > /dev/null
real    0m7.435s
(MEMCACHED_BEHAVIOR_NO_BLOCK,MEMCACHED_BEHAVIOR_TCP_NODELAY,MEMCACHED_BEHAVIOR_BUFFER_REQUESTS)=(ON,ON,ON)
# time ./実行ファイル SET > /dev/null
real    0m8.817s
# time ./実行ファイル GET > /dev/null
real    0m10.344s
# time ./実行ファイル DEL > /dev/null
real    0m8.716s


測定時間が短いので誤差も大きいでしょうが,この実験環境では,

  1. MEMCACHED_BEHAVIOR_NO_BLOCKはオフにした方が速い
  2. MEMCACHED_BEHAVIOR_TCP_NODELAYはよく分からない
  3. MEMCACHED_BEHAVIOR_BUFFER_REQUESTSはオンにした方が速い

という結果になりました.

ただし注意が必要で,MEMCACHED_BEHAVIOR_BUFFER_REQUESTSをオンにするとバッファリングされますので,リアルタイムにmemcachedには書き込まれません.実際,BUFFER_REQUESTSをオンにするとMEMCACHED_SUCCESSは返って来ず,上記のようなMEMCACHED_SUCCESSだけをチェックしているプログラムでは

memcached_set error=[ACTION QUEUED] for KEY[key00006] and VALUE[val00006]

と表示され,キューイングされたためにエラーになったかのような結果になってしまっています.MEMCACHED_BEHAVIOR_BUFFER_REQUESTSをオンにする場合は,MEMCACHED_SUCCESSだけでなくMEMCACHED_BUFFEREDもチェックしなければなりません.
例えば

  if(rc != MEMCACHED_SUCCESS && rc != MEMCACHED_BUFFERED)
  {
        printf("memcached_get error=[%s] for KEY[%s]\n",
                         memcached_strerror(memcached, rc),key);
  }

とか.

ちなみにこれは同僚が「memcachedに値を入れたはずなのに戻り値がSUCCESSになってないみたい」と気付き,libmemcachedのソースを見て知ったことです.私が無知でした.