MongoDBからMySQLにレプリケーション


表題のことをするためのライブラリ「[Momy](https://github.com/cognitom/momy)」をアップデートしたので、忘れないうちに記事にしておこうと思います。(アップデートの内容は、テスト追加が主です)

MongoDBはNoSQLなドキュメントデータベースなので、基本スキーマを持ちません。ただ、[CouchDB](http://couchdb.apache.org/)などとは異なり「コレクション」の概念があります。なので、若干強引ですが、コレクションをSQLのテーブルにマッピングすれば、MongoDBからMySQLへの一方向リプリケーションは比較的簡単に実現できます。

![ff85da46-c732-5794-b7b0-1f9d3fb9d231.png](https://qiita-image-store.s3.amazonaws.com/0/1858/eac9198e-9998-c90c-8a11-5a0d6c12bbc8.png "ff85da46-c732-5794-b7b0-1f9d3fb9d231.png")

MongoDBはそのままだと、Microsoft Accessほかのオフィス系のツールから繋ぎにくいですが、MySQLに移してしまえば、各種ツール、ドライバがあります。MongoDBからRDBにリプリケーションするツールとしては、現在2つのツールがあります。(他にもあるかも)

ライブラリ | レプリケーション | 実装 | 説明
:-- | :-- | :-- | :--
[MoSQL](https://github.com/stripe/mosql) | MongoDB → PostgreSQL | Ruby | [Stripe](https://stripe.com)が開発。残念ながら2年間メンテナンス停止状態。
[Momy](https://github.com/cognitom/momy) | MongoDB → MySQL | Node | 去年、私がカッとなって作りました。

PostgreSQLを使う場合はMoSQLをどうぞ。あと、[Apache Drill](https://drill.apache.org/)なども用途によっては選択肢に上がるかもしれません。

## Momyのインストール

[npm](https://www.npmjs.com/package/momy)から入手できます。

```bash
$ npm install -g momy
```

## スキーマ設定

スキーマレスとはいえ、実際にはアプリケーションが構造をある程度規定しているはずです。その「スキーマ」を`momyfile.json`に設定します。基本的には、MongoDB(同期元)とMySQL(同期先)のデータベースを指定し、コレクションごとにフィールド名とその型を書いていけばOKです。

```json
{
  "src": "mongodb://localhost:27017/myapp",
  "dist": "mysql://root@localhost:3306/myapp",
  "collections": {
    "contacts": {
      "_id": "string",
      "createdAt": "DATETIME",
      "lastName": "string",
      "firstName": "string",
      "code": "number",
      "birthday": "DATE",
      "address.state": "string",
      "address.city": "string",
      "address.street": "string"
    },
    "meetings": {
      "_id": "string",
      "createdAt": "DATETIME",
      "date": "DATE",
      "title": "string",
      "description": "TEXT"
    }
  }
}
```

### データベースの指定

同期元のデータベースを`src`に、同期先のデータベースを`dist`に設定します。

- `"src": "mongodb://localhost:27017/myapp"` *必ずMongoDBを指定*
- `"dist": "mysql://root@localhost:3306/myapp"` *必ずMySQLを指定*

データベースのURLは、次の書式をとります。

- `データベースの種類`://`ユーザ名`:`パスワード`@`サーバ名`:`ポート`/`データベース名`

### コレクション

だいたい、見たままですが、詳しくはAPPENDIXをどうぞ。

## MongoDBの下ごしらえ

Momyは、MongoDBのレプリカセットの情報を使います。そのため、standaloneモードのMongoDBだと、リプリケーションができません。次のように、レプリカセットをオンにして起動します。

```bash
$ mongod --replSet "rs0" --oplogSize 100
```

別のターミナルからmongoシェルを開き、レプリカセットを初期化(`rs.initiate()`)しておきます。このコマンドは初回のみ実行してください。これで、レプリカセットがこのデータベースでアクティブになりました。

```bash
$ mongo
....
> rs.initiate()
```

## 実行する

初回実行時は、`--import`が必要です。

```bash
$ momy --config momyfile.json --import
```

このオプションをつけると、`momyfile.json`のスキーマの通り、MySQLのテーブルが作成され、MongoDB上の既存ドキュメントが、MySQLにコピーされます。

コピーが完了すると、その後はMongoDBを監視して、リアルタイムにMySQLに同期するようになります。終了する場合は、`Ctl+X`を押します。

2回目以降は、

```bash
$ momy --config momyfile.json
```

だけでOKです。サーバなどで、確実に同期を実行し続けたい場合は、[forever](https://github.com/foreverjs/forever)がおすすめです。

```bash
$ forever momy --config momyfile.json
```

万が一、`momy`が落ちた場合も、立ち上げ直して実行を続けてくれます。

## 最後に

ライブラリ作成の経緯などは、[昨年の記事](http://qiita.com/cognitom/items/a10baff96bf8e79806ea)をどうぞ。詳しい使い方については[Momy](https://github.com/cognitom/momy)のリポジトリも参考にしてください。それでは。



## APPENDIX

### コレクションの設定

`momyfile.json`上では、次のような書式で、コレクション名とフィールド設定をします。

```json
{
  "コレクション名": {
    "_id": "string あるいは number",
    "フィールド名1": "フィールドの型",
    "フィールド名2": "フィールドの型",
    ...
  }
}
```

`_id`は必須。MySQLに持って行く際にプライマリーキーになります。

フィールド名には`.`を使って、ネストした値を取得することもできます。例:

- `{a:{b:{c: 'Hey!'}}}` というデータがあったら、`a.b.c`で'Hey!'を取得。
- `{address: {state: '東京', city: '世田谷', street: '代田'}}` というデータがあったら、`address.state`で'東京'を取得。

### フィールドの型

型として、次のものが使えます。

- `BIGINT`
- `DOUBLE`
- `TINYINT`
- `VARCHAR`
- `DATE`
- `DATETIME`
- `TIME`
- `TEXT`

**メモ**: もし、足りない型があったら[Issue](https://github.com/cognitom/momy/issues)か[Pull Request](https://github.com/cognitom/momy/pulls)ください。

エイリアスとして、次の表現も使えます。

- `string`: `VARCHAR`に同じ
- `number`: `DOUBLE`に同じ
- `boolean`: `TINYINT`に同じ

#### 日付型

`DATE`型と、`DATETIME`型には、`"2016-11-13"`といった文字列のほか、タイムスタンプ(ミリ秒)を渡すこともできます。もし、`DATE`が指定されたとすると、MongoDB上の値が次のいずれであっても、MySQLに渡される値は`"2016-11-13"`となります。

- `"2016-11-13 01:14:28"` (文字列)
- `"2016-11-13T01:14:28"` (文字列)
- `"20161113T011428"` (文字列)
- `1478967268294` (数値)

### 数値型

- `BIGINT`を指定した場合、小数点以下は`parseInt()`で丸められます。
- `DOUBLE`を指定した場合、`parseFloat()`で解釈されます。

### 文字列型

- `VARCHAR`は、実際には`VARCHAR(255)`です。255文字以上の場合は、トランケートされます。
- `TEXT`には、明確な文字数制限がありません。