LoginSignup
17
15

More than 5 years have passed since last update.

Momy: MongoのビューにMySQLを使ったっていいじゃないか

Last updated at Posted at 2015-10-21

最近、改めて(というか3周くらいして)、DBは混ぜて使ったほうが良いんじゃないかという話もちらほら。そんな高尚な話はともかく、Mongo始めたSQLerの「精神衛生のために」「いざとなったらSQLで」くらいのノリで作ってみた件です。

Artboard 1.png

コードだけ見たい方は、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」です。フランスにそんな地名があるらしいです。

momy.png

レプリカセット用のログを流用する

コードの要点は、次の部分でしょうか。(若干デフォルメ)

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)を置くというのは、リーズナブルかなと思う次第でした。

17
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15