Redisのサイズの測り方

追記@2014年1月6日
素直に redis-cli で、

redis 127.0.0.1:16379> info memory
# Memory
used_memory:1064592
used_memory_human:1.02M
used_memory_rss:1712128
used_memory_peak:1064512
used_memory_peak_human:1.02M
used_memory_lua:31744
mem_fragmentation_ratio:1.61
mem_allocator:libc

などとした方が良いかもです。。。


Redisプロセスが試験的にどれぐらいのサイズなのかしたくなる場合がありますね。Redisの作者さんは後述するような方法で計測していたので、このように計測すると良いのかと思います。ただ、RedisのRDBファイルには圧縮オプションがあるので、それは切ったほうが良いと思います(計測時は)。

Example:

edis 127.0.0.1:6379> flushall
OK
redis 127.0.0.1:6379> sadd set 1 2 3 4 5 6 7 8 9 10
(integer) 10
redis 127.0.0.1:6379> save
OK
redis 127.0.0.1:6379> quit
bash-3.2$ hexdump -C dump.rdb
00000000  52 45 44 49 53 30 30 30  33 fe 00 0b 03 73 65 74  |REDIS0003....set|
00000010  1c 02 00 00 00 0a 00 00  00 01 00 02 00 03 00 04  |................|
00000020  00 05 00 06 00 07 00 08  00 09 00 0a 00 ff        |..............|
0000002e
bash-3.2$ ls -l dump.rdb
-rw-r--r--  1 antirez  staff  46 Feb 20 14:57 dump.rdb

redis.confの圧縮の項目
中略

# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression no

中略

↑これをnoとして計測したほうが良いかと思います。

InstagramIDをPHPとAPCを使って作成、の改訂版

以前はこんな感じで作っていました↓

<?php

define('MAX', 1000000);

$ids = array();
$bench = new Benchmark();
for ($i = 0; $i < MAX; $i++) {
	$ids[] = genID2();
}
printf("=== Speed (APC) ===\n%.2f [req/sec]\n", MAX / $bench->end());

function genID2() {
	$seq2 = apc_inc('seq2');
	if (!$seq2) {
		apc_store('seq2', 1, 3600);
		$seq2 = 1;
	}
	return (int) ((microtime(TRUE) - 1283758447 ) * 1000) * 8388608 + (1 % 8192) * 1024 + $seq2 % 1024;
}

/**
 * My Benchmark tool
 */
class Benchmark {

	private $start;

	public function __construct() {
		$this->start();
	}

	private function start() {
		$this->start = microtime(TRUE);
	}

	public function end() {
		return (double) (microtime(TRUE) - $this->start);
	}

}


ベンチ
=== Speed (APC) ===
148108.44 [req/sec]

しかしながら、apc_store(‘seq1’, 1, 3600); としたときに、expireしないようなので(apc_incとしているのも悪いのかもしれません。。。)、数が増えた時に問題になるかなと思いました。64bit Integerですので溢れないと思いますし、ループするのではないかとは思いますが。。。

とは言えなんとなく落ち着かないので書き換えてみました。

<?php

define('MAX', 1000000);

$ids = array();
$bench = new Benchmark();
for ($i = 0; $i < MAX; $i++) {
	$ids[] = genID();
}
printf("=== Speed (APC) ===\n%.2f [req/sec]\n", MAX / $bench->end());

function genID() {
	$seq = apc_inc('seq');
	if (!($seq % 1024)) {
		apc_store('seq', 0);
		$seq = 0;
	}
	return (int) ((microtime(TRUE) - 1283758447 ) * 1000) * 8388608 + (1 % 8192) * 1024 + $seq;
}

/**
 * My Benchmark tool
 */
class Benchmark {

	private $start;

	public function __construct() {
		$this->start();
	}

	private function start() {
		$this->start = microtime(TRUE);
	}

	public function end() {
		return (double) (microtime(TRUE) - $this->start);
	}

}



ベンチ
=== Speed (APC) ===
148004.13 [req/sec]

1024ごとにapcのcounterを0に戻しています。それでも速度は秒間15万生成なので、問題無さそうです。

本当にループできているのか確かめてみます。
<?php

$seq = apc_inc('seq');
if (!($seq % 5)) {
	apc_store('seq', 0);
	$seq = 0;
}
echo $seq . "\n";

↑このようなコードを書いて、以下のように実験しました。5回でループするはずです。

$ curl localhost:8888/c.php
0

$ curl localhost:8888/c.php
1

$ curl localhost:8888/c.php
2

$ curl localhost:8888/c.php
3

$ curl localhost:8888/c.php
4

$ curl localhost:8888/c.php
0

$ curl localhost:8888/c.php
1

$ curl localhost:8888/c.php
2

$ curl localhost:8888/c.php
3

$ curl localhost:8888/c.php
4

$ curl localhost:8888/c.php
0

$ curl localhost:8888/c.php
1

というわけで、0~4でループしていて問題無いことが分かります。

jQueryでPHPのエラー内容を表示

エラー内容をブラウザで見れると楽ですよね。

jQueryの、XMLHttpRequest.responseText で取り出せるようです。jQueryで、

$.ajax({
	    type: "POST",
	    url: "/admin/join.php",
	    data: data,
	    dataType: 'json',

	    success: function(data, dataType)
	    {
		console.log(data);
		alert("OK!");
	    },

	    /**
	     * If Ajax communication failed.
	     */
	    error: function(XMLHttpRequest, textStatus, errorThrown)
	    {
		console.log(XMLHttpRequest.responseText);
	    }
	});

という感じにすると取り出せます。

XCacheのphp.iniの書き方

MAMPに入っていたものを参考にしました。
とりあえずこうやれば動きましたということで。。。
まあ使う予定はないのですが。。。

設定がうまくいかないと、以下の様なエラーが出ます。。。

/dev/zero: Operation not supported by device
Failed creating file mapping
[Thu Nov 14 09:58:54 2013] PHP Fatal error:  Failed creating file mapping in Unknown on line 0
<br />
<b>Fatal error</b>:  Failed creating file mapping in <b>Unknown</b> on line <b>0</b><br />
[Thu Nov 14 09:58:54 2013] PHP Fatal error:  XCache: Cannot create shm in Unknown on line 0
<br />
<b>Fatal error</b>:  XCache: Cannot create shm in <b>Unknown</b> on line <b>0</b><br />

/tmp/xcache: Is a directory
Cannot open or create file set by xcache.mmap_path, check the path permission or check xcache.size/var_size against system limitation
[Thu Nov 14 10:00:24 2013] PHP Fatal error:  Cannot open or create file set by xcache.mmap_path, check the path permission or check xcache.size/var_size against system limitation in Unknown on line 0
<br />
<b>Fatal error</b>:  Cannot open or create file set by xcache.mmap_path, check the path permission or check xcache.size/var_size against system limitation in <b>Unknown</b> on line <b>0</b><br />
[Thu Nov 14 10:00:24 2013] PHP Fatal error:  XCache: Cannot create shm in Unknown on line 0
<br />
<b>Fatal error</b>:  XCache: Cannot create shm in <b>Unknown</b> on line <b>0</b><br />

xcache用ディレクトリを作成
$ mkdir /tmp/xcachecore

extension= xcache.so

; xcache
[xcache]
; ini only settings, all the values here is default unless explained
; select low level shm/allocator scheme implemenation
xcache.shm_scheme =        "mmap"

; to disable: xcache.size=0
; to enable : xcache.size=64M etc (any size > 0) and your system mmap allows
xcache.size  =                64M
; set to cpu count
xcache.count =                 1
; just a hash hints, you can always store count(items) > slots
xcache.slots =                8K
; ttl of the cache item, 0=forever
xcache.ttl   =                 0
; interval of gc scanning expired items, 0=no scan, other values is in seconds
xcache.gc_interval =           0

; same as aboves but for variable cache
xcache.var_size  =            64M
xcache.var_count =             1
xcache.var_slots =            8K
; default ttl
xcache.var_ttl   =             0
xcache.var_maxttl   =          0
xcache.var_gc_interval =     300

xcache.test =                Off
; N/A for /dev/zero
xcache.readonly_protection = Off
; for *nix, xcache.mmap_path is a file path, not directory.
; Use something like "/tmp/xcache" if you want to turn on ReadonlyProtection
; 2 group of php won't share the same /tmp/xcache
;xcache.mmap_path =    "/Applications/MAMP/tmp/xcache"
xcache.mmap_path =    "/tmp/xcache"

; leave it blank(disabled) or "/tmp/phpcore/"
; make sure it's writable by php (without checking open_basedir)
xcache.coredump_directory =   "/tmp/xcachecore/"

; per request settings
xcache.cacher =               On
xcache.stat   =               On
xcache.optimizer =           On

MongoDBのjournalCommitIntervalとパフォーマンスの関係

MongoDBにはjournaling機能があります。ファイルシステムなど不勉強であまり理解していませんが、RDBでよくあるWAL、Write Ahead Logと同じ機能だと思います。測定結果から書きますと (#1は1回目、#2は2回目、という意味です)、

journalCommitInterval [ms] #1 [req/sec] #2 [req/sec] #3 [req/sec]
2 2750.77 2742.48 2745.22
5 3753.11 4643.63 3668.53
10 5878.66 5964.59 5811.40
20 6940.56 6907.89 6978.47
50 6994.27 7187.60 7055.46
100 7750.13 7833.18 7759.90
200 7984.70 7939.39 8021.59
300 8034.18 7852.37 7965.44
no journal 8238.88 8317.84 8289.27

となりました。書き込みの内容・サイズは、
$doc = array(
        'key1' => 'value1',
        'key2' => 'value2',
        'time' => time(),
);

です。time()は、unixtimeが整数値で出てきます。なんとなくtime()だとボトルネックになりそうですが、time()の代わりに適当な文字列にしてみてもパフォーマンスに変化はみられませんでした。

書き込み間隔が長いほどパフォーマンスが良いという順当な結果です。間隔が100~300[ms]程度だと、no journalの場合とあまり変わりません。なのでパフォーマンス重視の実運用を考えると100ぐらいに設定しておくのが良さそうです。

スペックは、
– NEC Express5800/S70 [N8100-9023]/MSS0332
– Linux version 3.2.0-52-generic (buildd@roseapple) (gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #78-Ubuntu SMP Fri Jul 26 16:21:44 UTC 2013 (Ubuntu 3.2.0-52.78-generic 3.2.48)
– DRAM: 8GB * 2
– SEAGATE ST500NM0011 (500GB HDD)
– Intel(R) Celeron(R) CPU G540 @ 2.50GHz stepping 07
– mongodb-linux-x86_64-2.4.8
– PHP 5.3.10-1ubuntu3.7 with Suhosin-Patch (cli) (built: Jul 15 2013 18:05:44)
– PHP mongo 1.4.0 stable
です。いまどきHDDなので残念感ありますが。。。SSDのベンチマークはどなたかお願いします。

MongoDB公式の設定オプションには、
>Lower values increase the durability of the journal, at the possible expense of disk performance
と書いてある通りの結果となりました。2 [ms]に指定した場合、パフォーマンスは約2700 [req/sec]程度になってしまいます。MySQLのベンチマークをしたときにこれぐらいだった記憶があり、早いとされているMongoDBでも、書き込みの信頼性を高めると結局MySQLなどとパフォーマンスに大差がないことが分かります。というよりこのあたりが現在のアーキテクチャ、ハードでの限界点なのでしょう。

永続化しつつ、パフォーマンス向上を成し遂げるにはスケールアウトする他なさそうです。

このようにjournalingやWALを考えますと、不揮発性メモリの到来が待ち遠しいですね(もう出来ているようですが、ある程度安価という意味で^^)。多くのDBが10倍以上の高速化を果たす可能性が高いでしょう。

実行コマンド
# CommitIntervalは↓のようにして設定します。2~300まで選択可能のようです
$ ./bin/mongod --journalCommitInterval 300

# journalなしの場合は、↓です。
$ ./bin/mongod  --nojournal

ベンチマークスクリプト
<?php

$mongo = new MongoClient('mongodb://' . 'localhost' . ":" . '27017');

$max = 10000;
$ids = array();
$bench = new Benchmark();
for ($i = 0; $i < $max; $i++) {
	$ids[] = insertSomething($mongo);
}
printf("%.2f [req/sec]\n", $max / $bench->end());

function insertSomething(MongoClient $mongo) {
	$db = $mongo->user;
	$col = $db->user;
	$doc = array(
		'key1' => 'value1',
		'key2' => 'value2',
		'time' => time(),
	);
	$col->insert($doc);

	return $doc['_id'];
}

class Benchmark {

	private $start;

	public function __construct() {
		$this->start();
	}

	private function start() {
		$this->start = microtime(TRUE);
	}

	public function end() {
		return (double) (microtime(TRUE) - $this->start);
	}

}


ちなみに、「–journalCommitInterval 1」とすると、エラー↓になるのですが、
# ./bin/mongod --journalCommitInterval 1
Wed Nov 13 12:29:10.772 --journalCommitInterval out of allowed range (0-300ms)
Wed Nov 13 12:29:10.773 dbexit: 
Wed Nov 13 12:29:10.773 shutdown: going to close listening sockets...
Wed Nov 13 12:29:10.773 shutdown: going to flush diaglog...
Wed Nov 13 12:29:10.773 shutdown: going to close sockets...
Wed Nov 13 12:29:10.773 shutdown: waiting for fs preallocator...
Wed Nov 13 12:29:10.773 shutdown: lock for final commit...
Wed Nov 13 12:29:10.773   Assertion failure c src/mongo/db/client.h 235
0xde05e1 0xda15bd 0x8c9768 0x8c9e6c 0x8ca262 0x8cc83d 0x8ccb75 0x9f2880 0x9f2fa2 0x6d8da9 0x6df459 0x7fa2028c976d 0x6cf1c9 
 ./bin/mongod(_ZN5mongo15printStackTraceERSo+0x21) [0xde05e1]
 ./bin/mongod(_ZN5mongo12verifyFailedEPKcS1_j+0xfd) [0xda15bd]
 ./bin/mongod(_ZN5mongo4Lock26ParallelBatchWriterSupport6relockEv+0x248) [0x8c9768]
 ./bin/mongod(_ZN5mongo4Lock26ParallelBatchWriterSupportC1Ev+0x2c) [0x8c9e6c]
 ./bin/mongod(_ZN5mongo4Lock10ScopedLockC2Ec+0x32) [0x8ca262]
 ./bin/mongod(_ZN5mongo4Lock10GlobalReadC1Ei+0x1d) [0x8cc83d]
 ./bin/mongod(_ZN5mongo11readlocktryC1Ei+0x45) [0x8ccb75]
 ./bin/mongod() [0x9f2880]
 ./bin/mongod(_ZN5mongo6dbexitENS_8ExitCodeEPKc+0x172) [0x9f2fa2]
 ./bin/mongod() [0x6d8da9]
 ./bin/mongod(main+0x9) [0x6df459]
 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7fa2028c976d]
 ./bin/mongod(__gxx_personality_v0+0x499) [0x6cf1c9]
Wed Nov 13 12:29:10.776 shutdown failed with exception
Wed Nov 13 12:29:10.776 dbexit: really exiting now
↑の中に、「Wed Nov 13 12:29:10.772 –journalCommitInterval out of allowed range (0-300ms)」と書いてあります。「1」は範囲内に見えるのですがどういうことなのでしょう。。。「0」でも同じエラーが出ます。公式には、
>This option accepts values between 2 and 300 milliseconds.
と書いてありますので、mongodのドキュメントの間違いでしょう。

ちなみに、以下のコードの用に、MongoClientの代わりにMongoCollectionを引数にしてみたところ、パフォーマンスが若干向上しました。具体的には、journalCommitIntervalが300のときに、8577.61 [req/sec]程度になりました。+10%程度は上がっているようです。実装を知らないので確証はないのですが、$db = $mongo->user; $col = $db->user;の段階で通信が行われているのかもしれません。tcpdumpして見てみるべきでしょう。。。

ちょっぴり早いコード
<?php

$mongo = new MongoClient('mongodb://' . 'localhost' . ":" . '27017');
$db = $mongo->user;
$col = $db->user;

$max = 10000;
$ids = array();
$bench = new Benchmark();
for ($i = 0; $i < $max; $i++) {
	$ids[] = insertSomething($col);
}
printf("%.2f [req/sec]\n", $max / $bench->end());

function insertSomething(MongoCollection $col) {
	$doc = array(
		'key1' => 'value1',
		'key2' => 'value2',
		'time' => time(),
	);
	$col->insert($doc);

	return $doc['_id'];
}

class Benchmark {

	private $start;

	public function __construct() {
		$this->start();
	}

	private function start() {
		$this->start = microtime(TRUE);
	}

	public function end() {
		return (double) (microtime(TRUE) - $this->start);
	}

}


さらに蛇足です。
Opteron 3280、その他条件はNECサーバーと同じのマシンで同じベンチマークをしてみました。
# ./bin/mongod --journalCommitInterval 300
という状態で、行ってみますと、3290.90 [req/sec]、でした。だいぶ遅いですね、半分以下の性能になってしまっています。

Celeron G540とOpteron3280の1コアあたりの性能は、Celeronが若干上で、全体の性能はコア数で稼ぐOpteronが高い、という結論が普段のベンチマークからは導かれているのですが。。。

バイナリ版のMongoDBを使っている影響かもしれません。。。つまりIntelに最適化されたバイナリの可能性が高いと思います。ソースからコンパイルするのは面倒なのでしませんが、まあこういうことがありましたということで。。。こんな下の方のページ、誰も見ていないでしょうけれど。

-> Opteronの方でMongoDBをソースからコンパイルしてパフォーマンスを取ってみましたが、バイナリ版と差がありません。。。Celeron G540の方もソースからやってみましたが、バイナリと変わらない成績でした。うーむ、バイナリ版MongoDBで十分のようです。