MongoDB超入門

MongoDB勉強中です、間違っていたらすみません。


## 目次
* MongoDBの特徴
* コマンド一覧
* JavaScriptシェルのサンプル
* コレクション
* ドキュメント
* クエリセレクタ
* インデックス
* レプリケーション
* シャーディング


## MongoDBの特徴

* ドキュメント・データベース
* NoSQL
* BSON
* B-treeインデックス
    * 1コレクションに対し最大64個
    * http://blog.h13i32maru.jp/entry/2012/07/01/000000
* 速度と耐久性
* レプリケーション
* シャーディング
* キャッシュ機構の充実による高パフォーマンス
    * データをメモリにキャッシュする仕様
    * memcachedなどを導入しなくてもある程度のパフォーマンスを確保可能

### BSON
* MongoDBのデータストレージ及びネットワーク転送フォーマットとして利用されるデータ交換フォーマット
* 使える型(ほぼJSON)
    * string
    * integer
    * double
    * date: unix時間のミリ秒
    * byte array: バイナリデータ配列
    * boolean
    * null
    * BSONオブジェクト
    * BSON配列G
    * 正規表現
    * JavaScriptコード
    * Number型は使えない
    * JSONはdateとbyte arrayを持たない
* JSONに比べ、ストレージ容量及びスキャン速度に効率的な設計

### 速度と耐久性

* セマンティックスの選択 ⇔ シンタックス
* ジャーナリングを有効にするか
    * http://doryokujin.hatenablog.jp/entry/20110614/1308010072
* データベースのレスポンスを待たずに処理を実行する
    * fire-and-forget
    * セーフモードを設定するとレスポンスを待つことができる

#### メモ

* ジャーナリングを有効にすると30%性能減
    * http://www.adathedev.co.uk/2011/03/mongodb-journaling-performance-single.html
* レプリケーションによりジャーナリング無しでもいける？


## コマンド一覧

* mongodump, mongorestore
    * バックアップ
    * 形式はBSON
    * ホットバックアップ(オンラインバックアップ)が可能
* mongoexport, mongoinport
    * 形式はJSON, CSV, TSVG
    * 初期データのインポートに有用
* mongosniff
    * データベースに送信された操作を覗き見することができる
    * 通信経路を流れるBSONを人が読めるシェル構文に変換してくれる
* mongostat
    * iostatと似たツール
    * 統計情報の提供をしてくれる
    * 毎秒の操作数、仮想メモリ量、サーバの接続数等


## JavaScriptシェルのサンプル

### 基本的なコマンド

* [`insert`](#insert)
* [`save`](#save)
* [`find`](#find)
* [`gt,lt`](#gt)
* [`count`](#count)
* [`update`](#update)
* [`remove`](#remove)
* [`drop`](#drop)
* [`ensureIndex`](#ensureIndex)

<a name='insert'/>
__insert__

```js
> db.users.insert({ username: 'test' });
```

<a name='save'/>
__save__

```js
> db.users.save({ username: 'test2' });
> db.users.save({ username: 'test3', favorites: { movies: ['piyo'] } });
> db.users.save
function ( obj , opts ){
    if ( obj == null )
        throw "can't save a null";

    if ( typeof( obj ) == "number" || typeof( obj) == "string" )
        throw "can't save a number or string"

    if ( typeof( obj._id ) == "undefined" ){
        obj._id = new ObjectId();
        return this.insert( obj , opts );
    }
    else {
        return this.update( { _id : obj._id } , obj , Object.merge({ upsert:true }, opts));
    }
}
```

<a name='find'/>
__find__

```js
> db.users.find();
> db.users.find({ username: 'test' });
> db.users.find({ 'favorites.movies': 'piyo' });

// AND検索
> db.users.find({ username: 'test', 'favorites.moveies': 'piyo' });
```
* クエリセレクタも参照

__it__

```js
> for (var i = 0; i < 20000; i++) {
  db.numbers.save({ num: i });
}

// 20件までしか表示されない
> db.numbers.find();

// 次の20件の表示
> it
```

<a name='gt'/>

__gt, lt__

```js
> for (var i = 0; i < 20000; i++) {
  db.numbers.save({ num: i });
}

> db.numbers.find({ num: { $gt: 20, $lt: 25 }});
```

<a name='count'/>
__count__

```js
> db.users.count();
```

<a name='update'/>
__update__

```js
> db.users.update({ username: 'test' }, { $set: { country: 'Canada' }});
> db.users.update({ username: 'test' }, { $unset: { country: 1 }});

> db.users.update({ username: 'test' }, {
  $set: {
    favorites: {
      cities: ['Tokyo', 'Gunma'],
      movies: ['hoge', 'fuga']
    }
  }  
});

// $set 上書きされる、citiesも消える
> db.users.update({ username: 'test' }, {
  $set: {
    favorites: {
      movies: ['piyo']
    }
  }  
});

// $push 追加する
> db.users.update({ username: 'test' }, {
  $push: {
    'favorites.movies': 'piyo'
  }
});

// $addToSet 既にあるものは追加されない
> db.users.update({ username: 'test' }, {
  $addToSet: {
    'favorites.movies': 'piyo'
  },
});

// 第４引数は複数更新
> db.users.update({ username: 'test' }, {
  $addToSet: {
    'favorites.movies': 'piyo'
  },
}, false, true);
```

<a name='remove'/>
__remove__

```js
// 全件削除 {}が必要になった様子 mongo --version v2.6.5
> db.users.remove({});

> db.users.remove({ username: 'test' });
```

<a name='drop'/>
__drop__

```js
> db.users.drop();
```

<a name='ensureIndex'/>
__index__

```js
> db.numbers.find({ num: { $gt: 19995 }}).explain();
{
    "cursor" : "BasicCursor",
    "isMultiKey" : false,
    "n" : 4,
    "nscannedObjects" : 20000,
    "nscanned" : 20000,
    "nscannedObjectsAllPlans" : 20000,
    "nscannedAllPlans" : 20000,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 9,
    "indexBounds" : {

    },
    "server" : "precise64:27017"
}

// numの昇順indexを貼る
> db.numbers.ensureIndex({ num: 1 });

> db.numbers.getIndices(); // db.numbers.getIndexes();
[
    {
        "v" : 1,
        "name" : "_id_",
        "key" : {
            "_id" : 1
        },
        "ns" : "tutorial.numbers"
    },
    {
        "v" : 1,
        "name" : "num_1",
        "key" : {
            "num" : 1
        },
        "ns" : "tutorial.numbers"
    }
]

> db.numbers.find({ num: { $gt: 19995 }}).explain();
{
    "cursor" : "BtreeCursor num_1",
    "isMultiKey" : false,
    "n" : 4,
    "nscannedObjects" : 4,
    "nscanned" : 4,
    "nscannedObjectsAllPlans" : 4,
    "nscannedAllPlans" : 4,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "num" : [
            [
                19995,
                1.7976931348623157e+308
            ]
        ]
    },
    "server" : "precise64:27017"
}
```


## コレクション

### キャップ付きコレクション

* 上限以上になると自動的に古いドキュメントが削除され、新しいドキュメントが追加される
* idに対するインデックスの自動生成がされない
    * パフォーマンス最適化のため
    * 必要な場合は手動でインデックスを貼れる
* シーケンシャルな処理の対象となるデータ構造
* 挿入順序の正順ソートで返される
* 逆順ソートの場合、$naturalを用いる
* CRUD操作に制約がある
    * Create, Read, Update, Delete
    * 個々のドキュメントの削除ができない
    * ドキュメントサイズを大きくするような更新ができない
* 設計がロギング用

### 特別なシステムコレクション

* system.namespaces
    * DBに定義されている全てのnamespaceが見れる
* system.indexes
    * DBの各インデックス定義が見れる


```js
> db.user.actions.find().sort({ "$natural": -1 });
```


## ドキュメント

### BSONのサイズを調べる

```js
> Object.bsonsize(doc);
```

### キー名もドキュメントとして保存される

* RDBMSは列名は分離されて保存される
* キー名が長い場合、レコード数に比例して容量を食ってしまう
* (例) date_of_birth ⇒ dobにすると10バイトの節約になり、10億レコードある場合、10GBの節約になる
    * キー名はなるべく短くわかりやすいものにするのが良い

### 文字列

* utf-8しか使えない
* uft-8以外の場合、コンバートするかBSONのバイナリ型で保存する必要がある

### 数値

* double, int, long
* 明示的に指定しないといけないのはJavascriptシェルで挿入する場合のみ
    * NumberLong(), NumberInt()

### 日付

* Unixのエポックミリ秒

### サイズ

* 最大16MB(MongoDB v2.0)
    * 開発者がネストが深くなる構造を避ける
    * パフォーマンス低下を防ぐ
    * 大きくなりそうな場合は分割すること

### バルクインサート

* 複数のレコードをまとめて挿入できる
* 理想的な量は10~200レコード
* ここにも16MBの制限がある



## クエリセレクタ

### 準備

```js
> db.users.save({ first_name: 'hoge', age: 20 });
> db.users.save({ first_name: 'fuga', age: 25 });
> db.users.save({ first_name: 'piyo', age: 30 });

> db.users.find();
{ "_id" : {省略}, "first_name" : "hoge", "age" : 20 }
{ "_id" : {省略}, "first_name" : "fuga", "age" : 25 }
{ "_id" : {省略}, "first_name" : "piyo", "age" : 30 }
```

### 範囲演算子

|演算子|意味|
|---|---|
|$lt|<|
|$lte|<=|
|$gt|>|
|$gte|>=|

```js
> db.users.find({ age: { $gt: 20, $lte: 25 }});
{ "_id" : {省略}, "first_name" : "fuga", "age" : 25 }
```

### 集合演算子

|演算子|意味|
|---|---|
|$in|いずれかに一致する(1つのキーに対するOR)|
|$nin|いずれも一致しない|
|$all|すべてに一致する $inのAND版|
* $in, $allはインデックスが利くが、$ninはインデックスが利かない

__$in__

```js
> db.users.find({ first_name: { $in: ['hoge', 'fuga'] } });
{ "_id" : {省略}, "first_name" : "hoge", "age" : 20 }
{ "_id" : {省略}, "first_name" : "fuga", "age" : 25 }
```

__$nin__

```js
> db.users.find({ first_name: { $nin: ['hoge', 'fuga'] } });
{ "_id" : {省略}, "first_name" : "piyo", "age" : 30 }
```

__$all__

```js
> db.users.update({ first_name: 'hoge' }, {
  $set: {
     favorites: {
        cities: ['tokyo', 'kyoto']
     }
  }
});
> db.users.find({ 'favorites.cities': { $all: ['tokyo', 'kyoto'] } });
{ "_id" : {省略}, "first_name" : "hoge", "age" : 20, favorites: { cities: ['tokyo', 'kyoto'] } }
```

### 論理演算子

|演算子|意味|
|---|---|
|$ne|一致しない値を返す(1つのキーに対するNAND)|
|$not|一致しない|
|$or|２つ以上のキーに対するOR|
|$and|AND|
|$exists|スキーマレスなためkeyが存在するかどうかを返す|

* $notは他の演算子と組み合わせて使うものではない
    * 基本否定形が用意されているため
    * $not: /^B/ みたいに使うのが正しい
* $inで済む場合は$orは使わない
* $orはクエリセレクタの配列も使える
    * $orを含むことはできない
* $andはこれでしか表現できないもののみ使う
* $andもクエリセレクタの配列を使える

__$ne__

```js
> db.users.find({ 'favorites.cities': { $ne: ['tokyo', 'kyoto'] } });
{ "_id" : {省略}, "first_name" : "fuga", "age" : 25 }
{ "_id" : {省略}, "first_name" : "piyo", "age" : 30 }

> db.users.find({ 'favorites.cities': { $ne: ['tokyo'] } });
{ "_id" : {省略}, "first_name" : "hoge", "age" : 20, favorites: { cities: ['tokyo', 'kyoto'] } }
{ "_id" : {省略}, "first_name" : "fuga", "age" : 25 }
{ "_id" : {省略}, "first_name" : "piyo", "age" : 30 }
```

__$not__

```js
> db.users.find({ 'first_same': { $not: /^ho|ga$/ } });
{ "_id" : {省略}, "first_name" : "piyo", "age" : 30 }
```

__$or__

```js
> db.users.find({
  $or: [{
    'favorites.cities': { $in: ['tokyo', 'kyoto'] }
  }, {
    first_name: 'piyo'
  }]
});
{ "_id" : {省略}, "first_name" : "piyo", "age" : 30 }
{ "_id" : {省略}, "age" : 20, "favorites" : { "cities" : [ "tokyo", "kyoto" ] }, "first_name" : "hoge" }
```

__$and__

```js
> db.users.update({ first_name: 'fuga' }, {
  $set: {
     favorites: {
        cities: ['gunma', 'fukuoka']
     }
  }
});
> db.users.find({
  $and: [
    { 'favorites.cities': { $in: ['tokyo', 'gunma'] } },
    { 'favorites.cities': { $in: ['kyoto', 'fukuoka'] } }
  ]
});
{ "_id" : {省略}, "age" : 20, "favorites" : { "cities" : [ "tokyo", "kyoto" ] }, "first_name" : "hoge" }
{ "_id" : {省略}, "age" : 25, "favorites" : { "cities" : [ "gunma", "fukuoka" ] }, "first_name" : "fuga" }
```

__$exists__

```js
> db.users.find({ 'favorites.cities': { $exists: false } });
{ "_id" : {省略}, "first_name" : "piyo", "age" : 30 }
```

### 更新演算子

|演算子|意味|
|---|---|
|$inc|数値の増減をする|
|$set|値をsetする|
|$unset|値のunsetする|
|$rename|キー名を変更する|
|$push|配列に1つの値を追加する|
|$pushAll|配列に値のlistを追加する|
|$addToSet|配列内に値が存在していない場合追加する|
|$each|$addToSetを複数行う場合に使う|
|$pop|配列から最後に追加された値を取り出す|
|$pull|配列から1つの値を指定して取り出す|
|$pullAll|配列から複数の値を指定して取り出す|

### サブドキュメント

* オブエジェクトのマッチを行うクエリはバイト単位での比較なため、キーの順序も重要

```js
> db.users.update({ first_name: 'hoge'}, { $set: { favorites: { food: 'sushi', music: 'classic' } } });

> db.users.find({ favorites: { food: 'sushi', music: 'classic' } });
{ "_id" : {省略}, "favorites" : { "food" : "sushi", "music" : "classic" }, "first_name" : "hoge" }

> db.users.find({ favorites: { music: 'classic', food: 'sushi' } });
// hitしない
```

### 配列

* 配列の場所を指定して検索できる
* $elemMatch
    * ひとつの要素内でマッチしているかどうか
    * 2つ以上のときに使う
* size検索

__初期化__

```js
> db.users.drop();

> db.users.save({
  username: 'test1' ,
  addresses: [{
    name: 'home',
    state: 'Japan'
  }, {
    name: 'work',
    state: 'NY'
  }]
});
```

__配列の0番目を指定して検索__

```js
db.users.find({ 'addresses.0.name': 'home' });
```

__配列の検索__

```js
> db.users.find({ 'addresses.name': 'home', 'addresses.state': 'NY' });
{ "_id" : {省略}, "username" : "test1", "addresses" : [ { "name" : "home", "state" : "NY" }, { "name" : "work", "state" : "NY" } ] }
```

__elemMatch__

```js
> db.users.find({ addresses: { $elemMatch: { name: 'home', state: 'NY' } } });
// hitしない

> db.users.find({ addresses: { $elemMatch: { name: 'home', state: 'Japan' } } });
{ "_id" : {省略}, "username" : "test1", "addresses" : [ { "name" : "home", "state" : "NY" }, { "name" : "work", "state" : "NY" } ] }
```

### size

```js
> db.users.find({ addresses: { $size: 2 } });
{ "_id" : {省略}, "username" : "test1", "addresses" : [ { "name" : "home", "state" : "NY" }, { "name" : "work", "state" : "NY" } ] }
```


## インデックス

### 注意点

* 貼りすぎると、挿入等の処理が重くなる
* インデックスと処理中のデータセットがRAMに収まらなければならない
    * インデックスの合計サイズはstatsコマンドで確認できる

### インデックスの種類

#### ユニークインデックス

* このインデックスを貼った場合、既にあるアイテムを挿入しようとすると失敗する
* 既に重複するデータがある場合、インデックスを貼ることができない
* dropDupsを指定すると、重複するデータを自動削除してインデックスを貼る
* 値を持っていないときnullが入りインデックスが貼られるが、２つ以上ある場合ユニーク性が保たれないので失敗する

```js
> db.users.ensureIndex({ username: 1 , { unique: true } });
> db.users.ensureIndex({ username: 1 , { unique: true, dropDups: true } });
```

#### スパースインデックス

* 値の持っているキーに対してのみインデックスを貼る

```js
> db.users.ensureIndex({ username: 1 , { sparse: true } });
> db.users.ensureIndex({ username: 1 , { unique: true, sparse: true } });
```

### マルチキーインデックス

* 配列にもインデックスが貼れる

### インデックスの生成と削除

```js
// 生成
> db.users.ensureIndex({ username: 1 });

// 削除
> db.users.dropIndex('username_1');
```

### ログの確認

* indexを貼るのに数日かかることもあり、そのステータスを見ることができる

```js
> db.currentOp();
```

### バックグラウンドでのインデックス構築

* 実働環境でもバックグランドでインデックスを貼る処理を走らせることができる

```js
> db.users.ensureIndex({ username: 1 }, { background: true });
```

### コンパクション

* 大量削除がある場合にインデックスが断片的になりRAMの消費量が増加する
* 書き込みロックが発生するため、MongoDBのインスタンスが使用不能になる
* オフライン時にしてください

```js
> db.users.reIndex();
```

### 低速クエリの検知

#### mongo.log

* 100msを超えると警告ログが出る
* mongod起動時にslowmsを付ける

```bash
$ mongod --slowms 50
```

#### プロファイラを利用する

##### 全ての書き込みを記録する

```js
> db.setProfilingLevel(2);
```

##### 指定したミリ秒以上のものを記録する(50ms)

```js
> db.setProfilingLevel(1, 50);
```

##### 無効にする

```js
> db.setProfilingLevel(0);
```

##### 遅いプロファイルの検索

* system.profileにキャップ付きコレクションで保存される

```js
> db.system.profile.find().sort({ $natural: -1 }).limit(5);
```

### 低速クエリの検証

* explainを利用する
* index貼ってみる

### カバーリングインデックス

* クエリが要求しているデータがインデックスそのものに含まれている場合ドキュメントを参照せずに返す　
    * インデックスオンリーのクエリ
* explainでindexOnlyがtrueになる



## レプリケーション

### 概要
* ２種類のレプリケーションが提供されている
    * マスター・スレーブレプリケーション
    * レプリカセット
* どちらも1つのプライマリーノードが全ての書き込みを受け取り、全てのセカンダリノードが読み取りと、自身への書き込みを非同期で行う
* 基本同じだが、レプリカセットは自動フェイルオーバーを保障する

### レプリカセット

* MySQLと比べ簡単に冗長化できる
* 自動フェイルオーバー
    * 以前のプライマリー復旧時はセカンダリーとして復旧する
* セカンダリーの有用性
    * バックアップを取るのが容易
    * 重たいインデックスを貼り、貼り終わったらプライマリーに昇格させる
* セカンダリーの読み取り時の注意点
    * ワーキングデータセットが利用可能なRAMよりもはるかに大きい場合、クエリの速度が低下する
    * 読み込みと書き込み比率が50%を超える場合、書き込み処理の速度が低下し、スループットの向上につながらない
        * スループット：単位時間あたりの処理能力
    * プライマリの最新の書き込みが反映されていることは保障されていない
        * 数時間かかるケースもある
* ノードは最低でも３つ必要
    * ２つは常時かどうのmongodインスタンスとして稼働する
        * どちらかがプライマリーとして働き、どちらも完全なコピーを持つ
    * ３つ目のノードはアビター（仲裁者）として働く
        * レプリケーションは行わない
        * 中立的な監視を行う
            * フェイルオーバーの際に新しいプライマリーを選択する

### レプリカセット作ってみる

#### 環境
* vagrant ubuntu
    * https://vagrantcloud.com/hashicorp/boxes/precise64

#### mongodの起動

```bash
# mkdir /data/node1
# mkdir /data/node2
# mkdir /data/arbiter

# mongod --replSet myapp --dbpath /data/node1 --port 40000
# mongod --replSet myapp --dbpath /data/node2 --port 40001
# mongod --replSet myapp --dbpath /data/arbiter --port 40002
[rsStart] replSet can\'t get local.system.replset config from self or any seed (EMPTYCONFIG)
```

#### レプリカセットの設定

```bash
# mongo --port 40000
```

```js
> rs.initiate();
{
    "info2" : "no configuration explicitly specified -- making one",
    "me" : "precise64:40000",
    "info" : "Config now saved locally.  Should come online in about a minute.",
    "ok" : 1
}

> rs.add('precise64:40001');
{ "ok" : 1 }

> rs.add('precise64:40002', { arbiterOnly: true });
{ "ok" : 1 }
```

#### masterのステータスの確認

```js
> rs.isMaster();
{
    "setName" : "myapp",
    "ismaster" : true,
    "secondary" : false,
    "hosts" : [
        "precise64:40000",
        "precise64:40001"
    ],
    "arbiters" : [
        "precise64:40002"
    ],
    "primary" : "precise64:40000",
    "me" : "precise64:40000",
    "maxBsonObjectSize" : 16777216,
    "maxMessageSizeBytes" : 48000000,
    "localTime" : ISODate("2014-11-20T08:38:17.701Z"),
    "ok" : 1
}

```

#### レプリカセットのステータスの確認

```js
> rs.status();
{
    "set" : "myapp",
    "date" : ISODate("2014-11-20T08:49:11Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "precise64:40000",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 1883,
            "optime" : Timestamp(1416472648, 1),
            "optimeDate" : ISODate("2014-11-20T08:37:28Z"),
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "precise64:40001",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 1679,
            "optime" : Timestamp(1416472648, 1),
            "optimeDate" : ISODate("2014-11-20T08:37:28Z"),
            "lastHeartbeat" : ISODate("2014-11-20T08:49:09Z"),
            "lastHeartbeatRecv" : ISODate("2014-11-20T08:49:10Z"),
            "pingMs" : 0,
            "syncingTo" : "precise64:40000"
        },
        {
            "_id" : 2,
            "name" : "precise64:40002",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 703,
            "lastHeartbeat" : ISODate("2014-11-20T08:49:09Z"),
            "lastHeartbeatRecv" : ISODate("2014-11-20T08:49:10Z"),
            "pingMs" : 0
        }
    ],
    "ok" : 1
}
```

### データの確認

#### プライマリーにデータ入力

```bash
# mongo precise64:40000
MongoDB shell version: 2.4.10
connecting to: precise64:40000/test
```

```js
myapp:PRIMARY> use bookstore
switched to db bookstore

myapp:PRIMARY> db.books.insert({ title: 'Oliver Twist' });
myapp:PRIMARY> show dbs
bookstore	0.203125GB
local	4.076171875GB
```

#### セカンダリーのデータ確認

```bash
# mongo precise64:40001
MongoDB shell version: 2.4.10
connecting to: precise64:40001/test
```
```js
myapp:SECONDARY> db.getMongo().setSlaveOk();

myapp:SECONDARY> show dbs
bookstore	0.203125GB
local	4.076171875GB

myapp:SECONDARY> use bookstore
switched to db bookstore

myapp:SECONDARY> db.books.find();
{ "_id" : ObjectId("546dac3a1dc17670d8dfc638"), "title" : "Oliver Twist" }
```

### フェイルオーバ

#### プライマリを落とす

* ctrl + c あたりで

#### 昇格の確認

```bash
> mongo precise64:40001
MongoDB shell version: 2.4.10
connecting to: precise64:40001/test
```
```js
myapp:PRIMARY> rs.status();
{
    "set" : "myapp",
    "date" : ISODate("2014-11-20T09:07:21Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "precise64:40000",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",
            "uptime" : 0,
            "optime" : Timestamp(1416473658, 1),
            "optimeDate" : ISODate("2014-11-20T08:54:18Z"),
            "lastHeartbeat" : ISODate("2014-11-20T09:07:21Z"),
            "lastHeartbeatRecv" : ISODate("2014-11-20T09:05:20Z"),
            "pingMs" : 0
        },
        {
            "_id" : 1,
            "name" : "precise64:40001",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 3360,
            "optime" : Timestamp(1416473658, 1),
            "optimeDate" : ISODate("2014-11-20T08:54:18Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "precise64:40002",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 1791,
            "lastHeartbeat" : ISODate("2014-11-20T09:07:21Z"),
            "lastHeartbeatRecv" : ISODate("2014-11-20T09:07:21Z"),
            "pingMs" : 0
        }
    ],
    "ok" : 1
}
```


## シャーディング

### 特徴

* スケールアウト
* 自動でシャーディングされる
* 水平分散の仕組み(mongos, 設定サーバ:mongoc)が備わっている
* ミドルウェア側で水平分散の実装が不要
* コレクションレベルでシャード間の分散をする
    * コレクションの特定範囲を一つのシャードに置く

### mongos

* ルータ群のこと
* 基本的にアプリケーションサーバと同じマシンに置かれ、シャードへの接続を管理する
* 永続化を行わない
    * 設定サーバ(mongoc)が必要になる

### 設定サーバ(mongoc)

* シャードクラスタのメタデータを永続化する
* mongocが持つ設定
    * グローバルなクラスタの設定
    * 各データベースやコレクションの場所
    * 特定の範囲のデータの場所
    * シャード間のデータ移行履歴の変更ログ
* ちょうど３台の設定サーバを必要とし、また別マシン上に置かなければならない


## メモ

* https://github.com/ansible/ansible-examples/tree/master/mongodb
* 高度なkey-valueストア
    * Dynamo, Cassandra, Project Voldemort, Riak
    * master-slave構成を持たない
    * スケールが容易
    * どのノードに障害が起きても書き込みを行うことができる
* MongoDB
    * RDBMSに近い思想
    * master-slave構成を持つ
    * 大規模でスケーラブルなWebアプリケーションというよりも汎用的なソリューションとして用いられる
    * トランザクション管理はできない
* データベースとコレクションは初めて挿入されたときに生成される
* strictモードでDBやコレクション名等のケアレスミスが防げる


## 参考文献
* MongoDB イン・アクション