FreeBSDで,Google leveldbを使ってみた.

まだ性能検証はしていませんが,記述を見るだけで凄そうな予感がしたGoogle謹製の新NoSQL.
GitHub - google/leveldb: LevelDB is a fast key-value storage library written at Google that provides an ordered mapping from string keys to string values.

コードを読む限り,想像以上に凄い感じでした.ドキュメント指向ではありませんが,valueにかなり自由度があるので(アスキーコードの非表示コードが入っていても良い.バイト長さえ分かっていて指定すれば,任意のバイナリデータが格納可能),なんでもできそうです.

まずは,FreeBSDにインストールしてちゃんと動くようにするまで3日かかった...

まとまったソースは今のところ配布されていないので,subversionリポジトリを落とします.

> svn co http://leveldb.googlecode.com/svn/trunk/ leveldb-read-only

HTTPアクセスにproxy設定が必要な場合は,こんな感じ.

> svn co --config-option servers:global:http-proxy-host=proxy.example.com  --config-option servers:global:http-proxy-port=123456 http://leveldb.googlecode.com/svn/trunk/ leveldb-read-only

configureも無くてMakefileしか無いので,まずは普通にmake.

> gmake
g++ -c -I. -I./include -DLEVELDB_PLATFORM_POSIX -std=c++0x -g2 db/db_bench.cc -o db/db_bench.o
cc1plus: error: unrecognized command line option "-std=c++0x"
gmake: *** [db/db_bench.o] エラー 1

デフォルトのg++はc++0xに対応していない...
portsコレクションから,ことごとく試してみました.
FreeBSD8では,portsではgcc-4.4からgcc-4.7まで入れることができます.

最新の4.6でやってみました.
(4.7はまだ不安定)
Makefileを書き換えます.

//CC = g++
CC=g++46

makeします.

g++46 -c -DLEVELDB_PLATFORM_POSIX -I. -I./include -std=c++0x -g2 db/db_bench.cc -o db/db_bench.o
In file included from ./port/port.h:14:0,
                 from ./util/coding.h:17,
                 from ./db/dbformat.h:13,
                 from ./db/db_impl.h:9,
                 from db/db_bench.cc:8:
./port/port_posix.h:10:20: fatal error: endian.h: No such file or directory
compilation terminated.
*** Error code 1

Stop in /home/hoge/leveldb-read-only.

FreeBSDでは微妙にパスが違うんです...port_posix.hを書き換えます.

//#include <endian.h>
#include <sys/endian.h>

またmake.

g++46 -c -DLEVELDB_PLATFORM_POSIX -I. -I./include -std=c++0x -g2 db/db_bench.cc -o db/db_bench.o
In file included from ./port/port.h:14:0,
                 from ./util/coding.h:17,
                 from ./db/dbformat.h:13,
                 from ./db/db_impl.h:9,
                 from db/db_bench.cc:8:
./port/port_posix.h:15:22: fatal error: cstdatomic: No such file or directory
compilation terminated.
*** Error code 1

Stop in /home/hoge/leveldb-read-only.

これはよく分からなかったのですが,includeファイルを調べるとファイル名が違うっぽいので,またソースport_posix.hを書き換えます.

//#include <cstdatomic>                                                                                                     
#include <atomic>

またまたmake.

> gmake
g++46 -c -DLEVELDB_PLATFORM_POSIX -I. -I./include -std=c++0x -g2 db/db_bench.cc -o db/db_bench.o
In file included from ./port/port.h:14,
                 from ./util/coding.h:17,
                 from ./db/dbformat.h:13,
                 from ./db/db_impl.h:9,
                 from db/db_bench.cc:8:
./port/port_posix.h:21: error: '__BYTE_ORDER' was not declared in this scope
./port/port_posix.h:21: error: '__LITTLE_ENDIAN' was not declared in this scope
*** Error code 1

Stop in /home/hoge/leveldb-read-only.

これはー...定数の定義がFreeBSDはちょっと違うのです.ソースを書き換えます.

//static const bool kLittleEndian = (__BYTE_ORDER == __LITTLE_ENDIAN);
static const bool kLittleEndian = (_BYTE_ORDER == _LITTLE_ENDIAN);

またまたまたmake.

g++46 -c -DLEVELDB_PLATFORM_POSIX -I. -I./include -std=c++0x -g2 ./util/cache.cc -o ./util/cache.o
./util/cache.cc: In member function 'void leveldb::<unnamed>::LRUCache::Unref(leveldb::<unnamed>::LRUHandle*)':
./util/cache.cc:148: error: 'free' was not declared in this scope
./util/cache.cc: In member function 'virtual leveldb::Cache::Handle* leveldb::<unnamed>::LRUCache::Insert(const leveldb::Slice&, void*, size_t, void (*)(const leveldb::Slice&, void*))':
./util/cache.cc:197: error: 'malloc' was not declared in this scope
*** Error code 1

Stop in /home/hoge/leveldb-read-only.

これはバグなんじゃね?って感じです.mallocやfree使うのにヘッダファイルがincludeされてない...ソースを修正します.

#include <stdlib.h>

またまたまたまたmake.

g++46 -c -DLEVELDB_PLATFORM_POSIX -I. -I./include -std=c++0x -g2 ./util/env_posix.cc -o ./util/env_posix.o
./util/env_posix.cc: In member function 'virtual leveldb::Status leveldb::<unnamed>::PosixSequentialFile::Read(size_t, leveldb::Slice*, char*)':
./util/env_posix.cc:43: error: 'fread_unlocked' was not declared in this scope
./util/env_posix.cc: In member function 'virtual leveldb::Status leveldb::<unnamed>::PosixMmapFile::Sync()':
./util/env_posix.cc:230: error: 'fdatasync' was not declared in this scope
*** Error code 1

Stop in /home/hoge/leveldb-read-only.

これは厳しい.fdatasyncやfread_unlockedはFreeBSDはまだサポートしていないんです.
仕方が無いので,遅い関数名に書き換えます.

//    size_t r = fread_unlocked(scratch, 1, n, file_);
    size_t r = fread(scratch, 1, n, file_);         

//      if (fdatasync(fd_) < 0) {
      if (fsync(fd_) < 0) {    

またまたまたまたまたmake.
リポジトリの最初の頃は違いましたが,現在はアーカイブライブラリを作るようになっているので,*.oじゃなくて*.aをリンクすれば良いようになっています.
テストプログラムを走らせると,

> ./arena_test 
/libexec/ld-elf.so.1: /usr/lib/libstdc++.so.6: version GLIBCXX_3.4.10 required by /home/hoge/leveldb-read-only/arena_test not found

おっと,これはgccバージョンを混在させているためなので,

> env LD_LIBRARY_PATH=/usr/local/lib/gcc46/ ./arena_test 
==== Test ArenaTest.Empty
==== Test ArenaTest.Simple
==== PASSED 2 tests

OK!
次,肝心なデータベースのテストを走らせると

> env LD_LIBRARY_PATH=/usr/local/lib/gcc46/ ./db_test
==== Test DBTest.Empty
Assertion failed: (internal_key.size() >= 8), function ExtractUserKey, file ./db/dbformat.h, line 87.
Abort (core dumped)

あり?
散々悩んだあげく,またコンパイラバージョンを変えてみると

CC = g++45

イケた.

> env LD_LIBRARY_PATH=/usr/local/lib/gcc45/ ./db_test
==== Test DBTest.Empty
==== Test DBTest.ReadWrite
==== Test DBTest.PutDeleteGet
==== Test DBTest.IterEmpty
==== Test DBTest.IterSingle
==== Test DBTest.IterMulti
==== Test DBTest.IterSmallAndLargeMix
==== Test DBTest.Recover
==== Test DBTest.RecoveryWithEmptyLog
==== Test DBTest.MinorCompactionsHappen
==== Test DBTest.RecoverWithLargeLog
==== Test DBTest.CompactionsGenerateMultipleFiles
==== Test DBTest.SparseMerge

Step 9900 of 10000
2705 entries compared: ok=1
2684 entries compared: ok=1
2705 entries compared: ok=1
==== PASSED 24 tests

Ok!

putとgetのマックス最小のプログラムも通りました.
データの書き込み.

#include <cassert>
#include <iostream>
#include "leveldb/db.h"
int main()
{

  leveldb::DB* db=NULL;
  leveldb::Options options;
  options.create_if_missing = true;
  leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
  assert(status.ok());

  leveldb::Slice key = "Hello,";
  std::string val;
  status = db->Get(leveldb::ReadOptions(), key, &val);
  if(!status.ok()) std::cout<<status.ToString()<<std::endl;

  std::cout<<key.ToString()<<val<<std::endl;

  delete db;
  return(0);
}

データの読み込み.

#include <cassert>
#include <iostream>
#include "leveldb/db.h"
int main()
{
  leveldb::DB* db=NULL;
  leveldb::Options options;
  options.create_if_missing = true;
  leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
  assert(status.ok());

  leveldb::Slice key = "Hello,";
  std::string val;
  status = db->Get(leveldb::ReadOptions(), key, &val);
  if(!status.ok()) std::cout<<status.ToString()<<std::endl;

  std::cout<<key.ToString()<<val<<std::endl;

  delete db;
  return(0);
}

実行してみます.

> env LD_LIBRARY_PATH=/usr/local/lib/gcc45/ ./put_leveldb

無言で終了.読みだしてみます.

> env LD_LIBRARY_PATH=/usr/local/lib/gcc45/ ./get_leveldb
Hello,World

データベースのファイルはこんな感じに格納されています.

> ls -l /tmp/testdb/
total 18
-rw-r--r--  1 hoge  wheel  123  5月 31 23:15 000005.sst
-rw-r--r--  1 hoge  wheel  123  5月 31 23:15 000008.sst
-rw-r--r--  1 hoge  wheel  123  5月 31 23:15 000011.sst
-rw-r--r--  1 hoge  wheel  123  5月 31 23:15 000014.sst
-rw-r--r--  1 hoge  wheel  123  5月 31 23:19 000019.sst
-rw-r--r--  1 hoge  wheel    0  5月 31 23:19 000020.log
-rw-r--r--  1 hoge  wheel    0  5月 31 23:19 000021.sst
-rw-r--r--  1 hoge  wheel   16  5月 31 23:19 CURRENT
-rw-r--r--  1 hoge  wheel    0  5月 31 23:19 LOCK
-rw-r--r--  1 hoge  wheel  484  5月 31 23:19 LOG
-rw-r--r--  1 hoge  wheel  353  5月 31 23:19 LOG.old
-rw-r--r--  1 hoge  wheel  220  5月 31 23:19 MANIFEST-000018

圧縮されているだけあってファイルサイズ小さいですね.まだデータが少ないから?
次は,まずはベンチマーク.Tokyo/Kyoto Cabinetやmemcachedとの速度比較ですね.それと前後して,Linuxでのインストールを成功させたいです.