【まだ途中】SQLite, Redis の Insert 比較 (By PHP)【ツッコミ歓迎】

動機

さまざまなDBの速度比較をしたかった。

各種DBへの個人的印象

比較する前に、どんなふうに思っているかを。。。ちなみに今回、私の比較では、検索とかしていないのでDatabaseってよりはI/Oなのかなと思ったり。RDBMSをそういうふうに使うのは本末転倒...?

SQLite

クライアントサーバー型ではないDB、つまりローカルでしか使えないらしい(とは言えアプリケーションに内蔵すればいい話ですが)がかなり高速らしい。

Redis

インメモリデータベースで、Key-Value系。と言っても単純なKey-Valueではないが。RedisはREmote DIctionary Serverの省略。メモリでやり取りするだけあって、超高速なのでは。ニコニコ動画でも部分的に使われているとのこと。どうやらRedisチームではCluster化に対応したい様子。ちなみにPredisというPHPのRedisツールでは、簡易的なクラスタリング手法のコードがExampleに載っている。試していないけれど。

MySQL【あとでこれも比較】

定番DB。ライセンスが商用OKなのか不明瞭、さらにはOracleに支配されていて将来不穏。と言いつつも未来永劫使える気がするが。。。そこらかしこで使われていてDBと言えばこれ。世界のブログの30%ぐらいはMySQLさんが裏で動いている感。

MySQL Cluster 【あとでこれも比較】

MySQLクラスタ。たくさん束ねて冗長化+高速化?あまりよく知らないがインメモリらしい。

PostgreSQL 【あとでこれも比較】

定番DB。ライセンスはBSD・MITライクらしく商用利用も安心。日本で人気?

pgpool-II 【あとでこれも比較】

PostgresSQLのクラスタ版。あまり良く知らない。開発は結構盛んのよう。

Firebird 【あとでこれも比較】

日本ではあまり有名でない気がするDB。MySQL, PostgresSQL, FirebirdオープンソースRDBMS御三家。正直なところ私もよく知らない。

Cassandra 【あとでこれも比較】

Facebookの連中が開発したらしいKey-Value系DB。使ったことがないから何とも言えない。リニアにスケールするとかなんとか。

HBase 【あとでこれも比較】

HadoopベースのDB?よく知らない。

MongoDB 【あとでこれも比較】

Key-Value系では一番有名?大人気感。

比較方法

Key-Valueっぽいデータを何万件かinsert or setしてそれにかかった時間を比較。Key-Valueっぽいデータの例↓

key value
id:1 1
id:2 2
id:3 3

マシン環境

DB環境

すべてローカルで完結するように行った。1台のマシンで完結する環境である。クライアントサーバー型ではなくローカルである。

  • SQLite3 (3.7.14.1)
  • Redis (2.6.7)

ソースコード

このポストの下の方(あとでGitHubに置きます)

結果

処理時間

件数: 1万件

DB名 条件 時間(sec)
PHP PHPのarray配列に入れてみた 0.035
Redis Predis使った 1.325
SQLite3 毎回のinsertでtransaction。つまりtransaction1万件 10.252
SQLite3 1万件のinsertを1度のtransactionで済ませた 0.143
総評

上記の結果より、コードの中で配列で持つのが最強!を示した( ー`дー´)キリッ
とは言えそれではやりにくいので、DBを使うわけであるが。

Redisはやはり高速。おそらく内部的にunix socketを使ってのこの結果なわけだが、それにもかかわらず1万IOPS近く出ているのは優秀と言えるだろう。もしかするとほとんどunix socketの処理に時間が取られているのかもしれないとすら感じる。

SQLiteは、毎回トランザクションが掛かるとそれなりに時間が掛かっていた。SSDでもこの程度掛かるわけか。一度のトランザクションで済ませるとRedisを上回る早さを見せるが、一回の書き込みで1万件行うわけでそりゃ高速である。Webサービスからのアクセスを考えると、1万件一気にどうのこうのというイベントはあまり発生しないので、参考にはならないだろう。バッチ処理的な場面では有効かもしれない。

ソースコード

(注意点: SQLiteの処理で、「$pdo->query("drop table test1");」とあるが、↓のコードの初回実行時にはこれをコメントアウトする必要がある。2回目以降は、この処理を書くと良い。)

<?php
//*** Redis に set ****

require_once('./../Predis/Autoloader.php');
Predis\Autoloader::register();

$client = new Predis\Client(array(
			'scheme' => 'tcp',
			'host' => 'localhost',
			'port' => 6379,
		));
try {
	$client->connect();
} catch (Exception $e) {
	print $e->getMessage();
	die("\nRedis/(^o^)\");
}

$client->flushall();

$num = 10000;
$start = microtime(true);
for ($i = 0; $i < $num; $i++) {
	$str_i = strval($i);
	$client->set("id:" . $str_i, $str_i);
}
$end = microtime(true);
printf("%.3f sec <- Redis の %s 件の Set\n", (double)($end - $start), $num);


//*** SQLite3 に set (毎回の insert ごとに transaction版。こちらが実際に近い?)****
$pdo = new PDO('sqlite:./db.sqlite3', "", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->query("drop table test1");
$pdo->query("create table test1 (key varchar(32), value varchar(32))");
$stmt = $pdo->prepare("insert into test1 values (:key, :value)");
$start = microtime(true);
for ($i = 0; $i < $num; $i++) {
	$str_i = strval($i);
	$key = "id:" . $str_i;
	
	$pdo->beginTransaction();
	$stmt->bindParam(":key", $key);
	$stmt->bindParam(":value", $str_i);
	$stmt->execute();
	$pdo->commit();
}
$end = microtime(true);
printf("%.3f sec <- SQLite3 の %s 件の Insert(毎回の insert で transaction)\n",(double)($end - $start), &#160;$num);


//*** SQLite3 に set (すべてのinsertを1回のtransactionでこなす。こちらは非現実的?)****
$pdo = new PDO('sqlite:./db.sqlite3', "", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->query("drop table test2");
$pdo->query("create table test2 (key varchar(32), value varchar(32))");
$stmt = $pdo->prepare("insert into test2 values (:key, :value)");
$start = microtime(true);
$pdo->beginTransaction();
for ($i = 0; $i < $num; $i++) {
	$str_i = strval($i);
	$key = "id:" . $str_i;
	
	$stmt->bindParam(":key", $key);
	$stmt->bindParam(":value", $str_i);
	$stmt->execute();
	
}
$pdo->commit();
$end = microtime(true);
printf("%.3f sec <- SQLite3 の %s 件の Insert(すべてのinsertをまとめて1回のtransaction)\n",(double)($end - $start), &#160;$num);


// *** PHP内での配列生成
$php_array = array();
$start = microtime(true);
for ($i = 0; $i < $num; $i++) {
	$str_i = strval($i);
	$php_array[] = array("id:" . $str_i => $str_i);
}
$end = microtime(true);
printf("%.3f sec <- PHP内 の %s 件の Array Set\n", (double)($end - $start), $num);
// var_dump($php_array);

//MySQL版
$mysql = "mysql:host=192.168.0.150;port=3306;dbname=test";
$user = "test_user";
$pass = "test_user";
$pdo = new PDO($mysql, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); //trueだとSQL文が1回で済む(そのぶんセキュリティが... -> と思ったが1万件で0.5sec遅くなった/(^o^)\
$pdo->query("drop table test_mysql");
// MySQL では、「key」という項目名でエラーなので、key1という名前にした↓
$pdo->query("create table test_mysql (key1 varchar(32), value1 varchar(32))");
$stmt = $pdo->prepare("insert into test_mysql values (:key1, :value1)");
$start = microtime(true);
for ($i = 0; $i < $num; $i++) {
	$str_i = strval($i);
	$key = "id:" . $str_i;
	
	$pdo->beginTransaction();
	$stmt->bindParam(":key1", $key);
	$stmt->bindParam(":value1", $str_i);
	$stmt->execute();
	$pdo->commit();
}
$end = microtime(true);
printf("%.3f sec <- MySQL(Remote) の %s 件の Insert(1件のinsertごとにtransaction)\n",(double)($end - $start),  $num);
?>
生のoutput

(sandbox.phpという名前にしている)

$ php sandbox.php 
1.325 sec <- Redis の 10000 件の Set
10.252 sec <- SQLite3 の 10000 件の Insert(毎回の insert で transaction)
0.143 sec <- SQLite3 の 10000 件の Insert(すべてのinsertをまとめて1回のtransaction)
0.035 sec <- PHP内 の 10000 件の Array Set