これは事実の半分に過ぎない。
naoyaのはてなダイアリー - Linux のページキャッシュということでデータサイズを見てページキャッシュに任せられそうなサイズなら OS に任せておくのが良いんじゃないかなと思います。
まず、Disk Cacheはreadには強くても、writeにはそれほど強くないということ。以下をご覧いただきたい。これは、/usr/shre/dict/wordsをBerkeley DBに変換するというトリビアルな例で(ソースは後ろ)、./が普通のdisk,/mdがmemory disk(md)。読み込みに関しては差が全く出ていないのに、書き込みに関しては有意に遅い。
# write /usr/share/dict/words => ./words.db: 10.1032350063324 seconds. /usr/share/dict/words => /md/words.db: 8.18685793876648 seconds. /usr/share/dict/words => ./words.po: 1.75748896598816 seconds. # read ./words.db: 5.22998595237732 seconds. /md/words.db: 5.23475217819214 seconds. ./words.po: 1.21963405609131 seconds. # read ./words.db: 4.97374606132507 seconds. /md/words.db: 5.02336287498474 seconds. ./words.po: 1.21673989295959 seconds.
こちらはキーあたりの値が小さいのでそれほどの差はないが、キーあたりのデータ量が増えると、この差はもっと開く。
/dev/shm に参照系DBを持っていくと I/O 負荷が激減した件(当たり前だけど) :: Drk7jpちなみに、今回実施した参照系DBとは mysql じゃなくって独自のインデックスデータだったりするので mysql とかだと別解とかあると思います
そうなるのは、この事を考えればごく自然な帰結に思える。
実は、DBMから(Postgre|My)SQLやSQLite、そしてOracleやDB/2といった商用DBに至るまで、Database(以下DB; Berkeley DBを指す場合はそう書く)と名のつくものは設計に一つの大前提を課している。それは Memory <<<< ディスク上のデータ というものだ。あくまでメモリーにはDBの一部を読み込み、変更があればそれを確実にディスクに反映させる。これはDBがなんのためにあるかを考えたら当然のことだが、現在ではこの前提は必ずしも正しいとは言えなくなってきている。
DBの実装が高速になったおかげで、かつてはDBを使わないようなケースでもDBをカジュアルに使うようになる一方、RAMとHDDの速度差は昔とそれほど変わらない。だからmemcachedのようなソリューションが登場するのだが、なんだか本末転倒にも思える。
DBというのは、その利便性と引き換えに、かなりのオーバーヘッドが課せられる。SQLであれば、それを解釈して実行して、さらにO/R Mapperを使ってオブジェクトに戻さねばならない。DBMやBerkeley DBのようなシンプルなDBでもこの事情は変わらない。そしてそれがDBである限り、フィールドが一つでも変わったらそれをディスクに反映させなければならない。そしてメモリーとディスクの間には、何段階ものキャッシュが横たわっている。
このあたりの「中間搾取」を何とか減らせないだろうか。
一つ考えられる手は、アプリケーションが使うオブジェクトを、DBではなく直接読み書きしてしまうという手である。最近のLLには、実はそのための手段がすでに用意されている。PerlであればStorable、PythonであればPickle、RubyであればMarshalといった具合に。
上のベンチマークで、.poというのが実はStorableを用いた例である。writeもreadもずっと高速であることがわかる。なにしろ読み込みは最初の一回、書き込みは最後の一回しかそれぞれ発生しないのである。高速なのも当然に思える。しかも生成されたファイルはDBの場合よりも小さいのだ。
LLの多くがnativeなobject serializerを持つようになった現在、Object DBという考えも視野に入れた方がいいのではないか。
Dan the Man with Too Much Data to Juggle
#!/usr/local/bin/perl
use strict;
use warnings;
use DB_File;
use Fcntl;
use Storable qw/nstore retrieve/;
use Time::HiRes qw/time/;
sub bench_w{
my ($filename, $dbname) = @_;
open my $rfh, '<', $filename or die "$dict:$!";
my $timer = time();
if ($dbname =~ /\.db$/){
tie my %db, "DB_File", $dbname, O_CREAT|O_RDWR, 0444, $DB_HASH
or die "$dbname:$!" ;
while(<$rfh>){
chomp;
$db{$_} = $_;
}
}else{
my %db;
while(<$rfh>){
chomp;
$db{$_} = $_;
}
nstore \%db, $dbname;
}
$timer = time() - $timer;
print "$filename => $dbname: $timer seconds.\n";
close $rfh;
}
sub bench_r {
my $dbname = shift;
my $timer = time();
if ($dbname =~ /\.db$/){
tie my %db, "DB_File", $dbname, O_RDONLY, 0444, $DB_HASH
or die "$dbname:$!" ;
while(my ($k, $v) = each %db){
$v eq $k or die;
}
}else{
my $db = retrieve $dbname or die "$dbname:$!";
while(my ($k, $v) = each %$db){
$v eq $k or die;
}
}
$timer = time() - $timer;
print "$dbname: $timer seconds.\n"
}
bench_w '/usr/share/dict/words' => './words.db';
bench_w '/usr/share/dict/words' => '/md/words.db';
bench_w '/usr/share/dict/words' => './words.po';
bench_r( "./words.db" );
bench_r( "/md/words.db" );
bench_r( "./words.po" );
bench_r( "./words.db" );
bench_r( "/md/words.db" );
bench_r( "./words.po" );
商用の ACL なら OODB が組み込まれていますよ。