最速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が同時アクセス数に無関係に速いようです.
結果的に,トータルでは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); }