現在担当している業務で、CassandraやRedisなどいくつかのNoSQLデータベースの導入検討をしてきましたが、最終的にMongoDBを使うことになりました。アプリケーションの上の層ではPythonやPHPで作りますが、パフォーマンス要件が厳しい部分はC++ドライバを使う予定です。
まずはインストールから。
* 環境
Linux 2.6.18-194.26.1.el5 x86_64 (CentOS release 5.7)
gcc version 4.4.4 20100726
インストール
C++ドライバは Boost と pcre 、SCons が別途必要なのでそちらを先にインストールします。Boostはインストール済みな人も多いかと思いますが、僕の環境ではバージョンが古めだったのでアップデートしました。v1.47からは bjam の他に b2 でもビルドできるようです。yumだと古いバージョンが入るのでソースからビルドします。
1 2 3 4 5 |
$ wget http://sourceforge.net/projects/boost/files/boost/1.47.0/boost_1_47_0.tar.gz/download $ tar zxf boost_1_47_0.tar.gz $ cd boost_1_47_0 $ ./bootstrap.sh $ sudo ./b2 install --prefix=/usr/local ## インストールしたいディレクトリ |
/usr/local/ 以下を汚したくない場合は prefix で任意のディレクトリを指定すればOKです。
次にpcreですが、こちらはyum(Debian系はapt)で入れても大丈夫です。バージョンの古さが気になる場合は Utter Ramblings レポジトリを追加してそこからインストールするといいです。
1 |
$ sudo yum install pcre.x86_64 --enablerepo=utter |
MongoDB C++ドライバのビルドには SCons を使うので、もし入っていなければインストールします。
1 |
$ sudo yum install scons |
目的のMongoDB C++ドライバ(ここではv2.0を使う)を入れます。頻繁に更新されているようなので注意してください。特にv2.0のドライバは古いものだとコンパイルに失敗することが多いようです。
C++ Driver Download
1 2 3 4 5 6 |
## modified 2011-11-09 17:53:31 のドライバをビルド $ wget http://downloads.mongodb.org/cxx-driver/mongodb-linux-x86_64-v2.0-latest.tgz $ tar zxf mongodb-linux-x86_64-v2.0-latest.tgz $ cd cd mongo-cxx-driver-v2.0 $ scons -c ## 念のためビルド前にターゲットをクリーンアップしておく $ scons --extrapath=/usr/local ## boostのインストールされているディレクトリを指定 |
scons でも make と同様に -j オプションで並列ビルドができますが今回は付けませんでした。案の定、コンパイルにはけっこうな時間がかかってしまいましたが。。
無事にビルドできたら、出来上がった libmongoclient.so を ldd で依存関係を確認しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ ldd ./libmongoclient.so libpthread.so.0 => /lib64/libpthread.so.0 (0x00002ae841b9e000) libboost_thread.so.1.47.0 => /usr/local/lib/libboost_thread.so.1.47.0 (0x00002ae841db9000) libboost_filesystem.so.1.47.0 => /usr/local/lib/libboost_filesystem.so.1.47.0 (0x00002ae841fd2000) libboost_system.so.1.47.0 => /usr/local/lib/libboost_system.so.1.47.0 (0x00002ae8421f2000) libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00002ae8423f5000) libm.so.6 => /lib64/libm.so.6 (0x00002ae8426f5000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00002ae842979000) libc.so.6 => /lib64/libc.so.6 (0x00002ae842b87000) /lib64/ld-linux-x86-64.so.2 (0x0000003cd5a00000) librt.so.1 => /lib64/librt.so.1 (0x00002ae842edf000) ## 確認したら ldconfig で共有ライブラリのリンク設定を更新 $ sudo cp ./libmongoclient.so /usr/local/lib $ sudo ldconfig /usr/local/lib |
動作確認
インストールが終わったら動作確認。ここでは簡単なCRUD操作を試してみます。
* データベース: test, コレクション: users
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
/* mongo_test.cpp */ #include <iostream> #include <mongo/client/dbclient.h> int main(void) { using namespace std; try { /* connect */ mongo::DBClientConnection client; client.connect("localhost:27017"); /* database: test, collection: users */ const string ns = "test.users"; /* insert */ /* { "name": "Ryo", "age": 26 } */ /* { "name": "Ryo", "age": 26, "address": "tokyo" } */ cout << "--- insert ---" << endl; mongo::BSONObjBuilder builder; builder.append("name", "Ryo").append("age", 26); // method chain mongo::BSONObj doc = builder.asTempObj(); cout << doc.toString() << endl; client.insert(ns, doc); builder.append("address", "tokyo"); mongo::BSONObj doc2 = builder.obj(); cout << doc2.toString() << endl; client.insert(ns, doc2); /* find */ /* { } , fetch all documents */ cout << "--- find ---" << endl; auto_ptr<mongo::DBClientCursor> cursor = client.query(ns, mongo::BSONObj()); // represent { } while(cursor->more()) { mongo::BSONObj p = cursor->next(); mongo::OID oid = p["_id"].OID(); // retrieve ObjectId string name = p["name"].str(); // retrieve string value int age = p["age"].numberInt(); // retrieve integer value string address = p["address"].str(); cout << "ObjectId: " << oid << endl; cout << "name: " << name << endl; cout << "age: " << age << endl; cout << "address: " << address << endl; } /* update (using BSON macro) */ /* { "$set": { "name": "Joe" } } */ cout << "--- update ---" << endl; mongo::Query query(BSON("name" << "Ryo")); mongo::BSONObj modifier = BSON("$set" << BSON("name" << "Joe")); cout << modifier.toString() << endl; client.update(ns, query, modifier, /* upsert */ false, /* multi */ true); cursor = client.query(ns, mongo::BSONObj()); while(cursor->more()) { mongo::BSONObj p = cursor->next(); cout << p.toString() << endl; // JSON string output } /* remove */ /* { "name": "Joe" } */ cout << "--- remove ---" << endl; client.remove(ns, mongo::Query(BSON("name" << "Joe"))); } catch(const mongo::ConnectException& e) { cerr << "connect error" << endl; cerr << e.getCode() << endl; cerr << e.what() << endl; } catch(const mongo::DBException& e) { cerr << e.getCode() << endl; cerr << e.what() << endl; } return 0; } |
* コンパイル/実行
1 2 |
$ g++ -g -Wall -I/usr/local/include -L/usr/local/lib -lmongoclient mongo_test.cpp -o mongo_test $ ./mongo_test |
* 出力結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- insert --- { name: "Ryo", age: 26 } { name: "Ryo", age: 26, address: "tokyo" } --- find --- ObjectId: 4ebfc78bb7e28e3bc501cd08 name: Ryo age: 26 address: ObjectId: 4ebfc78bb7e28e3bc501cd09 name: Ryo age: 26 address: tokyo --- update --- { $set: { name: "Joe" } } { _id: ObjectId('4ebfc78bb7e28e3bc501cd08'), name: "Joe", age: 26 } { _id: ObjectId('4ebfc78bb7e28e3bc501cd09'), name: "Joe", age: 26, address: "tokyo" } --- remove --- |
Cドライバと比べるとインタフェースはかなり高級です。
mongo::BSONObjBuilder のメソッドは内部で return *this しているのでメソッドチェーンができるようです(実際使うかどうかは別として)。この mongo::BSONObjBuilder のインスタンスは使い回したいと思うかもしれませんが、規模が大きくなってくるといろいろ罠にハマりやすいので(例えば、obj() を一度呼んでいるのに後でうっかり append() してしまうとSEGVする等)、使う度に生成するようにした方がいいような気がします。コードが読み辛くなるのを許容できるなら、BSONObjBuilder を一切使わずに BSON マクロのみでクエリを組み立てるのもいいかもしれません。
また、mongo::DBClientConnection::query() は戻り値として std::auto_ptr<mongo::DBClientCursor> を返すようになっているので注意。boost必須のドライバなんだし boost::shared_ptr を返すようにすればいいのにと思いますが、。
今回はMongoDB C++ドライバを使って簡単なCRUD操作を試してみました。今回紹介できなかったクラスもまだたくさんあるので少しずつ調べていこうと思います。
* C++ドライバ APIリファレンス
doxygen API docs