MongoDBのメモリの使い方とプレヒーティング

MongoDBのMongoDBのメモリの使い方とプレヒーティングについて簡単に解説します。

*前提はMongoDB ～2.6 on Linuxです。2.8の新しいストレージエンジン(wired tiger)ではこの話は関係ありません。*

## MongoDBのメモリ管理

まずMongoDBがどうやってメモリを管理しているかですが、

**結論から言うとメモリは管理していません。**

OSにすべてを任せています。具体的にはLinuxOSのmmap()というシステムコールを利用しています

### mmapとは？

mmapはファイルシステム上のファイルをメモリにマッピングして、プロセスからはメモリ上に乗せられたファイルの中身に高速にアクセスできる仕組みです。
プロセス起動直後にmmapでファイルをマップしても、最初は何もメモリには乗りません。
プロセスがファイルを読み取ろうとしたときに初めてメモリに乗せます。
その後はメモリ上にとどまり続け、次回以降のアクセスはメモリで応答します。
その後メモリが溢れてきたらmmapのアルゴリズムによりメモリから退かされます。これがページフォルトです。

### MongoDBとmmap

MongoDBではデータディレクトリ以下のデータベースファイルをmmapでメモリに乗せています。
データディレクトリの中をlsするとデータベース名.0やデータベース名.1といったものがあると思いますが、
これがデータベースファイルです。

```
[db]# ls -lh
合計 545M
drwxr-xr-x 2 root root 4.0K 11月 27 22:40 2014 journal
-rw------- 1 root root  64M 11月 27 22:40 2014 local.0
-rw------- 1 root root  16M 11月 27 22:40 2014 local.ns
-rwxr-xr-x 1 root root    6 11月 27 22:40 2014 mongod.lock
-rw------- 1 root root  64M 11月 27 21:15 2014 test.0 ★これ
-rw------- 1 root root 128M 11月 27 21:15 2014 test.1 ★これ
-rw------- 1 root root 256M 11月 27 21:15 2014 test.2 ★これ
-rw------- 1 root root  16M 11月 27 21:15 2014 test.ns
```

### データファイルの中身
データファイルの中身はデータ自体(BSON)とインデックスです。
データファイルの中にデータ用エクステントとインデックス用エクステントが可変長で格納されています。
細かい話は以下のスライドあたりがわかりやすいです。
http://blog.mongolab.com/2014/01/how-big-is-your-mongodb/

## MongoDBがメモリに乗せるタイミング

### 起動時にはメモリに乗らない

今までの話でお分かりと思いますが、

**MongoDBは起動時にはデータとインデックスはメモリに乗りません。**

それはmmapを使っているためです。

例えば、以下hogeコレクションを見てみましょう

```
> db.hoge.stats()
{
        "ns" : "test.hoge",
        "count" : 1740681,
        "size" : 83552848,
        "avgObjSize" : 48,
        "storageSize" : 123936768,
        "numExtents" : 11,
        "nindexes" : 4,
        "lastExtentSize" : 37625856,
        "paddingFactor" : 1,
        "systemFlags" : 0,
        "userFlags" : 1,
        "totalIndexSize" : 228331152,
        "indexSizes" : {
                "_id_" : 50846544,
                "a_1" : 43807008,
                "_id_1_a_1" : 66838800,
                "a_1__id_1" : 66838800
        },
        "ok" : 1
}
```

このようにデータサイズは83552848(約80M),インデックスサイズは228331152(約220M)ですが、serverStatus()を見ると

```
> db.serverStatus().mem
{
        "bits" : 64,
        "resident" : 47,　←実メモリは47M
        "virtual" : 1482, 
        "supported" : true,
        "mapped" : 544, ←マップされているのは544M
        "mappedWithJournal" : 1088
}
```

` "resident" : 47　` となっているため47Mしかメモリを使っていないことになります。
つまりhogeコレクションデータもインデックスも全くメモリには乗っていません。
ただしマップはされています。

### クエリを書けると徐々にメモリに乗ってくる

起動直後はメモリには一切なく、クエリをかけて必要になったデータ・インデックスからメモリに乗っていきます。

**なのでMongoDB起動直後は応答速度は遅いです。**

これは困りますよね。

## プレヒーティング

そこでプレヒーティングの出番です。
プレヒーティングをすれば、好きなデータをメモリに乗せられます。
文法は以下の通り

```
db.runCommand({ touch: "コレクション名", data: [true|false], index: [true|false] })
```

例えば先ほどの例でインデックスだけをメモリに乗せてみましょう。

```
>  db.serverStatus().mem
{
        "bits" : 64,
        "resident" : 48,　←起動直後は48M
        "virtual" : 1482,
        "supported" : true,
        "mapped" : 544,
        "mappedWithJournal" : 1088
}
> db.runCommand({ touch: "hoge", data: false, index: true })　←インデックスだけメモリに乗せる
{
        "indexes" : {
                "num" : 4,
                "numRanges" : 30,
                "millis" : 48
        },
        "ok" : 1
}
> db.serverStatus().mem
{
        "bits" : 64,
        "resident" : 324,　←インデックスだけメモリ乗った
        "virtual" : 1482,
        "supported" : true,
        "mapped" : 544,
        "mappedWithJournal" : 1088
}
```
すると、` "resident" : 324 `とインデックスの分だけ増えました（ちょっと計算が合わないけど。。。）