最近、改めて(というか3周くらいして)、DBは混ぜて使ったほうが良いんじゃないかという話もちらほら。そんな高尚な話はともかく、Mongo始めたSQLerの「精神衛生のために」「いざとなったらSQLで」くらいのノリで作ってみた件です。
コードだけ見たい方は、Momyリポジトリをどうぞ。
はじめに
動機はいくつかあって、MongoDBに対して、
- がしがし集計したい
- さくっとテーブル間のJOINをしたい
- Microsoft Accessや、Libre Office Baseから接続したい
など。よくある話の割に、MEANやらSPAやらの文脈で考えたときに、結構面倒なんですよね。最後のやつも意外と 鬼門です。
- 注1: ここでは、一応、MongoDB→MySQL同期の方向に話を持って行きますが、もっといい方法があったら是非教えてください。
- 注2: ちなみにMEANの「A」はanythingの略ですよ?
ソリューションを探して
ODBC?
一応オープンなものが存在していますが、「実験的」なまま3年経過しているので、あくまで参考まで。あー、もう、MySQLなら簡単につなげるのに...! やっぱりNoSQLに直接繋ぎに行くって無理があるのか。
- sp13_10g - mongoDb ODBC Driver EXPERIMENTAL
あとは、商用であればこことか、こことかからも、ドライバが出ているようですが、「お高いんでしょ?」で深追いせず。
MoSQL?
Stripeが「MoSQL」というオープンソースツールを出しています。そのままズバリMongoDBからSQLの世界に同期をかけるツールで、Rubyで書かれています。GitHubのスターが1Kくらい。ただし、 PostgreSQLにのみ対応 です。MySQLに対応させる動きも一瞬あったんですが、なかったことになってるっぽい。残念。
とはいえ、PostgreSQLに転向可能であれば選択肢のひとつです。MEANの文脈だと扱いづらいのは確かですが...
Nodeの世界にないんですか?
ここまで候補を見てきたものの、今一歩。なんとか、Nodeの世界で解決できないものか? しらみ潰しに探してみると...、それっぽいものが。こ、これが正解か!?
npmにも公開されているのですが、どうも作りかけで放置した感がパない。実際、使ってみると...動かなくて途方に暮れます。やはり、これも「3年前」で止まっていて、皆もうMongoに飽きたんだな...とか分かって歴史を感じます。
仕方がない、オレオレで
途方に暮れつつも、コードを読むと、MoSQLにしろMongoDB-to-MySQLにしろ、やっていることはシンプル。
- MongoDB間のReplication機能をうまく流用する
- Tailable Cursor(追尾カーソル?)でMongoDBの変更を監視
- 変更箇所のログを取ってきて、SQLなデータベースに反映
以上。ほとんど、MongoDB自体の機能でなんとかなるかも。これだけなら作れそうだよ?
Momy
作りました。
当初、MongoDB-to-MySQLを改造する方向で始めたんですが、結果どこにも元のコードが残ってないところまで来たので、別名にしました。 Mongo to MySQLで「Momy」です。フランスにそんな地名があるらしいです。
レプリカセット用のログを流用する
コードの要点は、次の部分でしょうか。(若干デフォルメ)
tail () {
const
filters = {
// 前回のタイムスタンプより新しいログを監視
ts: { $gt: Timestamp.fromNumber(ts) }
},
curOpts = {
// 追尾モードを設定
tailable: true,
awaitdata: true
}
mongodb.connect(url)
.then(db => {
db.collection('oplog.rs')
.find(filters, curOpts)
// 検索結果はストリームとして取得
.stream()
.on('data', log => { /* MySQLに保存 */ })
})
}
単体で使ってる時は存在しないのですが、MongoDBにリプリケーション関連の設定をすると、local
という名のデータベースが追加されます。(GUIアプリだと不可視になっていることもあります)
最近のMongoDBだと基本的にはレプリカセット(replica set)を使って、複数台でのレプリケーション/フェールオーバーを実現します。この、本来はMongoDB同士の同期に利用されるログ情報は、先ほどのlocal
内のoplog.rs
コレクションに保存されています。これを監視しつつ、ストリームとして取得すると、さっきのようなコードになるわけです。便利。MongoDBのAPI便利すぎ。
データベースの下ごしらえ
まずは、空のmongodを立ち上げましょう。その際に、--replSet
と--oplogSize
のオプションを加えます。前者はリプリカセットの「グループ名」みたいなもの、後者はログのサイズを設定するものです(MB単位)。
-
注: すでに手元で運用中のmongodがある場合は、別ポート
--port
かつ別ディレクトリ--dbpath
にしたほうが良いかもしれません。
$ mongod --replSet "rs0" --oplogSize 100
別ウィンドウのターミナルで、mongoシェルを開きます。
$ mongo
....
> rs.initiate()
このように、rs.initiate()
コマンドを実行すると、必要なデータベースとコレクションを生成してくれます。
後は、普段通りに、
- MongoDB: アプリケーション用のデータベースを作成
- MySQL: 受入れ用のデータベースを作成
すれば準備完了。データベース名は任意ですが、以降の説明ではどちらも「myapp」とします。
スキーマ的な何か
MongoDB上の全フィールドをコピーしても構いませんが、「必要なところだけ」にするのも手です。「統計に使う数値は取り込み、備考欄とかはスキップする」などが考えられますね。
スキーマレスなNoSQLとはいえ、実際にはアプリケーション側である程度の構造を持たせているはず。主要項目、解析に必要な項目に限定した方が、MongoDB側のスキーマ変更に逐一追従せずに済み、現実的かも。考え方としては、
- ドキュメント (帳票データとか) - MongoDB
- メタデータ (共通項目、集計項目) - MySQL
とするとわかりやすいでしょうか? 前者を「ファイル」、後者を「ファイルシステム」に例えても良いですね。
Momyでは、次のようなjsonファイルを用意して、スキーマを定義します。ファイル名はmomyfile.json
を推奨しています。
{
"src": "mongodb://localhost:27017/myapp",
"dist": "mysql://root@localhost:3306/myapp",
"prefix": "t_",
"collections": {
"collection1": {
"_id": "number",
"field1": "number",
"field2": "string",
"field3": "boolean"
},
"collection2": {
"_id": "number",
"field1": "number",
"field2": "string",
"field3": "boolean"
}
}
}
-
src
: MongoDBサーバのURL -
dist
: MySQLサーバのURL -
prefix
: MySQLのデータベース名に共通の接頭辞をつけたい場合に設定 (省略可) -
collections
: 同期するコレクションごとのフィールド定義
フィールド定義には、シンプルに数値(number)、文字列(string)、真偽値(boolean)を指定するようにしています。
"<コレクション名>": {
"<フィールド名>": "<フィールド型>"
}
動かしてみる
インストールは、npmからどぞ。
$ npm install -g momy
MongoDB側には、すでに何かしらのデータが入っているものとします。初回実行時は、そのデータをまとめてインポートしてから、監視を始めます。--import
オプションを指定します。
$ momy --config momyfile.json --import
データの件数次第では時間がかかります。MySQL側にデータがコピーされているか確認しましょう。止めるときは、Ctrl + C
で。以降は、差分だけで良いはずなので、次のようにオプション無しでOKです。
$ momy --config momyfile.json
あるいは、forever
を使うのもあり。
$ forever momy --config momyfile.json
-
注:
--config
オプションを外すと、ワーキングディレクトリのmomyfile.json
を使います。
APIから
APIからも使えるよう作っています (たぶん)。
まとめ
BaaSが当たり前になっていく中、こうやってデータベースで遊べるのもあと数年の話でしょうか。はやくRelay的な世界に移行しますように。
とはいえ、現場レベルだとMicrosoft Accessはまだ幅を利かせているので、そこからBaaSにどうやって繋げるの? とかはまだ不透明だったりします。ログの解析ツールはオンラインで色々ありますが、社内での解析方法自体が秘伝のタレ化し、オンライン化不可能案件になっていることもしばしば。ビッグデータになったとたん、Accessだとどうしようもなくなるという話はそれはそれ。
MongoDB自体での集計機能も、ここ数年でだいぶ良くなってきました。それを使いこなすのも筋ですが、
- SQL慣れしているほど、覚えるのがめんどくさい
- 多機能になった集計機能(aggregation)をどのようにAPIに晒すか
とか考えてしまいます。後者は結構面倒な問題で、
- 定型の集計以外
- 設計時には想定外のテーブルと複雑にJOINさせつつ、傾向を探るとか
API越しにどうすれば良いのか、着地点がイマイチ見えてません(私が)。
こういった話に答えが出るまでは、メイン機能はMongoDBで柔軟に作って、集計とか裏方用に、読出し専用のMySQL(ほかのRDS)を置くというのは、リーズナブルかなと思う次第でした。