blog

MongoDBを純粋なインメモリデータベースとして使う

MongoDBをインメモリデータベースとして使うこと、つまりMongoDBにデータをディスクに保存させないことは、ますます関心を集めています。...

Apr 21, 2022 · 10 min. read
シェア

この使い方は次のような用途に超便利です:

低速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の書き込みを達成できました。

Read next