この使い方は次のような用途に超便利です:
低速RDBMSシステム用の書き込み集中型キャッシュ
組み込みシステム
永続的なデータを必要としないPCI準拠システム
ライブラリから簡単に削除できるデータを持つ軽量データベースを必要とするユニットテスト
MongoDBのクエリー/リトリーブの機能を巧みに利用して、ディスク操作を伴わずにクエリー/リトリーブを行うことができるのです。ご存知のように、ディスクIOは99%のケースでボトルネックになり、データを書き込むにはディスク操作は避けられません。
MongoDBは、メモリシャドウファイルを使ってディスクファイルのデータの読み込みと書き込みのリクエストを処理できるようにしました。つまり、RAMとディスクを別々に扱うのではなく、MongoDBはファイルを巨大な配列として扱い、バイト単位でデータにアクセスします!この設計上の決定により、MongoDBはRAM上で何の修正も加えることなく実行できるのです。
実施方法
# mkdir /ramdata
# mount -t tmpfs -o size=16000M tmpfs /ramdata/
# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/xvde1 5905712 4973924 871792 86% /
none 15344936 0 15344936 0% /dev/shm
tmpfs 16384000 0 16384000 0% /ramdata
無駄なRAMを減らすために、smallfilesとnopreallocをtrueに設定します。今はRAMベースなので、そうしてもパフォーマンスはまったく落ちません。この時点でjournalを使う意味はないので、nojournalをtrueに設定してください。
dbpath=/ramdata
nojournal = true
smallFiles = true
noprealloc = true
MongoDBが起動すると、非常にうまく動作し、ファイルシステム内のファイルが期待通りに表示されることがわかります:
# mongo
MongoDB shell version: 2.3.2
connecting to: test
> db.test.insert({a:1})
> db.test.find()
{ "_id" : ObjectId("51802115eafa5d80b5d2c145"), "a" : 1 }
# ls -l /ramdata/
total 65684
-rw-------. 1 root root 16777216 Apr 30 15:52 local.0
-rw-------. 1 root root 16777216 Apr 30 15:52 local.ns
-rwxr-xr-x. 1 root root 5 Apr 30 15:52 mongod.lock
-rw-------. 1 root root 16777216 Apr 30 15:52 test.0
-rw-------. 1 root root 16777216 Apr 30 15:52 test.ns
drwxr-xr-x. 2 root root 40 Apr 30 15:52 _tmp
では、データを追加して、まったく問題なく動作することを確認してみましょう。まず1KBのドキュメントを作成し、MongoDBに400万回追加します:
> str = ""
> aaa = "aaaaaaaaaa"
aaaaaaaaaa
> for (var i = 0; i < 100; ++i) { str += aaa; }
> for (var i = 0; i < 4000000; ++i) { db.foo.insert({a: Math.random(), s: str});}
> db.foo.stats()
{
"ns" : "test.foo",
"count" : 4000000,
"size" : 4544000160,
"avgObjSize" : 1136.00004,
"storageSize" : 5030768544,
"numExtents" : 26,
"nindexes" : 1,
"lastExtentSize" : 536600560,
"paddingFactor" : 1,
"systemFlags" : 1,
"userFlags" : 0,
"totalIndexSize" : 129794000,
"indexSizes" : {
"_id_" : 129794000
},
"ok" : 1
}
ご覧のように、ドキュメントの平均サイズは1136バイトで、データは合計5GBの容量を占めています。RAMにデータの重複がないか、MongoDBにデータのコピーがあるか、ファイルシステムにコピーがあるか。MongoDB は自分自身のプロセスではデータをキャッシュせず、ファイルシステムのキャッシュにのみデータをキャッシュすることを覚えておいてください。ファイルシステムのキャッシュをクリアして、RAM にあるデータを確認しましょう:
# echo 3 > /proc/sys/vm/drop_caches
# free
total used free shared buffers cached
Mem: 30689876 6292780 24397096 0 1044 5817368
-/+ buffers/cache: 474368 30215508
Swap: 0 0 0
ご覧の通り、6.3GBのRAMのうち、5.8GBがファイルシステムのキャッシュ(バッファ)に使われています。なぜ、すべてのキャッシュをクリアしてもまだ5.8GBのファイルシステムキャッシュが残っているのでしょうか?Linuxはとても賢いので、tmpfsとキャッシュに重複したデータを保持しないからです。すごい!つまり、RAMにはデータのコピーが1つしかないということです。以下のドキュメントをすべて見て、RAMの使用量が変わらないことを確認してください:
> db.foo.find().itcount()
4000000
# free
total used free shared buffers cached
Mem: 30689876 6327988 24361888 0 1324 5818012
-/+ buffers/cache: 508652 30181224
Swap: 0 0 0
# ls -l /ramdata/
total 5808780
-rw-------. 1 root root 16777216 Apr 30 15:52 local.0
-rw-------. 1 root root 16777216 Apr 30 15:52 local.ns
-rwxr-xr-x. 1 root root 5 Apr 30 15:52 mongod.lock
-rw-------. 1 root root 16777216 Apr 30 16:00 test.0
-rw-------. 1 root root 33554432 Apr 30 16:00 test.1
-rw-------. 1 root root 536608768 Apr 30 16:02 test.10
-rw-------. 1 root root 536608768 Apr 30 16:03 test.11
-rw-------. 1 root root 536608768 Apr 30 16:03 test.12
-rw-------. 1 root root 536608768 Apr 30 16:04 test.13
-rw-------. 1 root root 536608768 Apr 30 16:04 test.14
-rw-------. 1 root root 67108864 Apr 30 16:00 test.2
-rw-------. 1 root root 134217728 Apr 30 16:00 test.3
-rw-------. 1 root root 268435456 Apr 30 16:00 test.4
-rw-------. 1 root root 536608768 Apr 30 16:01 test.5
-rw-------. 1 root root 536608768 Apr 30 16:01 test.6
-rw-------. 1 root root 536608768 Apr 30 16:04 test.7
-rw-------. 1 root root 536608768 Apr 30 16:03 test.8
-rw-------. 1 root root 536608768 Apr 30 16:02 test.9
-rw-------. 1 root root 16777216 Apr 30 15:52 test.ns
drwxr-xr-x. 2 root root 40 Apr 30 16:04 _tmp
# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/xvde1 5905712 4973960 871756 86% /
none 15344936 0 15344936 0% /dev/shm
tmpfs 16384000 5808780 10575220 36% /ramdata
予想通りです! :)
コピーについてはどうですか?
サーバを再起動するとRAM上のすべてのデータが失われるため、レプリケーションを使用することをお勧めします。標準的なレプリカセットを使用すると、自動フェイルオーバーが可能になり、データの可読性が向上します。サーバーが再起動した場合、同じレプリカセット内の別のサーバーからデータを読み込むことで、データを再構築することができます。大量のデータとインデックスがあっても、インデックス作成処理はRAMで行われるため、この処理は十分に高速です :)
1つの重要な点は、書き込み操作はローカル・データベースにあるoplogと呼ばれる特別なコレクションに書き込むということです。デフォルトでは、そのサイズは全データボリュームの5%です。私の場合、oplogは16GBの5%、つまり800MBのスペースを占有することになります。不確実な場合は、oplogSizeオプションを使用してoplogの固定サイズを選択する方が安全です。代替サーバーがoplogのサイズより長くダウンした場合、再同期する必要があります。サイズを1GBに設定するには、次のようにします:
oplogSize = 0010
スライスは?
MongoDBのクエリ機能をすべて手に入れたところで、MongoDBを使って大規模なサービスを実装するには何が必要でしょうか?大規模でスケーラブルなインメモリデータベースを実装するために、シャーディングはいくらでも使えます。サーバーの構成も、ディスクベースのソリューションを使うよりはまだマシです。なぜなら、これらのサーバーは大量のアクティビティがあるわけではなく、クラスタをゼロから再構築し続けるのは楽しくないからです。
ほら
RAMは希少なリソースであり、この場合、データセット全体がRAMに収まるようにしたいものです。tmpfsにはディスクの助けを借りてスワップする機能がありますが、パフォーマンスの劣化が大きくなります。RAMを最大限に活用するには、次のことを考慮する必要があります:
usePowerOf2Sizes オプションによるストレージバケットの正規化
定期的にcompactコマンドを実行するか、ノードを再同期します。
スキーマの設計はかなり標準化されている必要があります。
はんけつをくだす
これで、MongoDBをインメモリデータベースとして使えるようになります!パフォーマンスについては、かなり印象的なはずです。シングルスレッド/コアでテストしたところ、1秒間に20Kの書き込みを達成できました。