最速KVS研究:memcached/mdcached/redis/Kyototycoon-Kyotocabinet

今回は,以下の条件でサーバを起動してアクセス速度を調べました.

  • クライアントマシン1台とサーバマシン1台をギガビットハブで結び,TCPv4アクセスの速度を調べる
  • サーバは4ワーカスレッド,ディスクへのスナップ機能はオフにする
  • クライアントはC言語APIに統一
  • キーは32バイト固定,バリューは2560バイトと41960バイトの2種類
  • サーバもクライアントもプロセス数は1とする
  • クライアントからのアクセスは,OpenMPを用いて1スレッドから16スレッドまで変化させる
  • データのsetを100000回または10000回繰り返し,その後getを同じ回数だけ繰り返す

まずはバリューの長さが短いケース.
データのsetもgetも,特にどのKVSでも差は出ないようです.


結果として,読み書きを足した場合でも差は見られません.


続いて,バリューの長さが40Kバイトと大きい場合.

まずデータのsetでは,redisとmdcachedが同時アクセス数に無関係に速いようです.


データのgetは,mdcachedが圧倒的に速いです.


結果的に,トータルではmdcachedが最速になっています.

各サーバのオプションは以下のような感じです.

memcached: 4スレッド
memcached -v -t 4


redis: VMスレッド数4,スナップオフ
vm-max-threads 4
redis-server


mdcached: ネットワークスレッド数4,スナップオフ
mdcached -i 0 -n 4 -d


Kyototycoon: メモリツリーDB+memcached互換プラグイン
ktserver -le -plsv /usr/local/libexec/ktplugservmemc.so -plex 'port=11211'

ただし,mdcachedは,レプリケーションもシャーディングもありません.
機能の違いについては,また次回まとめます.
Kyototycoon+Kyotocabinetについても,Web+DB PRESSで取り上げられたりファイナルファンタジーのバックエンドで使われていると発表があったりと話題なので,掘り下げたいです.凄く手軽にMapReduceが書けます.

各クライアントのプログラムは,以下のような感じです.

  • memcached/Kyototycoon+Kyotocabinet(libmemcached使用)
/*
 c++ -fopenmp -o memgetset-bench-memcached memgetset-bench-memcached.c -L/usr/local/lib -lmemcached -lmd -I/usr/local/include
*/

#include <libmemcached/memcached.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <md5.h>
#include <omp.h>

#define MAX_LOOP 100000
#define VAL_LEN_TIMES 256
#define REP 10
void set_key_val(MD5_CTX *con, int seed, char *key, char *val)
{
  sprintf(key,"stress-test-%010d",seed);
  for(int i=0;i<VAL_LEN_TIMES;++i)
  {
    sprintf(val+i*10,"%010d",seed);
  }
  MD5Init(con);
  MD5Update(con, key, strlen(key));
  MD5End(con, key);
}
int main(int argc, char *argv[])
{
  int suc = 0, i;
  time_t start, set_end, get_end;
  int n = omp_get_max_threads();
  memcached_st **pool = (memcached_st **)malloc(sizeof(memcached_st *)*n);
  for(i=0;i<n;++i)
  {
    memcached_server_st *servers;
    servers= memcached_servers_parse(argv[1]);
    pool[i] = memcached_create(NULL);
    memcached_server_push(pool[i], servers);
    memcached_server_list_free(servers);
  }
  start = time(NULL);
  memcached_return rc;
  MD5_CTX con;
  MD5_CTX con;
#pragma omp parallel for private(rc, con)
  for(i=0;i<MAX_LOOP;++i)
  {
    memcached_st *memc = pool[(int)i/(MAX_LOOP/n)];
    char key[33], val[10*VAL_LEN_TIMES+1];
    set_key_val(&con, (int)(i/REP), key, val);
    rc= memcached_set(memc,key, strlen(key), val, strlen(val), 0, 0);
    if (rc != MEMCACHED_SUCCESS)
    {
      fprintf(stderr, "memset: %s: memcache error %s\n",
            key, memcached_strerror(memc, rc));
      if (memc->cached_errno)
        fprintf(stderr, " system error %s\n", strerror(memc->cached_errno));
    }else
    {
      ++suc;
    }
    if(i%(MAX_LOOP/100) == 0) printf("set: %d\n",i);
  }
  set_end = time(NULL);
#pragma omp parallel for private(rc, con)
  for(i=0;i<MAX_LOOP;++i)
  {
    memcached_st *memc = pool[(int)i/(MAX_LOOP/n)];
    char key[33], *val, should_be[10*VAL_LEN_TIMES+1];
    size_t val_len;
    uint32_t flags;
    set_key_val(&con, (int)(i/REP), key, should_be);
    val = memcached_get(memc, key, strlen(key), &val_len, &flags, &rc);
    if (rc != MEMCACHED_SUCCESS)
    {
      fprintf(stderr, "memget: %s: memcache error %s\n",
            key, memcached_strerror(memc, rc));
      if (memc->cached_errno)
      fprintf(stderr, " system error %s\n", strerror(memc->cached_errno));
    }else if(strncmp(val, should_be, val_len) == 0)
    {
      ++suc;
    }else
    {
      fprintf(stderr, "value should be [%s] but returned [%s]\n",
                                 should_be, val);
    }
    free(val);
    if(i%(MAX_LOOP/100) == 0) printf("get: %d\n",i);
  }
  for(i=0;i<n;++i)
  {
    memcached_free(pool[i]);
  }
  get_end = time(NULL);
  printf("try=%d success=%d ratio=%f\n",MAX_LOOP,suc/2,(double)suc/2/MAX_LOOP*100.0);
  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n",
           set_end-start, get_end-set_end, get_end-start);
  return(0);
}
  • Redis(hiredis使用)
/*
 c++ -fopenmp -o memgetset-bench-redis memgetset-bench-redis.c -L/usr/local/lib -lhiredis -lmd -I/usr/local/include
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <md5.h>
#include <omp.h>
#include "hiredis/hiredis.h"

#define MAX_LOOP 100000
#define VAL_LEN_TIMES 256
#define REP 10
void set_key_val(MD5_CTX *con, int seed, char *key, char *val)
{
  sprintf(key,"stress-test-%010d",seed);
  sprintf(val,"%010d",seed);
  for(int i=0;i<VAL_LEN_TIMES;++i)
  {
    sprintf(val+i*10,"%010d",seed);
  }
  MD5Init(con);
  MD5Update(con, key, strlen(key));
  MD5End(con, key);
}
int main(int argc, char *argv[])
{
  int suc = 0, i;
  time_t start, set_end, get_end;
  int n = omp_get_max_threads();
  redisContext **pool = (redisContext **)malloc(sizeof(redisContext *)*n);
  struct timeval timeout = { 1, 500000 };
  for(i=0;i<n;++i)
  {
    pool[i] = redisConnectWithTimeout(argv[1], 6379, timeout);
    if (pool[i]->err)
    {
       printf("Connection error: %s\n", pool[i]->errstr);
       exit(1);
    }
  }
  start = time(NULL);
#pragma omp parallel for
  for(i=0;i<MAX_LOOP;++i)
  {
    redisContext *conn = pool[(int)i/(MAX_LOOP/n)];
    redisReply *rc;
    MD5_CTX con;
    char key[33], val[10*VAL_LEN_TIMES+1];
    set_key_val(&con, (int)(i/REP), key, val);
    rc = (redisReply *)redisCommand(conn,"SET %s %s", key, val);
    if (conn->err)
    {
      fprintf(stderr, "redisCommand: %s: set error %s\n",key, conn->errstr);
    }else{
      ++suc;
      freeReplyObject(rc);
    }
    if(i%(MAX_LOOP/100) == 0) printf("set: %d\n",i);
  }
  set_end = time(NULL);
#pragma omp parallel for
  for(i=0;i<MAX_LOOP;++i)
  {
    redisContext *conn = pool[(int)i/(MAX_LOOP/n)];
    redisReply *rc;
    MD5_CTX con;
    char key[33], *val, should_be[10*VAL_LEN_TIMES+1];
    size_t val_len;
    uint32_t flags;
    set_key_val(&con, (int)(i/REP), key, should_be);
    rc = (redisReply *)redisCommand(conn, "GET %s", key);
    if (conn->err)
    {
      fprintf(stderr, "redisCommand: %s: get error %s",key, conn->errstr);
    }else{
       val = rc->str;
     if(strncmp(val, should_be, val_len) == 0)
      {
        ++suc;
        freeReplyObject(rc);
      }else
      {
        fprintf(stderr, "value should be [%s] but returned [%s]\n",
                                   should_be, val);
        freeReplyObject(rc);
      }
    }
    if(i%(MAX_LOOP/100) == 0) printf("get: %d\n",i);
  }
  get_end = time(NULL);
  printf("try=%d success=%d ratio=%f\n",MAX_LOOP,suc/2,(double)suc/2/MAX_LOOP*100.0);
  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n",
           set_end-start, get_end-set_end, get_end-start);
  return(0);
}
  • mdcached(mdcachedオフィシャルライブラリ使用)
/*
 c++ -fopenmp -o memgetset-bench-mdcached memgetset-bench-mdcached.c -L/usr/local/lib -lmdcached -lmd -I/usr/local/include
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <md5.h>
#include <omp.h>
#include <mc_protocol.h>
#include <mc_client.h>

#define MAX_LOOP 100000
#define VAL_LEN_TIMES 256
#define REP 10
void set_key_val(MD5_CTX *con, int seed, char *key, char *val)
{
  sprintf(key,"stress-test-%010d",seed);
  for(int i=0;i<VAL_LEN_TIMES;++i)
  {
    sprintf(val+i*10,"%010d",seed);
  }
  MD5Init(con);
  MD5Update(con, key, strlen(key));
  MD5End(con, key);
}
int main(int argc, char *argv[])
{
  int suc = 0, i;
  time_t start, set_end, get_end;
  int n = omp_get_max_threads();
  struct mc_connection **pool = (mc_connection **)malloc(sizeof(mc_connection *)*n);
  char *err = NULL;
  char *server = argv[1];
  for(i=0;i<n;++i)
  {
    pool[i] = mc_connect_tcp(server, MC_DEFAULT_INET_PORT, TRUE, &err);
  }
  start = time(NULL);
  MD5_CTX con;
#pragma omp parallel for private(err, con)
  for(i=0;i<MAX_LOOP;++i)
  {
    struct mc_connection *conn = pool[(int)i/(MAX_LOOP/n)];
    char key[33], val[10*VAL_LEN_TIMES+1];
    set_key_val(&con, (int)(i/REP), key, val);
    //printf("%s:%s\n",key,val);
    int rc = mc_put_simple(conn, key, sizeof(key), val, sizeof(val), 0, &err);

    if (rc != MC_RESULT_OK)
    {
      fprintf(stderr, "mc_put_simple: %s: mdcached error %s\n",
            key, err);
    }else
    {
      ++suc;
    }
    if(i%(MAX_LOOP/100) == 0) printf("set: %d\n",i);
  }
  set_end = time(NULL);
#pragma omp parallel for private(con)
  for(i=0;i<MAX_LOOP;++i)
  {
    struct mc_connection *conn = pool[(int)i/(MAX_LOOP/n)];
    char key[33], *val, should_be[10*VAL_LEN_TIMES+1];
    size_t val_len;
    uint32_t flags;
    set_key_val(&con, (int)(i/REP), key, should_be);
    int rc = mc_get_simple(conn, key, sizeof(key), &val, &val_len, &err);
    if (rc != MC_RESULT_OK)
    {
      fprintf(stderr, "mc_get_simple: %s: memcache error %s\n",
            key, err);
    }else if(strncmp(val, should_be, val_len) == 0)
    {
      ++suc;
      free(val);
    }else
    {
      fprintf(stderr, "value should be [%s] but returned [%s]\n",
                                 should_be, val);
      free(val);
    }
    if(i%(MAX_LOOP/100) == 0) printf("get: %d\n",i);
  }
  for(i=0;i<n;++i)
  {
    free(pool[i]);
  }
  get_end = time(NULL);
  printf("try=%d success=%d ratio=%f\n",MAX_LOOP,suc/2,(double)suc/2/MAX_LOOP*100.0);
  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n",
           set_end-start, get_end-set_end, get_end-start);
  return(0);
}