【記事途中】各種Databaseの性能を Apache Bench でいろいろと比較【MySQL, MemSQL, Redis, MongoDB】
まだ途中の記事です。 いろいろなDBを追加していきます。
長い長い記事なのでまとめ↓
安定性: MySQL >= MongoDB >> MemSQL = Redis
高速性: MongoDB > Redis > MySQL > MemSQL
使うならMySQLかMongoDBですね(もしくはどちらも)。Redisは安定性がなさすぎて、MemSQLは早くもなければ安定でもないという誰得状態です(私のベンチのとり方が悪いのかな?)※個人の検証です
☆ 動機
DBというものは複数からアクセスがあって初めてその性能が分かるものでしょう。
【まだ途中】SQLite, Redis の Insert 比較 (By PHP)【ツッコミ歓迎】 - 自分用備忘録
↑先日このように比較しましたが、自分で比較しておいて何ですがあまり参考にならないかと思いました。なぜなら先日のものは単一のアクセスだからです。一般的にデータベースは同時にたくさんのアクセスがくるために作られているので(ほんとう?)、不適切な比較だったかもしれません。
そんなわけで、Apache BenchでURLを叩いて比較してみようと思いました。これで本当の性能が分かることを祈りつつ。。。
☆ 環境
- 鯖A (192.168.0.100、アプリケーション、データベース)
- Pentium Dual Core E5200
- DDR2-800 2GB * 2
- Crucial m4 SSD 64GB
- P5KPL-CM
- GbE (AR8121/AR8113 ?)
- CentOS 6.3
- Apache/2.2.15 (Unix) <- httpd.conf は、access_logを出力しないようにしただけでほかはデフォルト。
- MySQL 5.1.66, for redhat-linux-gnu (x86_64) using readline 5.1
- MemSQL 5.5.8
- Redis 2.6.7 (by gcc version 4.4.6)
- MongoDB v2.0.7, pdfile version 4.5
- phpredisを使ってredis-serverにアクセス
- php
# php -v PHP 5.3.3 (cli) (built: Jul 3 2012 16:53:21) Copyright (c) 1997-2010 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies with Xdebug v2.1.4, Copyright (c) 2002-2012, by Derick Rethans # php -m [PHP Modules] apc bz2 calendar Core ctype curl date dba dom ereg exif fileinfo filter ftp gd gettext gmp hash iconv igbinary json libxml mbstring mcrypt mongo mysql mysqli odbc openssl pcntl pcre PDO pdo_mysql PDO_ODBC pdo_pgsql pdo_sqlite pgsql Phar readline redis Reflection session shmop SimpleXML soap sockets SPL sqlite3 standard tokenizer uuid wddx xdebug xml xmlreader xmlrpc xmlwriter xsl zip zlib [Zend Modules] Xdebug
☆ 接続とRTT
鯖A <= GbE => NECルーター(GbE、Aterm WR8700N) <= GbE => クライアント
# 鯖A -> クライアント のPING [root@e5200] # ping 192.168.0.150 PING 192.168.0.150 (192.168.0.150) 56(84) bytes of data. 64 bytes from 192.168.0.150: icmp_seq=1 ttl=64 time=0.098 ms 64 bytes from 192.168.0.150: icmp_seq=2 ttl=64 time=0.107 ms 64 bytes from 192.168.0.150: icmp_seq=3 ttl=64 time=0.118 ms ^C --- 192.168.0.150 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2723ms rtt min/avg/max/mdev = 0.098/0.107/0.118/0.014 ms # クライアント -> 鯖A のPING [root@g530: ~] # ping 192.168.0.100 PING 192.168.0.100 (192.168.0.100) 56(84) bytes of data. 64 bytes from 192.168.0.100: icmp_seq=1 ttl=64 time=0.101 ms 64 bytes from 192.168.0.100: icmp_seq=2 ttl=64 time=0.137 ms 64 bytes from 192.168.0.100: icmp_seq=3 ttl=64 time=0.099 ms ^C --- 192.168.0.100 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2948ms rtt min/avg/max/mdev = 0.099/0.112/0.137/0.019 ms # 鯖A -> 鯖A のPING(localhost) (DBが起動してListen状態のデータ) [root@e5200: ~] # ping localhost PING localhost (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.024 ms 64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.042 ms 64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.030 ms ^C --- localhost ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2428ms rtt min/avg/max/mdev = 0.024/0.032/0.042/0.007 ms # クライアント -> クライアント のPING(localhost) [root@g530: ~] # ping localhost PING localhost.localdomain (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=1 ttl=64 time=0.023 ms 64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=2 ttl=64 time=0.029 ms 64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=3 ttl=64 time=0.027 ms ^C --- localhost.localdomain ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2483ms rtt min/avg/max/mdev = 0.023/0.026/0.029/0.004 ms
☆ 初期状態
[root@g530: ~] # curl http://192.168.0.100/benchmarks/static_file.html counter: 12345 [root@g530: ~] # curl http://192.168.0.100/benchmarks/memsql.php counter: 307636 [root@g530: ~] # curl http://192.168.0.100/benchmarks/mysql.php counter: 990695 [root@g530: ~] # curl http://192.168.0.100/benchmarks/phpredis.php counter: 999832 [root@g530: ~] # curl http://192.168.0.100/benchmarks/mongodb.php counter: 547848
☆ DBのテーブル・構造
# MySQL・MemSQL mysql> desc incr; +--------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+ | key1 | varchar(16) | YES | | NULL | | | number | int(11) | YES | | NULL | | +--------+-------------+------+-----+---------+-------+ 2 rows in set (0.00 sec) # Redis redis 127.0.0.1:6379> get counter "256598" # MongoDB > db.benchmarks.insert({"name":"key1", "number":1}) > db.benchmarks.update({"name":"key1"}, {"$inc": {"number":1} }) > db.benchmarks.find(); { "_id" : ObjectId("50c8dc945139fe0dcad11646"), "name" : "key1", "number" : 11 }
☆ 計測方法
Apache Benchという、HTTPサーバーの性能を調べるソフトウェアを使って、様々なDBが単位時間あたりにどのぐらいのRequestをさばけるか調査しました。インクリメントするベンチマークです。具体的なコマンドは、
# ab -n 10000 -c 10 http://192.168.0.100/benchmarks/phpredis.php
です。クライアント(192.168.0.150)のターミナルから、↑のコマンドを打ち鯖A(192.168.0.100)がそれを捌きます。n: リクエスト数、c: 同時接続数らしいです。nは固定で、cを変えて計測します(Apache Benchについてよく分かっていないのでアレですが、↑の例だと10同時に接続して、合計1000回のリクエストになるまで計測するという意味なのでしょうか?誰か教えてください><)。リクエスト数は1万で固定、同時接続数をいろいろと変えて試してみます。ベンチ後、Failed Requestが出ていたら、topコマンドで確認してapacheのプロセスが残っていたらapache再起動して一度だけcurlでアクセスします。また、異なるDBを使う前にはやはりApacheを再起動して一度だけcurlでアクセスしてから計測しています。計測回数は1回だけです。apache benchなのかサーバーのapacheが原因なのか分かりませんが、同時接続数が250を超えてくるとエラーが出たりするようになりますのでそこで打ち止めにしています。”apr_socket_recv: Connection reset by peer (104)" ←こういうエラーが出たりします。
参考のために、静的なhtmlファイルも測定しました。
レコードが極めて小さいので、インメモリ処理の速度のベンチマークになっていますが。。。
mysql>show table status\G *************************** 1. row ***************************            Name: incr          Engine: MyISAM         Version: 10      Row_format: Dynamic            Rows: 1  Avg_row_length: 20     Data_length: 20 Max_data_length: 281474976710655    Index_length: 1024       Data_free: 0  Auto_increment: NULL     Create_time: 2012-12-13 00:36:56     Update_time: 2012-12-13 23:32:54      Check_time: NULL       Collation: utf8_general_ci        Checksum: NULL  Create_options:          Comment:  1 row in set (0.00 sec)
☆ 結果
同時接続数 1 (ab -n 10000 -c 1 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] |
---|---|
静的ファイル | 2575.24 |
MemSQL | 786.83 |
MySQL | 972.82 |
Redis | 958.11 |
MongoDB | 997.91 |
同時接続数 5 (ab -n 10000 -c 5 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] |
---|---|
静的ファイル | 9072.13 |
MemSQL | 1935.80 |
MySQL | 2137.21 |
Redis | 2446.51 |
MongoDB | 2964.15 |
同時接続数 10 (ab -n 10000 -c 10 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] |
---|---|
静的ファイル | 14391.80 |
MemSQL | 1861.21 |
MySQL | 2071.94 |
Redis | 2411.20 |
MongoDB | 2935.10 |
同時接続数 25 (ab -n 10000 -c 25 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] |
---|---|
静的ファイル | 13811.39 |
MemSQL | 1880.41 |
MySQL | 2035.98 |
Redis | 2430.54 |
MongoDB | 2921.33 |
同時接続数 50 (ab -n 10000 -c 50 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] |
---|---|
静的ファイル | 13505.19 |
MemSQL | 1850.73 |
MySQL | 1986.39 |
Redis | 2378.09 |
MongoDB | 2904.16 |
同時接続数 75 (ab -n 10000 -c 75 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] |
---|---|
静的ファイル | 13720.13 |
MemSQL | 1868.24 |
MySQL | 2057.14 |
Redis | 2438.65 |
MongoDB | 2893.70 |
同時接続数 100 (ab -n 10000 -c 100 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] |
---|---|
静的ファイル | 13178.53 |
MemSQL | 1868.09 |
MySQL | 2038.22 |
Redis | 2422.57 |
MongoDB | 2894.46 |
同時接続数 250 (ab -n 10000 -c 250 http://192.168.0.100/benchmarks/***.php)
DB名 | Request Per Sec [#/sec] | 備考 |
---|---|---|
静的ファイル | 7024.53 | Failed Requestこそないものの、ガクンと遅くなりました。同時接続数が多すぎる感です |
MemSQL | 1590.09 | httpd |
MySQL | 1595.75 | httpd |
Redis | 1600.28 | やはりhttpd |
MongoDB | 1600.16 | ここでもhttpd |
# 同時接続数250あたりからエラーが頻発しはじめたのでここで打ち止めにします。
おまけ: リクエスト数10万、同時接続数50でやってみました。具体例-> "ab -n 100000 -c 50 http://192.168.0.100/benchmarks/***.php"。各DBの測定前に毎回 httpd を再起動しています。
DB名 | Request Per Sec [#/sec] | Failed requests [%] | 備考 |
---|---|---|---|
静的ファイル | 13927.32 | 0 | 単純なhtmlはそりゃまあ高速ですね |
MemSQL | 642.98 | 19.7 | エラー率が20%近いですね |
MySQL | 2001.63 | 0 | Failed requestsなし!さすが安定のMySQL |
Redis | 686.96 | 19.3 | Failed requestが20%!/(^o^)\。ただ、phpredisを使っている影響があるかも? |
MongDB | 2822.92 | 0 | エラーなし、そしてこの速度! |
☆ 総評
まずは計測の感想から。同時接続数が250を超えてくるとエラーが出始めます。何が原因かいまいちわかっていません。同時接続数は、OS、Apache、NICになどにより左右されそうですので。。。
以下、それぞれについて書いていきます。
静的なファイル: 単なるhtmlです。このシステムでの限界性能が測れるはず、です。10K req/sec 以上出ていますね。これを100K とかにしたいところなのですがどうすればいいのでしょうね。。。
MemSQL: MySQLのタイポではなく、MemSQLです。MySQLをインメモリデータベースにしてみたよー、というコンセプトらしいです。http://www.memsql.com/#download ←ここでフォームを入力してダウンロードできます。割と期待していましたが、(´・ω・`)ガッカリ…な速度でした。インメモリなのは結構なのですが昨今のRDBはデータがメモリに載るならほぼ間違いなくインメモリで動くので、優位性がなさそうですね。ただ、MemSQLの実行時に、
WARN: SSE4.2 is not supported. Resorting to software CRC32C. Warning: The recommended configuration for MemSQL is at least 8 GB of RAM
と言われました。今回、DBサーバーはCore2時代のPentium Dual Core、E5200ですのでSSE4.2に対応していないのでしょう。また、DDR2-800の4GBDRAMしか詰んでいません。最新のIvyBridgeを使い、DDR3-2400のメモリを4channelにすればだいぶ結果は異なってくるかも知れません(たぶん他のDBも同様に早くなりますがね!)。
MySQL(MyISAM): いろんなところで使用されているだけあってそれなりの速度です。↑のおまけの測定結果を見てもらうと分かると思いますが、非常に安定性が高いです。むちゃなリクエストを出しても何とかしてくれます。本や資料、サポート会社がたくさんありますので、DB選択の本命と言えるでしょう。InnoDBのときも比較したいですね。
Redis: MySQLと比較して+20%の速度ですね。なかなかです。しかし安定性にかなり疑問。。。Failed Request出まくり。。。
MongoDB: この中で一番高速です。MySQL(これは5.1ですが)より50%近く高速です。人気があるだけあるなぁという印象です。安定性も高く、RDBが嫌でNoSQLを使いたいならMongoDBが良さそうです。ちなみに、最大で 600KB / s 程度のTransfer Rateになっていました。DB鯖だと、10MbpのNICで足りるのかもしれませんね(最新のNICのRTTなどは改善されている可能性は十分あるので何とも言えませんが)。
まとめ:
安定性 MySQL >= MongoDB >> MemSQL = Redis
高速性 MongoDB > Redis > MySQL > MemSQL
使うならMySQLかMongoDBですね(もしくはどちらも)。Redisは安定性がなさすぎて、MemSQLは早くもなければ安定でもないという誰得状態です(私のベンチのとり方が悪いのかな?)
追記@2012年12月14日
何となく鯖Aとクライアント(Bとします)を入れ替えてみました。そしたら、MongoDBやRedisが、4000 req/sec を超えてきました。静的ファイルだと16512.85 req/sec も出ています。MySQL(MyISAM)だと2911.94!。50%近く向上しています。うーん、鯖AはCore2時代のCPUでDDR2-800、クライアントBは、Sandy世代のCeleron G530でDDR3-1066で、そこまでスペックに差がある感じではないのですが。。。ただ、Sandy世代ということでCPU性能の向上、キャッシュへのヒット率の向上、メモリがちょっぴり高速、あたりが原因かもしれません。
ちなみにi5-3570Kを持っていてWindowsを入れているのですが、こちらでも実験したくなって来ました。MongoDBで6000 req/secぐらい行くのかも? また、研究室にSandyBridge-Eなi7があるのでそれで検証してみたいですね。L2キャッシュてんこ盛りなCPUだとすごいことになるのではと思ったり。
また、MySQLのPDOで、
$pdo = new PDO($mysql, $user, $pass, array(PDO::ATTR_PERSISTENT => true));というものを使ってみました。コネクションが保持されるらしいです。使いまわしするみたいですね。たしかにいちいち切断するのは無駄ですね。
接続プーリング OFF↓
# ab -n 100000 -c 10 http://192.168.0.100/benchmarks/mysql.php Requests per second: 2007.30 [#/sec] (mean)
接続プーリング ON ↓# ab -n 100000 -c 10 http://192.168.0.100/benchmarks/mysql2.php Requests per second: 2518.83 [#/sec] (mean)ひゃー、30%近く高速化してしまった!すごいですね!効果絶大。MySQL最強フラグな気がしないでもないですね(MyISAMではなくInnoDBも試さねば。。。)
まとめ:
測定に用いたデータ(レコード)がメモリで収まることに留意しつつも、MySQLが予想外に早いです。phpredisは+20%ぐらい早く見えると思いますがとにかく安定性が悪いです。かなり苦労しました。しょっちゅうFailed requestsを出してきます。リクエスト件数が多くても同時接続数が多くても出ます。同じ条件でも出る時と出ない時があります。一方でMySQLは総じて安定しています。MongoDBもかなり安定しています。
ソースを見てもらえると分かると思いますが、phpredisの方は、incr一回、MySQLの方はクエリ二回なので明らかにMySQLが不利だろうと思っていましたが、結果的にはかなり均衡していました。
とにかく安定性が段違いですし、速度もそう変わらないので、RedisとMySQLだけを比較すればMySQLを使うべきなのではないかと感じました。
MongoDBはかなり高速ですね。3000req/sec近いのは魅力的です。
書いていて思いましたが、メモリにおさまるデータサイズならどのDBも2000req/sec程度出そうな雰囲気です。だからどれを使ってもいいんじゃないでしょうか(ぉ。安定性重視だとやはりMySQLですね。RedisはインメモリDBだから高速!と思っていたら他のDBもメモリにデータがおさまるならメモリアクセスしちゃうので実質インメモリDB状態だったでござるの巻ですね。
ちなみに、クライアント側のNICをIntelのNIC(PCI-Ex版の一番安いもの)に変えてみると、+10%程度になりました。MongoDBで最高3000 req/secを超えました。サーバー側の方も交換して計測してみたいですね。
ToDo:
グラフ化する
☆ 疑問点
localhostでのRTTが0.1msecぐらいということは、秒間に1万request程度しか原理的に出来ない計算になります。さらに鯖A、鯖B間のpingのRTTはもっとあります。そんな状態で2000req/s程度出ていたのは立派な気がします。ただ、巷では、10万req/s行ったぜーなんて話を聞きますが、本当にそんな速度出るのかなと疑問です。NICをたくさん使うとかするのでしょうか。今回は鯖一つにつきNIC一個でしたが。高速化技術を知りたいですね。今回、ルーターを一つ挟んで、すべてGbEで接続して計測しましたが、ネットワークがボトルネックにも思えます。さらに言えばたとえば鯖A内でMySQLを走らせた場合、鯖Aがクライアントからリクエストを貰った時にunix socketでMySQLと通信すると思いますが、そのときのunix socketもネックではないでしょうか(これはおそらく ping localhost のRTTですよね)。
また、PHPで比較するのが適切なのが疑問になって来ました。せめてJavaを使うべきだったかもしれません。
うーん、今回のシステムでは、アプリケーションとDBが一体化しています。tcp/ipを使わないほうが早いだろうと思ってそうしたのですが、アプリケーション・サーバー10台、DBサーバー1台の構成であれば、もっとQuery Per Secが出るのかもしれませんね。。。
☆ ソース
static_file.html
counter: 12345
memsql.php
<?php try { $mysql = "mysql:host=127.0.0.1;port=3307;dbname=MemSQL_tutorial"; $user = "root"; $pdo = new PDO($mysql, $user); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); $stmt1 = $pdo->prepare("update incr set number = number +1"); $stmt2 = $pdo->prepare("select number from incr"); //$pdo->beginTransaction(); $stmt1->execute(); $stmt2->execute(); //$pdo->commit(); $row = $stmt2->fetch(PDO::FETCH_ASSOC); printf("counter: %s\n", $row["number"]); } catch (PDOException $e) { printf("Error: データベース接続に失敗しました。\n"); print $e->getMessage(); die(); } ?>
<?php try { $mysql = "mysql:host=localhost;port=3306;dbname=benchmark"; $user = "bm-user"; $pass = "bm-user"; $pdo = new PDO($mysql, $user, $pass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); $stmt1 = $pdo->prepare("update incr set number = number +1"); $stmt2 = $pdo->prepare("select number from incr"); $pdo->beginTransaction(); $stmt1->execute(); $stmt2->execute(); $pdo->commit(); $row = $stmt2->fetch(PDO::FETCH_ASSOC); printf("counter: %s\n", $row["number"]); } catch (PDOException $e) { printf("Error: データベース接続に失敗しました。\n"); print $e->getMessage(); die(); } ?>
phpredis.php
<?php $redis = new Redis(); if ($redis->connect('localhost', 6379)) { //print("redis connect OK"); } else { die("Connection to Redis Failed."); } print "counter: " . $redis->incr("counter") . "\n"; ?>
mongodb.php
<?php try { $link = new Mongo('localhost:27017'); $db = $link->test; $col = $db->benchmarks; //はじめに追加するときはこれ。一旦加えたらあとはコメントアウト。 //$doc = array('key' => 'value', 'number' => 1); //$col->insert($doc); //increment $col->update(array('key' => 'value'), array('$inc' => array('number' => 1))); $obj = $col->findOne(); printf("counter: %s\n", $obj['number']); } catch (MongoConnectionException $e) { die('error'); }
☆ おまけ
各種DB実行方法:
MemSQL (CentOS)
# MemSQLの公式サイトからバイナリを落としてきます # ↓ memsqldを実行(DRAMが8GB以上ないとWarning出ますが気にしない) # ./memsqld --port 3307 -u root # ↓他の端末で行う、memsqlのcliモードに入る # mysql -u root -h 127.0.0.1 -P 3307 --prompt="memsql> " # データベースとテーブルを作って値を挿入 memsql> CREATE DATABASE MemSQL_tutorial; memsql> USE MemSQL_tutorial; memsql> create table incr(key1 varchar(16), number int); memsql> insert into incr values("value1", 1); memsql> update incr set number = number +1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 memsql> select * from incr; +--------+--------+ | key1 | number | +--------+--------+ | value1 | 2 | +--------+--------+ 1 row in set (0.00 sec) # memsqldを落とすときは↓ # ps aux | grep mem root 358 0.2 2.4 1483012 197424 pts/0 Sl+ 19:05 0:03 ./memsqld --port 3307 -u root root 362 0.0 0.0 147204 1920 pts/0 Sl+ 19:05 0:00 ./memsqld --port 3307 -u root root 20633 0.0 0.0 107464 992 pts/1 S+ 19:31 0:00 grep --color mem [root@g530: ~] # kill 358 [root@g530: ~] # kill 362