Serf を動かしてみる

注意: Serf の勉強中ですのでおそらく誤りや勘違いがあります。


最近話題の Serf を試してみました。
いまいちよく分かっていないのですが動作させてみました。
個人的に出来たらいいなと思うこととしては、
– 障害の高速で確実な検知
– 障害を検知したら登録したスクリプトを走らせる
です。
今回、動作可能ノード一覧の取得と、障害検知までをやりました。イベントフックはまだです。

そもそも Serf とは、
– 生きているノードのリストの提供
– 障害検知とリカバリ
– イベントフック
を行ってくれる、分散ノード管理ツールとのことです(公式サイトより)。

SWIMと呼ばれるゴシッププロトコルをベースにしているとのことです。論文はこちら。著者は現在グーグルにいるようですね。

何はともあれ動かしてみます。

今回試した構成図は以下のようになります。
serf用に4台の物理マシン(=ノード)、観測用に1台の物理マシンを用意しました。合計5台で試してみます。
serf1~serf4は、それぞれの動いているノードで動いているserf agentです。
環境は、
– 4台の物理マシン: Debian
– 1台の観測用マシン: MacBook
です。

serf_example

さて、まずはそれぞれのノードに serf をインストールします。

$ cd 
$ wget https://dl.bintray.com/mitchellh/serf/0.3.0_linux_amd64.zip
$ unzip

# serf を動かします。
$ ./serf agent -bind=0.0.0.0:5000 -rpc-addr=0.0.0.0:7373

↑この操作を4台分行います。また、クライアントにおいてもインストールします。クライアントではserfを動かす必要はありません。SerfのNode Nameは一意である必要があります。つまり同じ名前を使っているとうまく行きません。

今回私が用いたマシンは以下のように命名しました。隣にIPアドレスがついていますがこれは個人によりけりだと思います。

– atom (= 192.168.0.144)
– pikachu (= 192.168.1.3)
– ratta (= 192.168.1.2)
– e5200 (= 192.168.1.118)

atomのターミナルで、「./serf join IPADDR:PORT」としてjoinします。具体的には以下のとおりです。
# pikachu node
$ ./serf join 192.168.1.3:5000
Successfully joined cluster by contacting 1 nodes.

# ratta node
$ ./serf join 192.168.1.2:5000
Successfully joined cluster by contacting 1 nodes.

# e5200 node
$ ./serf join 192.168.1.118:5000
Successfully joined cluster by contacting 1 nodes.

Serfにリーダーの概念はないようです(Decentralizedってあるからそりゃそうですが)が、ひとまずある特定のノードのターミナル上で、joinコマンドで他のノードを追加してみました。それぞれのノードから追加してみたいのですがどうやれば良いのかよく分かりません。。。(-> どうやら最初にjoinするときは、特定の存在しているメンバーに接続する必要があるようです。それは知っておく必要があるとのことです。P2Pでよく見られる最初の一歩問題ってやつですね。それ以降は、存在しているどのノードに接続してもOKのようです(http://www.serfdom.io/intro/getting-started/join.html より))

さて、ノード一覧を取得してみます。クライアントのマシンのターミナルでatomノードに問い合わせします。以下のようにしてみます。

$ ./serf members -rpc-addr=192.168.0.144:7373
atom    192.168.0.144:5000    alive    
pikachu    192.168.1.3:5000    alive    
ratta    192.168.1.2:5000    alive    
e5200    192.168.1.118:5000    alive    

無事、取得出来ました。もちろん、他のserfノードに問い合わせても同じ結果が得られます。

$ ./serf members -rpc-addr=192.168.1.118:7373
e5200    192.168.1.118:5000    alive    
atom    192.168.0.144:5000    alive    
pikachu    192.168.1.3:5000    alive    
ratta    192.168.1.2:5000    alive

もちろんCAP定理より明らかですが、これはAPであり、一貫性はweakです。

さて次に、障害検知をしてみます。たいへんナイーブなやり方ですが、ストップウォッチ片手にpikachuノードのUTPケーブルを引っこ抜いて、クライアントから「./serf members -rpc-addr=192.168.0.144:7373」を連打してみます。

結果、8.3sec程度で障害を検知しました。↓のように、pikachuがfailedになっています。

$ ./serf members -rpc-addr=192.168.1.118:7373
e5200    192.168.1.118:5000    alive    
atom    192.168.0.144:5000    alive    
pikachu    192.168.1.3:5000    failed    
ratta    192.168.1.2:5000    alive  

さて今度は逆にUTPケーブルを復活させてみます。
-> 5.1sec程度で復活しました↓。

$ ./serf members -rpc-addr=192.168.1.118:7373
e5200    192.168.1.118:5000    alive    
atom    192.168.0.144:5000    alive    
pikachu    192.168.1.3:5000    alive    
ratta    192.168.1.2:5000    alive  

もう一度抜いてみますと、7.3sec程度でfailedとなりました。このように検出できるのは面白いですね。UTPケーブル引っこ抜きが障害の代表例かと言うとかなり怪しいのでは思ったので、ごく普通にpikachuノードのserfプロセスを落として検出までの時間を計測してみました。
-> 8.1secでした。UTPケーブル引っこ抜きとあまり変わりありません。今度は復帰させてみました。
-> 16.8secでした。

面倒なので試行回数を増やすことはしませんが、5sec程度で検知したり20sec程度掛かったりといろいろでした。1分以上掛かったこともありました。公式サイトには、

Serf automatically detects failed nodes within seconds

http://www.serfdom.io/intro/


と書いていますが、数秒で検知と行きませんでした。もちろん私が何が間違ったコンフィグをしている可能性はあります。

という訳で今回は、

– Serf を動作させる
– 生きているノード一覧を取得してみる
– 障害検知してみる

を行いました。次回はイベントフックを試してみたいと思います。

補遺

Serfの構成図のgraphviz

graph serf {

layout="circo";

subgraph serf_cluster
{
node [style=filled];

serf1--serf2 [style="dashed"];
serf1--serf3 [style="dashed"];
serf1--serf4 [style="dashed"];
serf2--serf3 [style="dashed"];
serf2--serf4 [style="dashed"];
serf3--serf4 [style="dashed"];

label = "Serf Cluster";
}

client--serf1;
}

Apache ZooKeeper を Debian で使ってみる

とある事情により、Apache Zookeeper を使ってみました。

そもそも ZooKeeper とはなんぞやという感じですが、

ZooKeeper とは

ZooKeeper は、設定情報の保守、名前付け、分散同期化の提供、および各種グループサービスの提供を目的とした集中型サービスです。分散アプリケーションでは、こうした種類のサービスのすべてを何らかの形で利用しています。分散アプリケーションを実装する場合、実装のたびに大量の作業が発生し、それに伴って不可避的に生じるバグとレースコンディションの修正に追われるのが実状です。上に挙げた種類のサービスの実装は決して容易ではないので、アプリケーションでは当初はこれらのサービスの実装を適当に済ませることが多く、あとで変更が生じたりすると対応できずに管理が困難になります。これらのサービスが適切に実装されている場合でも、実装方法に違いがあると、アプリケーションを配置する段階で管理が複雑になります。ZooKeeper は、これらのサービスの本質的な部分を取り出し、集中型のコーディネーションサービスへの非常にシンプルなインタフェースとして提供することを目的に開発されています。

http://oss.infoscience.co.jp/hadoop/zookeeper/


要は分散管理のためのサービスですね。複数台のマシンを活用していて、コンフィグなどを一元化しておきたい・一元化しておいてクライアントから書き換えたい、などの要求がよくあると思います。ただし、もともとは Hadoop 由来のプロジェクトらしく、C か Java でのみネイティブアクセス可能のようです。きちんと使える Rest インターフェースはなさそうですね。

インストールからクライアント UI まで試してみます。OS は Debian を想定しています。事前に Java 等が必要かもしれません。apt-get で入れておいてください。

$ cd

# 2013-12-25時点の最新版です。とは言え、↓は2012年後半のものですね、枯れていると思って良いのでしょうか。
$ wget http://ftp.riken.jp/net/apache/zookeeper/stable/zookeeper-3.4.5.tar.gz

# 解凍します
$ tar zxvf zookeeper-3.4.5.tar.gz

$ cd zookeeper-3.4.5/

# conf/zoo.cfgというファイルを作ります。中身は、↓でcatしています。
$ vi conf/zoo.cfg
$ cat conf/zoo.cfg 
tickTime=2000
dataDir=/var/zookeeper
clientPort=2181

# ZooKeeper を走らせます
$ bin/zkServer.sh start
JMX enabled by default
Using config: /root/my_repos/zookeeper-3.4.5/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

# 走っているか確認します。下の例ではJavaと出ているのでOKです
$ netstat -natp | grep java
tcp6       0      0 :::35091                :::*                    LISTEN      2444/java       
tcp6       0      0 :::2181                 :::*                    LISTEN      2444/java 

# クライアントを使ってみます
$ bin/zkCli.sh -server 127.0.0.1:2181
Connecting to 127.0.0.1:2181
2013-12-25 10:58:55,527 [myid:] - INFO  [main:Environment@100] - Client environment:zookeeper.version=3.4.5-1392090, built on 09/30/2012 17:52 GMT
2013-12-25 10:58:55,533 [myid:] - INFO  [main:Environment@100] - Client environment:host.name=ratta
2013-12-25 10:58:55,534 [myid:] - INFO  [main:Environment@100] - Client environment:java.version=1.7.0_25
2013-12-25 10:58:55,536 [myid:] - INFO  [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
2013-12-25 10:58:55,537 [myid:] - INFO  [main:Environment@100] - Client environment:java.home=/usr/lib/jvm/java-7-openjdk-amd64/jre
2013-12-25 10:58:55,538 [myid:] - INFO  [main:Environment@100] - Client environment:java.class.path=/root/my_repos/zookeeper-3.4.5/bin/../build/classes:/root/my_repos/zookeeper-3.4.5/bin/../build/lib/*.jar:/root/my_repos/zookeeper-3.4.5/bin/../lib/slf4j-log4j12-1.6.1.jar:/root/my_repos/zookeeper-3.4.5/bin/../lib/slf4j-api-1.6.1.jar:/root/my_repos/zookeeper-3.4.5/bin/../lib/netty-3.2.2.Final.jar:/root/my_repos/zookeeper-3.4.5/bin/../lib/log4j-1.2.15.jar:/root/my_repos/zookeeper-3.4.5/bin/../lib/jline-0.9.94.jar:/root/my_repos/zookeeper-3.4.5/bin/../zookeeper-3.4.5.jar:/root/my_repos/zookeeper-3.4.5/bin/../src/java/lib/*.jar:/root/my_repos/zookeeper-3.4.5/bin/../conf:
2013-12-25 10:58:55,539 [myid:] - INFO  [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib/jni:/lib:/usr/lib
2013-12-25 10:58:55,540 [myid:] - INFO  [main:Environment@100] - Client environment:java.io.tmpdir=/tmp
2013-12-25 10:58:55,542 [myid:] - INFO  [main:Environment@100] - Client environment:java.compiler=<NA>
2013-12-25 10:58:55,543 [myid:] - INFO  [main:Environment@100] - Client environment:os.name=Linux
2013-12-25 10:58:55,544 [myid:] - INFO  [main:Environment@100] - Client environment:os.arch=amd64
2013-12-25 10:58:55,545 [myid:] - INFO  [main:Environment@100] - Client environment:os.version=3.5.0-43-generic
2013-12-25 10:58:55,546 [myid:] - INFO  [main:Environment@100] - Client environment:user.name=root
2013-12-25 10:58:55,547 [myid:] - INFO  [main:Environment@100] - Client environment:user.home=/root
2013-12-25 10:58:55,548 [myid:] - INFO  [main:Environment@100] - Client environment:user.dir=/root/my_repos/zookeeper-3.4.5
2013-12-25 10:58:55,550 [myid:] - INFO  [main:ZooKeeper@438] - Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@3b7db74b
Welcome to ZooKeeper!
2013-12-25 10:58:55,611 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@966] - Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
JLine support is enabled
2013-12-25 10:58:55,620 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@849] - Socket connection established to localhost/127.0.0.1:2181, initiating session
[zk: 127.0.0.1:2181(CONNECTING) 0] 2013-12-25 10:58:55,770 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1207] - Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x1432775d2d30000, negotiated timeout = 30000

WATCHER::

WatchedEvent state:SyncConnected type:None path:null

[zk: 127.0.0.1:2181(CONNECTED) 0] 

# ls, create, get, set 操作をしてみます。
# まず ls です
[zk: 127.0.0.1:2181(CONNECTED) 0] ls /
[zookeeper]

# create してみます
[zk: 127.0.0.1:2181(CONNECTED) 1] create /piyo fuga     
Created /piyo

# lsして新しく出来ているか確認します
[zk: 127.0.0.1:2181(CONNECTED) 2] ls /
[zookeeper, piyo]

# get してみます
[zk: 127.0.0.1:2181(CONNECTED) 3] get /piyo
fuga
cZxid = 0x2
ctime = Wed Dec 25 11:00:33 JST 2013
mZxid = 0x2
mtime = Wed Dec 25 11:00:33 JST 2013
pZxid = 0x2
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0

# set で先ほどの/piyoを書き換えて、getして確認してみます
[zk: 127.0.0.1:2181(CONNECTED) 4] set /piyo fugafuga
cZxid = 0x2
ctime = Wed Dec 25 11:00:33 JST 2013
mZxid = 0x3
mtime = Wed Dec 25 11:01:36 JST 2013
pZxid = 0x2
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0

[zk: 127.0.0.1:2181(CONNECTED) 5] get /piyo
fugafuga
cZxid = 0x2
ctime = Wed Dec 25 11:00:33 JST 2013
mZxid = 0x3
mtime = Wed Dec 25 11:01:36 JST 2013
pZxid = 0x2
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0

ls, create, get, set のみ試しましたが、うまくやれば使えそうですね。そして、よくあるファイルシステムとは違って、ZooKeeperのディレクトリというものは、それ自体が値と子ディレクトリの二つを持つようです。これを znode と呼ぶようですね。具体的には、たとえば /dir という znode は、値として value、子ノードとしてchild1, child2, child3を持ちます。クライアントを使って操作してみます。

# まず znode を確認してみます
[zk: 127.0.0.1:2181(CONNECTED) 63] ls /
[zookeeper]

# dir という znode を作ります。
[zk: 127.0.0.1:2181(CONNECTED) 64] create /dir value
Created /dir

# どういうものか get してみます。どうやら"value"という値が書き込まれているようです。
[zk: 127.0.0.1:2181(CONNECTED) 65] get /dir
value
cZxid = 0x1f
ctime = Wed Dec 25 11:15:31 JST 2013
mZxid = 0x1f
mtime = Wed Dec 25 11:15:31 JST 2013
pZxid = 0x1f
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

# 子 znode を3つ作ってみます
[zk: 127.0.0.1:2181(CONNECTED) 66] create /dir/child1 value-child1
Created /dir/child1
[zk: 127.0.0.1:2181(CONNECTED) 68] create /dir/child2 value-child2
Created /dir/child2
[zk: 127.0.0.1:2181(CONNECTED) 69] create /dir/child3 value-child3
Created /dir/child3

# dir を get してみます。numChildrenが3となっており、先ほど作ったznodeの数と一致します。
[zk: 127.0.0.1:2181(CONNECTED) 70] get /dir
value
cZxid = 0x1f
ctime = Wed Dec 25 11:15:31 JST 2013
mZxid = 0x1f
mtime = Wed Dec 25 11:15:31 JST 2013
pZxid = 0x22
cversion = 3
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 3

できていますね。普通のファイルシステムとは感覚が異なりますが、慣れでしょう。

DebianでのXHProfの使い方

XHProfとはPHPのプロファイリングツールです。いい感じですので備忘録的に書いておきます。

# いろいろとインストールしておきます
$ aptitude install php5-dev php-pear graphviz apache2

# XHProfをインストールします
$ pecl install channel://pecl.php.net/XHProf-0.9.4

# XHProf用のディレクトリを準備します
$ mkdir /var/log/xhprof
$ chmod -R 777 /var/log/xhprof/

# XHProfをphp.iniに書きます
$ echo 'extension=xhprof.so' >> /etc/php5/apache2/php.ini
$ echo 'xhprof.output_dir=/var/log/xhprof' >> /etc/php5/apache2/php.ini

# apache2をrestartします
$ service apache2 restart

$ cd /var/www
$ git clone https://github.com/facebook/xhprof.git

# phpinfoを出力します。ひとまず、http://localhost/phpinfo.php を訪れて、XHProfがあるか確認しましょう
$ echo '<?php echo phpinfo(); ' > phpinfo.php

# XHProfの具体例です。中身はこのページの最後の方に書いていますのでそちらを参照してください。
$ vi test.php

次に、ブラウザでそのページに訪れましょう。http://localhost/test.php などになるかと思います。もしうまくいかない場合は、less /var/log/apache2/error.log などして確認してみてください。

こんな感じになります↓
xhprof01
xhprof02

test.php
<?php

/* start xhprof */
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);

/* target */
$random_password = bin2hex(openssl_random_pseudo_bytes(12));
$salt = time();
$hash = '';
for ($i = 0; $i < 1000; $i++) {
	$hash = hash('sha512', $random_password . $salt . $hash);
}

echo $hash;

/* end */
$data = xhprof_disable();
require_once(dirname(__FILE__)."/xhprof/xhprof_lib/utils/xhprof_lib.php");
require_once(dirname(__FILE__)."/xhprof/xhprof_lib/utils/xhprof_runs.php");
 
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($data, "xhprof_testing");
header("Location: xhprof/xhprof_html/index.php?run={$run_id}&source=xhprof_testing\n");

MySQLでのユーザーとパスワードとデータベースの作成

覚書です。

まずルートで入る

usernameとpasswordを設定する(username: hoge, password: secretpasswordとします)

flushする

です。

# mysqlをport 8889で立ち上げているとします(MySQLのデフォルトのポートは3306です)。
$ mysql5 -h 127.0.0.1 -P 8889 -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 35
Server version: 5.5.29 Source distribution

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

root@localhost [(none)] > show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)

root@localhost [(none)] > GRANT ALL PRIVILEGES ON *.* TO hoge
    -> IDENTIFIED BY 'secretpassword' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

root@localhost [(none)] > FLUSH PRIVILEGES;

root@localhost [(none)] > CREATE DATABASE t01 CHARACTER SET utf8;
Query OK, 1 row affected (0.00 sec)

root@localhost [(none)] > use hoge
Database changed
root@localhost [hoge] > 

↑ただし、これだとlocalhostから繋ぐことが出来ません。。。外部からなら繋ぐことは出来ますが。。。localhostで繋ぎたい場合は、
mysql> GRANT ALL PRIVILEGES ON *.* TO hoge@'localhost' IDENTIFIED BY 'secretpassword' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

こんな感じです。MySQLのコンフィグで、bind-address = 127.0.0.1だとローカルからでしか繋がりませんので、bind-address = 0.0.0.0とするか、コメントアウトしましょう。

ユーザーを削除するには、

drop user hoge@'localhost';

ですね。

MySQLのベンチマーク

追記@2014-07-16
mysqlslapの他にもベンチマークソフトは様々あります。mybench: http://jeremy.zawodny.com/mysql/mybench/ もいいと思います。具体的には、自分が作ったスキーマに対してベンチをしたりでき柔軟です。mysqlslapと合わせて使うと良いかもです。
使い方は検索して下さい。


=== まとめここから ===

mysqlslapの結果まとめ

OS MySQL Version MySQL Engine CPU Storage Read (req/sec) Write (req/sec)
Debian 7.2 5.5.31 InnoDB Pentium DualCore E5200 Crucial m4 CT064M4SSD2 (64GB SSD) 27800.9 18892.8
Ubuntu 12.04.3 LTS 5.5.34 InnoDB Opteron 3280 Hitachi HDT721010SLA360 14806.0 9145.7
Ubuntu 12.04.3 LTS 5.5.34 InnoDB Celeron G540 ST500NM0011 39370.0 14359.5

やはりSSDは高速ですね。DBでは信頼性を確保するためにはどうしても不揮発性の媒体に書いておく必要があり、そのストレージで性能が決まるようです。不揮発性メモリが楽しみですね!

今回↑で使ったinsertのクエリの具体例は↓です。
INSERT INTO t1 VALUES 
(uuid(),1976594918,'xHogEXydBNC1CRXW0HkRAR8A2T2R04JxDzTKWRxai2buLyHMF1hQKicN3Wq3Q2sg1L0OhxyNrZADYRRnS0Doii3mCuhfoaJpnBDS0Bksdu59LYohYuL7cPlgBQJz0W')

ところで、Celeron G540 + ST500NM0011の組み合わせが健闘しています。読み込みはE5200 + SSDより高速ですし(メモリに全部載っていて、DRAMの速度の違いなのかもです、E5200の方はDDR2-800なので)、書き込みもHDDを使っているにも関わらずSSDの半分の速度が出ています。HDD自体の速度向上と、キャッシュによるものなのでしょうかね。

ふと思ったのですが、HDDやSSDにはDRAMのキャッシュが載っています(WD Blackですと64MBほど)。それに書き込んだ時点でOKを返すのでしょうか?だとしたらあまり信頼性が高くないような、、、という気がしてきました。その辺の議論ってどうなっているのでしょうね?確か某SSDですと、電源断が起こったときはコンデンサに貯めこんでおいた電気を使うはずです(なのでコンデンサが充電されていない状態で落ちるとデータは揮発します)。HDDだとどうなんでしょうね。まあトラブルは電源断だけではないので(OOM Killerに落とされるとか)、難しいところですが。。。

また、↑のベンチマークでは、Celeron G540でReadが3.9万req/sec、Writeが1.4万req/sec程度出ていて、早すぎないかと疑問に思いました。しかし、Amazon Relational Database Serviceによると、

データベースインスタンスごとに最大 3 TB のストレージ、30,000 IOPS をプロビジョニングできます。m2.4xlarge インスタンスで実行される 50% の書き込み、50% の読み取り作業負荷に対し、Oracle は最大 25,000 IOPS まで実現できます。cr1.8xlarge で実行している同様の作業負荷では、MySQL または PostgreSQL は最大 20,000 IOPS まで実現


とありますので、ほぼ一致と言えるでしょう。というのも、
>50% の書き込み、50% の読み取り作業負荷
を今回の私のベンチで言うと、3.9 * 0.5 + 1.4 * 0.5 = 2.65 万 [req/sec]となり、この計算結果は「最大 20,000 IOPS」と近い値だからです。

=== まとめここまで ===

MySQLのベンチマークをどのように取るかは悩ましいところだと思います。

– mysqlslapを使う
– アプリケーション側で記述してみる

などのやり方があると思います。アプリケーション側だとコンカレンシーを増やすなどがやりにくいので、基本的にはベンチマークツールを使いたいところですね。mysqlslapはデフォルトで付属しているツールなのですが、オプションが多すぎていまいち使い勝手がよくないです。。。

が、調べてみると丁寧に解説がなされたものをいくつか見つけました。

MySQL5.5と、mysqlslapについて

↑かなり参考にさせていただきました。

という訳でベンチマークを取ってみました。MySQLのコンフィグファイルの設定は山ほどあります。今回は速度と信頼性の中間ぐらいの、標準的なものにしてみました。

マシンA(ベンチマーク対象)
– DMI: System manufacturer System Product Name/P5KPL-CM, BIOS 0514 08/14/2008
– Intel Corporation 82G33/G31/P35/P31 Express DRAM Controller (rev 10)
– Intel Pentium(R) Dual-Core CPU E5200 @ 2.50GHz stepping 06
– M4-CT064M4SSD2
– Ethernet controller: Atheros Communications Inc. AR8121/AR8113/AR8114 Gigabit or Fast Ethernet (rev b0)
– Linux version 3.2.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 4.6.3 (Debian 4.6.3-14) ) #1 SMP Debian 3.2.51-1

マシンAの準備

# mysqlをインストール
$ sudo aptitude install mysql-server

# version確認
$ mysql --version
mysql  Ver 14.14 Distrib 5.5.31, for debian-linux-gnu (x86_64) using readline 6.2

# テスト用ユーザーの作成 (username: piyo, password: piyo)
$ mysql -u root -p

mysql> GRANT ALL PRIVILEGES ON *.* TO piyo
    -> IDENTIFIED BY 'piyo' WITH GRANT OPTION;

# my.confの編集 (後述)
$ sudo vi /etc/mysql/my.cnf

# restart
$ sudo service mysql restart

注意点として↑で作ったユーザーは、ローカルホストからはアクセスできません。なので他のマシンからアクセスしてください。localhostからアクセス出来るようにするには、↓を打ち込みます。
mysql> GRANT ALL PRIVILEGES ON *.* TO piyo@'localhost' IDENTIFIED BY 'piyo' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

さて、マシンBを用意します(mysqlをインストールしておいてください)。マシンAとはGbEスイッチングハブなどでつなぎます。

マシンBからベンチマーク(mysqlslap)を実行 (同時実行数は10,実行回数は1万回で合計10万リクエスト)
# 読み込み
$ mysqlslap --no-defaults --create-schema=SLAP --auto-generate-sql --auto-generate-sql-guid-primary --engine=InnoDB --number-int-cols=1 --number-char-cols=1 --concurrency=10 --auto-generate-sql-write-number=10000 --auto-generate-sql-execute-number=10000 --auto-generate-sql-load-type=key --iterations=3 --user=piyo --password=piyo --host=192.168.1.118 --port=3306
Benchmark
	Running for engine InnoDB
	Average number of seconds to run all queries: 3.563 seconds
	Minimum number of seconds to run all queries: 3.440 seconds
	Maximum number of seconds to run all queries: 3.649 seconds
	Number of clients running queries: 10
	Average number of queries per client: 10000

# 書き込み
$ mysqlslap --no-defaults --create-schema=SLAP --auto-generate-sql --auto-generate-sql-guid-primary --engine=InnoDB --number-int-cols=1 --number-char-cols=1 --concurrency=10 --auto-generate-sql-write-number=10000 --auto-generate-sql-execute-number=10000 --auto-generate-sql-load-type=write --iterations=3 --user=piyo --password=piyo --host=192.168.1.118 --port=3306
Benchmark
	Running for engine InnoDB
	Average number of seconds to run all queries: 5.071 seconds
	Minimum number of seconds to run all queries: 4.766 seconds
	Maximum number of seconds to run all queries: 5.344 seconds
	Number of clients running queries: 10
	Average number of queries per client: 10000

ちなみに↑を実行中のマシンAの状態です。
# 読み込み時
$ dstat
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
  9   3  87   2   0   0|  18k 2652k|   0     0 |   0     0 |1786  8163 
  0   0 100   0   0   0|   0     0 | 984B 1664B|   0     0 |  40    86 
 16   6  77   1   0   0|   0    88k| 596k  191k|   0     0 |5192  6927 
 20   9  71   1   0   0|   0  1764k| 809k  258k|   0     0 |6573  8770 
 40  12  46   1   0   2|   0  1844k|1694k 2289k|   0     0 |  17k   30k
 54  27  12   1   0   7|   0   588k|2988k 6586k|   0     0 |  31k   58k
 41  36  18   0   0   6|   0   168k|5698k   12M|   0     0 |  32k   60k
 38  37  21   0   0   5|   0   120k|3991k 8797k|   0     0 |  31k   58k
 20   5  73   2   0   1|   0   316k|1878k 2761k|   0     0 |6553  8910 
 19   4  77   1   0   0|   0  1852k| 630k  201k|   0     0 |5415  7226 
 27  10  60   2   0   2|   0  2148k| 790k  252k|   0     0 |  11k   18k
 54  28  12   0   0   6|   0  1196k|3888k 8531k|   0     0 |  32k   62k
 39  41  12   1   0   8|   0  1072k|3511k 7740k|   0     0 |  29k   53k
 38  43  13   0   0   6|   0   360k|5575k   12M|   0     0 |  30k   55k
 22  17  58   1   0   2|   0   452k|2005k 4030k|   0     0 |  13k   21k
 19   6  71   4   0   0|   0  7496k|1126k  360k|   0     0 |5898  7940 
 18   7  71   4   0   0|   0  5724k| 706k  226k|   0     0 |6081  8174 
 54  25  15   0   0   6|   0  1196k|3411k 7270k|   0     0 |  30k   58k
 45  34  15   1   0   5|   0   360k|3673k 8096k|   0     0 |  32k   58k
 36  42  18   0   0   4|   0   328k|3910k 8618k|   0     0 |  33k   61k
 20  15  64   0   0   1|   0   384k|3892k 8576k|   0     0 |  15k   28k
  0   0  99   1   0   0|   0   384k|  66B  354B|   0     0 |  67   127 
  0   1  99   1   0   0|   0   992k| 132B  468B|   0     0 | 103   208 
  1   0  95   4   0   0|   0  5192k| 132B  468B|   0     0 | 157   329 

# 書き込み時
$ dstat
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
  9   3  86   2   0   0|  18k 2643k|   0     0 |   0     0 |1806  8176 
  0   0 100   0   0   0|   0     0 | 444B 1322B|   0     0 |  38    87 
 17  10  72   1   0   1|   0    84k| 773k  247k|   0     0 |6330  8465 
 19   9  71   1   0   0|   0  1528k| 803k  256k|   0     0 |6519  8707 
 40  16  38   2   0   4|   0  5924k|3490k 1493k|   0     0 |  11k   19k
 54  19  15   8   0   5|   0    20M|3751k 1201k|   0     0 |  13k   25k
 52  16  18  10   0   5|   0    21M|5419k 1735k|   0     0 |  12k   23k
 55  22  14   5   0   5|   0    18M|4257k 1362k|   0     0 |  14k   27k
 52  18  19   7   0   5|   0    17M|3452k 1106k|   0     0 |  13k   24k
 33   9  50   6   0   2|   0    14M|4277k 1369k|   0     0 |8310    14k
 19   9  71   2   0   0|   0  2232k| 784k  251k|   0     0 |6441  8622 
 23   9  65   2   0   1|   0  3668k|1194k  381k|   0     0 |6888  9461 
 46  13  22  15   0   4|   0    13M|3164k 1391k|   0     0 |  11k   20k
 52  19  18   7   0   4|   0    18M|5428k 1739k|   0     0 |  13k   24k
 50  17  20   9   0   4|   0    22M|3943k 1262k|   0     0 |  12k   23k
 55  19  15   6   0   5|   0    11M|3515k 1126k|   0     0 |  14k   26k
 47  14  23  12   0   4|   0    23M|4953k 1588k|   0     0 |  11k   21k
 34  14  47   4   0   2|   0  7588k|2844k  910k|   0     0 |9035    15k
 20   8  68   4   0   1|   0  6384k|1075k  344k|   0     0 |6312  8416 
 20   9  69   3   0   0|   0  5024k| 817k  261k|   0     0 |6422  8589 
 49  20  20   7   0   3|   0    17M|4472k 1811k|   0     0 |  13k   23k
 53  21  16   6   0   6|   0    18M|4299k 1376k|   0     0 |  14k   25k
 55  18  16   7   0   5|   0    18M|3877k 1241k|   0     0 |  14k   25k
 52  16  20   8   0   5|   0    19M|5826k 1865k|   0     0 |  13k   23k
 53  20  15   7   0   5|   0    14M|3736k 1196k|   0     0 |  13k   25k
  3   1  94   3   0   0|   0  5668k|1576k  504k|   0     0 | 459   800 
  1   0  98   1   0   0|   0  2684k|  66B  354B|   0     0 | 109   198 
  0   0  99   1   0   0|   0  1432k|  66B  354B|   0     0 |  63   117 
  1   0  99   1   0   0|   0   824k|  66B  354B|   0     0 | 110   197

↑読み込み時にもけっこうなdisk writeがありますが、WAL(Write Ahead Log)なのでしょうか。selectでは意味がなさそうですが。。。それと、↑を見ると、MySQLプロトコルが古いのかなという気がします、というのもRedisあたりのベンチマーク中にdstatを見ますと、読み込みと書き込み時では、net/totalの桁が異なるからです。具体的には読み込み時はrecvがわずかで、sendが大きくなります。

ちなみに、いろんなマシンで計測した結果を一番上の表にまとめておきます。

my.cnfの内容を以下に置いておきます。速度と信頼性の中間を取った感じです。デフォルトから書き換えた・追加した項目は以下の4項目です。

– bind-address = 0.0.0.0
– innodb_buffer_pool_size = 1G
– innodb_flush_log_at_trx_commit = 2
– sync_binlog = 1
– innodb_flush_method = O_DIRECT

bind-addressについてですが、127.0.0.1だとローカルホストからのみアクセス可能です。ですので0.0.0.0としてグローバルで取れるようにしておきます。その他の↑のオプションについては検索してみてください。

my.conf
#
# The MySQL database server configuration file.
#
# You can copy this to one of:
# - "/etc/mysql/my.cnf" to set global options,
# - "~/.my.cnf" to set user-specific options.
# 
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

# This will be passed to all mysql clients
# It has been reported that passwords should be enclosed with ticks/quotes
# escpecially if they contain "#" chars...
# Remember to edit /etc/mysql/debian.cnf when changing the socket location.
[client]
port		= 3306
socket		= /var/run/mysqld/mysqld.sock

# Here is entries for some specific programs
# The following values assume you have at least 32M ram

# This was formally known as [safe_mysqld]. Both versions are currently parsed.
[mysqld_safe]
socket		= /var/run/mysqld/mysqld.sock
nice		= 0

[mysqld]
#
# * Basic Settings
#
user		= mysql
pid-file	= /var/run/mysqld/mysqld.pid
socket		= /var/run/mysqld/mysqld.sock
port		= 3306
basedir		= /usr
datadir		= /var/lib/mysql
tmpdir		= /tmp
lc-messages-dir	= /usr/share/mysql
skip-external-locking
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address		= 127.0.0.1
bind-address = 0.0.0.0

#
# * Fine Tuning
#
key_buffer		= 16M
max_allowed_packet	= 16M
thread_stack		= 192K
thread_cache_size       = 8
# This replaces the startup script and checks MyISAM tables if needed
# the first time they are touched
myisam-recover         = BACKUP
#max_connections        = 100
#table_cache            = 64
#thread_concurrency     = 10
#
# * Query Cache Configuration
#
query_cache_limit	= 1M
query_cache_size        = 16M
#
# * Logging and Replication
#
# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# As of 5.1 you can enable the log at runtime!
#general_log_file        = /var/log/mysql/mysql.log
#general_log             = 1
#
# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf.
#
# Here you can see queries with especially long duration
#log_slow_queries	= /var/log/mysql/mysql-slow.log
#long_query_time = 2
#log-queries-not-using-indexes
#
# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
#       other settings you may need to change.
#server-id		= 1
#log_bin			= /var/log/mysql/mysql-bin.log
expire_logs_days	= 10
max_binlog_size         = 100M
#binlog_do_db		= include_database_name
#binlog_ignore_db	= include_database_name
#
# * InnoDB
#
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
# Read the manual for more InnoDB related options. There are many!
#
# * Security Features
#
# Read the manual, too, if you want chroot!
# chroot = /var/lib/mysql/
#
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem

innodb_buffer_pool_size = 1G
innodb_flush_log_at_trx_commit = 2
sync_binlog = 1
innodb_flush_method = O_DIRECT

[mysqldump]
quick
quote-names
max_allowed_packet	= 16M

[mysql]
#no-auto-rehash	# faster start of mysql but no tab completition

[isamchk]
key_buffer		= 16M

#
# * IMPORTANT: Additional settings that can override those from this file!
#   The files must end with '.cnf', otherwise they'll be ignored.
#
!includedir /etc/mysql/conf.d/