シンプルなクエリに対する高速なDBは

ごくシンプルなクエリ,ただの変換テーブルのようなものに対して,どんなデータベースシステムが適しているのか比べてみました.

私はC/Ruby派なんですが,同僚の参考になるように,言語はPHPを使います.OSはLinuxのCent OS 5.1で.
マシンのCPUはXeon X3350で,ハードディスクは普通のSATAです.
比べるデータベースは,MySQLMemcached,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
で入れてしまう.

また,PHPmemcached対応にするために,

# 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)とネットワークタイプ(memcachedMySQL)の速度差が比べられないので,ネットワークタイプの方のコネクションを,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秒.

ここまでをまとめると,

アクセスにネットワークを通す場合

MYSQL+MYISAMMYSQL+InnoDBMYSQL+MEMORYMEMCACHEDQDBMBerkeley DB
処理時間275秒1035秒241秒6秒不可不可

アクセスにネットワークを通さない場合

MYSQL+MYISAMMYSQL+InnoDBMYSQL+MEMORYMEMCACHEDQDBMBerkeley DB
処理時間263秒1027秒239秒4秒1秒以内1秒以内

こう見ると,MYISAMが結構速いというか,MEMCACHEDがシンプルな場合は異様に速いというか,ネットワークを介さないローカルなハッシュDBの場合,専用アプリがやはり圧倒的に速いというのが分かりました.