シンプルなクエリに対する高速なDBは
ごくシンプルなクエリ,ただの変換テーブルのようなものに対して,どんなデータベースシステムが適しているのか比べてみました.
私はC/Ruby派なんですが,同僚の参考になるように,言語はPHPを使います.OSはLinuxのCent OS 5.1で.
マシンのCPUはXeon X3350で,ハードディスクは普通のSATAです.
比べるデータベースは,MySQL,Memcached,QDBM,Berkeley DBとし,単純に文字列で(ユニークなキー,値)のペアを10万レコード用意し,1万回ランダムに選んだキーから値を取り出すまでの時間を測定しました.
各ソフトは特にチューニングせずに,ほとんどデフォルトのままのパラメータです.
準備
各ソフトのインストールから行きます.
まず,qdbm.
ここから取ってきます.
moved
ドキュメントを読まずに,ただ
./configure; make
とやると,次のようなエラーが出ます.
icc: command line error: invalid argument for option '-m'
正しく
make -f LTmakefile -j 4
とやると通ります.
次にmemcached.
ここから取ってきます.
memcached - a distributed memory object caching system
./configure --enable-threads; make; make install; make test
実際にデーモンを動かすには,単に
# memcached -d -u nobody -p /var/run/memcached.pid -m 2048 -vv 2>&1 > /var/log/memchached.log
とか.
それからMySQL.
INSTALL-BINARYの通りに,
# /usr/sbin/groupadd mysql; /usr/sbin/useradd -g mysql mysql; chown -R mysql .; chgrp -R mysql .; ./scripts/mysql_install_db --user=mysql; chown -R root .; chown -R mysql data; ./bin/mysqld_safe --user=mysql&
でOK.
Berkeley DBは,Linuxはデフォルトで入っているので,問題なし.
PHPを使うので,apacheは入れないとダメっぽい.シンプルに
./configure --enable-so; make ; make install
で入れてしまう.
# wget http://pecl.php.net/get/memcache-2.2.3.tgz
# tar xvf memcache-2.2.3.tgz
# cd memcache-2.2.3
# phpize
# ./configure
# make; make install
# ce php-5.2.6
# ./configure --with-qdbm=/usr/local --with-db4=/usr --with-mysql=/usr/local/mysql --with-apxs2=/usr/local/apache2/bin/apxs
# make; make install
とか.
PHPは
# ./configure --with-qdbm=/usr/local --with-db4=/usr --with-mysql=/usr/local/mysql --with-apxs2=/usr/local/apache2/bin/apxs
みたくして,各DBに対応させます.
測定
最初はMySQLで.基本プログラムはこんな感じです.
<?php define("MAX_LINE",100000); $conn = mysql_connect("127.0.0.1", "root",""); mysql_query("CREATE DATABASE IF NOT EXISTS BENCH"); mysql_query("USE BENCH"); // create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=INNODB"); // create index for speed up $result = mysql_query("CREATE INDEX mykey_index USING BTREE ON BENCH (mykey)"\ ); $start_time = time(); for($i=0;$i<MAX_LINE;++$i) { $key = sprintf("%010d",$i); $result = mysql_query("INSERT BENCH (mykey,myval) VALUES({$key},{$i})"); if (!$result) { echo "DB Error, could not insert values\n"; echo 'MySQL Error: ' . mysql_error(); } } for($i=0;$i<10000;++$i) { $q = sprintf("SELECT * FROM BENCH WHERE mykey=%010d",rand(0,MAX_LINE)); // print_r($q); $result = mysql_query($q); $row = mysql_fetch_array($result); // print_r($row); } $end_time = time(); printf("time=%d\n",$end_time-$start_time); // destroy an experiment table $result = mysql_query("DROP TABLE BENCH"); ?>
まず,一番スタンダードな,MYISAMエンジン+BTREEインデックス.
// create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=MYISAM"); // create index for speed up $result = mysql_query("CREATE INDEX mykey_index USING BTREE ON BENCH (\ mykey)");
約275秒.
パーティショニングをする.
// create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=MYISAM PARTITION BY KEY(mykey) PARTITIONS 10");
約287秒.ほとんど変わらない.
インデックスが効かないようなケースでも,パーティショニングは効かない.
次に,InnoDBエンジン+BTREEインデックス.
// create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=INNODB");
約1035秒.
結構遅くなったので,パーティショニングをする.
// create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=INNODB PARTITION BY KEY(mykey) PARTITIONS 10");
約1321秒.遅くなる...
InnoDBでも,パーティショニングでファイルが分割するよう,パラメータを設定してみると...
# ./bin/mysqld_safe --user=mysql --innodb_file_per_table=TRUE
約1472秒.もっと遅くなる...
それから,MEMORYエンジン+HASHインデックス.
// create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=MEMORY"); // create index for speed up $result = mysql_query("CREATE INDEX mykey_index USING HASH ON BENCH (m\ ykey)");
約241秒.
MEMCACHEDを使った場合.
<?php dl('memcache.so'); error_reporting(E_ALL); define("MAX_LINE",100000); $memcache = new Memcache; $memcache->connect('127.0.0.1', 11211); $start_time = time(); for($i=0;$i<MAX_LINE;++$i) { $key = sprintf("%010d",$i); $obj = new stdClass; $obj->str_attr = $key; $memcache->set($key, $obj, false, 0) ; } for($i=0;$i<10000;++$i) { $q = sprintf("%010d",mt_rand(0,MAX_LINE-1)); $result = $memcache->get($q); } $end_time = time(); printf("time=%d\n",$end_time-$start_time); $result = $memcache->flush(); ?>
約6秒...速い...
QDBMを使った場合.
<?php error_reporting(E_ALL); // phpinfo(); define("MAX_LINE",100000); define("DBF","./bench.qdbm"); unlink(DBF); $conn = dba_open(DBF,"n","qdbm"); $start_time = time(); for($i=0;$i<MAX_LINE;++$i) { $key = sprintf("%010d",$i); dba_insert($key, $key, $conn); } for($i=0;$i<10000;++$i) { $q = sprintf("%010d",mt_rand(0,MAX_LINE-1)); $result = dba_fetch($q, $conn); } $end_time = time(); printf("time=%d\n",$end_time-$start_time); dba_close($conn); ?>
約1秒...とてつもなく速い.
Berkeley DBを使った場合.
<?php error_reporting(E_ALL); phpinfo(); define("MAX_LINE",100000); define("DBF","./bench.db4"); unlink(DBF); $conn = dba_open(DBF,"n","db4"); $start_time = time(); for($i=0;$i<MAX_LINE;++$i) { $key = sprintf("%010d",$i); dba_insert($key, $key, $conn); } for($i=0;$i<10000;++$i) { $q = sprintf("%010d",mt_rand(0,MAX_LINE-1)); $result = dba_fetch($q, $conn); } $end_time = time(); printf("time=%d\n",$end_time-$start_time); dba_close($conn); ?>
約1秒.もう桁が違います.
余りに差があってファイルタイプ(qdbm,Berkeley DB)とネットワークタイプ(memcached,MySQL)の速度差が比べられないので,ネットワークタイプの方のコネクションを,UNIXソケットを使うようにしてみます.
MySQLはデフォルトでソケット/tmp/mysql.sockをソケットファイルとして通信に使えるので(もっと言えば,接続先ホストをlocalhostにすると勝手にUNIXソケットを見に行ってくれるので),まずMEMORYエンジン+HASHインデックス.
define("MAX_LINE",10000); $conn = mysql_connect("localhost", "root",""); mysql_query("CREATE DATABASE IF NOT EXISTS BENCH"); mysql_query("USE BENCH"); // create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=MEMORY");
約239秒.
MYISAMエンジン+BTREEインデックス.
define("MAX_LINE",10000); $conn = mysql_connect("localhost", "root",""); mysql_query("CREATE DATABASE IF NOT EXISTS BENCH"); mysql_query("USE BENCH"); // create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=MYISAM"); $result = mysql_query("CREATE INDEX mykey_index USING BTREE ON BENCH (mykey)"\ );
約263秒.
パーティショニングをする.
// create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=MYISAM PARTITION BY KEY(mykey) PARTITIONS 10");
約294秒.やっぱりIOの方がネックになっちゃってます.
InnoDB+BTREEインデックス.
$conn = mysql_connect("localhost", "root",""); mysql_query("CREATE DATABASE IF NOT EXISTS BENCH"); mysql_query("USE BENCH"); // create a key-value pair table for an experiment $result = mysql_query("CREATE TABLE IF NOT EXISTS BENCH (mykey CHAR(32),myval\ CHAR(32)) ENGINE=INNODB");
約1027秒.
次,memcahcedをUNIXソケット対応で立ち上げ,
# ./memcached -u nobody -p /var/run/memcached.pid -m 2048 -l localhost -s /tmp/memcached.sock -vv
そのソケットを参照します.
<?php dl('memcache.so'); error_reporting(E_ALL); define("MAX_LINE",100000); $memcache = new Memcache; $memcache->connect('unix:///tmp/memcached.sock', 0); $start_time = time(); for($i=0;$i<MAX_LINE;++$i) { $key = sprintf("%010d",$i); $obj = new stdClass; $obj->str_attr = $key; $memcache->set($key, $obj, false, 0) ; } for($i=0;$i<10000;++$i) { $q = sprintf("%010d",mt_rand(0,MAX_LINE-1)); $result = $memcache->get($q); } $end_time = time(); printf("time=%d\n",$end_time-$start_time); $result = $memcache->flush(); ?>
約4秒.
ここまでをまとめると,