MongoDB スタンドアロンでもReplica Setは有用

# 概要
基本的にReplica Setは3台以上の構成で使います。しかし、スタンドアロン（1台）でReplica Setとして使うメリットがあります。

Replica Setのメリット

- トランザクション処理ができる
- Oplogが使える
    - oplogはoperations logの略で更新ログなどが格納されるCapped Collection
    - localデータベースにoplog.rsのコレクション名で作成される
- Change Streamsが使える

#環境

- Windows10
- MongoDB 4.2.0
    - 4.0　以上であれば可能です


# Replica Setの設定

サーバーでの設定は`--replSet`で行います。メモリサイズ以外で注意が必要なのは`oplog`のサイズです。

Oplogサイズ

| OS | default | 下限値 | 上限値 |
|:---|--------:|----:|-----:|
|Windows| 空き容量の5% | 990MB| 50GB |
| Unix | 空き容量の5% | 990MB| 50GB |
| Mac | 192MB |  - | - |

以降はWindowsを前提に進めます。下限が990MBですが、初期設定で小さなoplogを作ることが出来ます。oplogは再設定でサイズを変更できますが、その時の値は下限値～上限値の内です。ただし、一度990MB以上にすると990MB未満には再設定できません。

次の例はoplogを100MBで作成します。

```shell
mongod --dbpath db --replSet repl --oplogSize 100
```

Replica Setの初期化

```javascript
mongo
> rs.initiate()
{
        "info2" : "no configuration specified. Using a default configuration for the set",
        "me" : "localhost:27017",
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1568940523, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1568940523, 1)
}
repl:OTHER>
repl:PRIMARY>
```

初期化が成功してしばらくすると、プロンプトに`PRIMARY`が出てくるので、それまで**enter**を押します。 
初期化が済むと**oplog**が作られますので、次に**mongod**を起動するときは`--oplogSize`が無くてもOKです。

次に**oplog**のサイズの確認とサイズ変更コマンドを紹介します。

```javascript
repl:PRIMARY> use local
repl:PRIMARY> db.oplog.rs.stats().maxSize
104857600
```
```javascript
repl:PRIMARY> db.adminCommand({replSetResizeOplog:1, size: 990}) //990MBに変更
```

# トランザクション処理

トランザクション対象のコレクションはトランザクション処理開始前に存在しなくてはなりませんので、コレクションを作成するところからの例を紹介します。

```javascript
repl:PRIMARY> db.createCollection("col")
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1568958114, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1568958114, 1)
}
repl:PRIMARY> sess = db.getSession()
session { "id" : UUID("1869f6ed-2b47-42fb-9343-7f88f3099a27") }
repl:PRIMARY> sess.startTransaction()
repl:PRIMARY> db.col.insert({a:1})
WriteResult({ "nInserted" : 1 })
repl:PRIMARY> db.col.find()
{ "_id" : ObjectId("5d8466e82ca8580d7e037a0a"), "a" : 1 }
repl:PRIMARY> sess.abortTransaction()
repl:PRIMARY> db.col.find()
repl:PRIMARY>
```

上記例では、`sess.abortTransaction()`を実行していますので、insertが取り消されています。更新を確定するためには`sess.commitTransaction()`を実行します。

Go言語のトランザクション処理については「[MongoDBの Offical Go言語 Driverを使ってみる（１）準備・Insert](https://qiita.com/h6591/items/f3a7c1bca31cfa634cca#%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%87%A6%E7%90%86insert%E3%81%AE%E3%81%BF%E3%81%AE%E4%BE%8B)」の最後に載せています。

# Change Streams

Change Streams更新されたデータをリアルタイムで取得する仕組みである。

Change Streamsで取得できるレベルは次の3つあります

- コレクション
    - 指定したコレクションの変更を取得
    - db.$<$collection$>$.watch()
- データベース
    - 指定したデータベースの変更を取得
    - db.watch()
- deployment
    - Replica SetあるいはShardingにおける変更を取得
    - Mongo.watch()


## データベースのwatch()例

**watch()**側のmongoシェルでの処理
次の例は永久ループを回避するために**delete**の更新データを受け取ったら終了します。

```javascript:mongoシェル
var dbCur=db.watch()
while (!dbCur.isExhausted()){
   if (dbCur.hasNext()){
      var doc = dbCur.next()
      printjson(doc)
      if (doc.operationType == "delete") {
         dbCur.close()
         break
      }
   } 
}
```

別のmongoシェルで更新系のコマンドを実行します。

```javascript:mongoシェル
db.col.insert({a:1,b:1})
db.col.insert({a:1,b:2})
db.col.updateMany({a:1},{$inc:{b:1}})
db.sample.insert({a:1})
db.sample.remove({})
```
 
**watch()**側で表示されるデータ

```json
{
        "_id" : {
                "_data" : "825D8475D0000000012B022C0100296E5A100472FD948260B44F318323BC49331D863446645F696400645D8475D02CA8580D7E037A190004"
        },
        "operationType" : "insert",
        "clusterTime" : Timestamp(1568962000, 1),
        "fullDocument" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a19"),
                "a" : 1,
                "b" : 1
        },
        "ns" : {
                "db" : "test",
                "coll" : "col"
        },
        "documentKey" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a19")
        }
}
{
        "_id" : {
                "_data" : "825D8475D0000000022B022C0100296E5A100472FD948260B44F318323BC49331D863446645F696400645D8475D02CA8580D7E037A1A0004"
        },
        "operationType" : "insert",
        "clusterTime" : Timestamp(1568962000, 2),
        "fullDocument" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a1a"),
                "a" : 1,
                "b" : 2
        },
        "ns" : {
                "db" : "test",
                "coll" : "col"
        },
        "documentKey" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a1a")
        }
}
{
        "_id" : {
                "_data" : "825D8475D0000000032B022C0100296E5A100472FD948260B44F318323BC49331D863446645F696400645D8475D02CA8580D7E037A190004"
        },
        "operationType" : "update",
        "clusterTime" : Timestamp(1568962000, 3),
        "ns" : {
                "db" : "test",
                "coll" : "col"
        },
        "documentKey" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a19")
        },
        "updateDescription" : {
                "updatedFields" : {
                        "b" : 2
                },
                "removedFields" : [ ]
        }
}
{
        "_id" : {
                "_data" : "825D8475D0000000042B022C0100296E5A100472FD948260B44F318323BC49331D863446645F696400645D8475D02CA8580D7E037A1A0004"
        },
        "operationType" : "update",
        "clusterTime" : Timestamp(1568962000, 4),
        "ns" : {
                "db" : "test",
                "coll" : "col"
        },
        "documentKey" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a1a")
        },
        "updateDescription" : {
                "updatedFields" : {
                        "b" : 3
                },
                "removedFields" : [ ]
        }
}
{
        "_id" : {
                "_data" : "825D8475D0000000052B022C0100296E5A1004F505B42981DE4121B97FDE4554C4475E46645F696400645D8475D02CA8580D7E037A1B0004"
        },
        "operationType" : "insert",
        "clusterTime" : Timestamp(1568962000, 5),
        "fullDocument" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a1b"),
                "a" : 1
        },
        "ns" : {
                "db" : "test",
                "coll" : "sample"
        },
        "documentKey" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a1b")
        }
}
{
        "_id" : {
                "_data" : "825D8475D1000000012B022C0100296E5A1004F505B42981DE4121B97FDE4554C4475E46645F696400645D8475D02CA8580D7E037A1B0004"
        },
        "operationType" : "delete",
        "clusterTime" : Timestamp(1568962001, 1),
        "ns" : {
                "db" : "test",
                "coll" : "sample"
        },
        "documentKey" : {
                "_id" : ObjectId("5d8475d02ca8580d7e037a1b")
        }
}
```

データベースに対してwatchしているのでデータベース下のすべてのコレクション(col,sample)に対する変更が取得されています。

# まとめ


障害対応を考えるとはやり3台以上の構成が必要ですが、トランザクション処理やChange Streamが使えるので、1台でもReplica Setにする価値はあると思います。