satococoa's blog

主にサーバーサイド、Web 系エンジニアのブログです。Go, Ruby, React, GCP, ...etc.

第3回 MongoDB JP 勉強会 in Tokyo に参加しました

第2回(第2回 MongoDB JP 勉強会 in Tokyoに参加してきた)に引き続き、第3回 MongoDB JP 勉強会 in Tokyoに参加してきました。

心に残ったところをまたメモ程度にまとめます。

MongoDB 全機能チュートリアル & v1.8-1.9 新機能チュートリアル

@doryokujinさん
資料:http://www.slideshare.net/doryokujin/mongodb1

MongoDBの機能から、大まかに以下の項目を除いた全機能を網羅する大作でした。

あまりに大作なのでメモが追いつきませんでした。
特に注意しておこうと思った項目だけ並べますが、詳しくは資料を要参照。

また、資料中にはそれぞれドキュメントの該当箇所へとリンクを入れてくださっています。

mongodについて
  • ひとつのmongodに対してはシングルスレッドしか使えず、また多くのオペレーションに対してロックがかかる。
  • このロックはDB全体に対してかかるもので、Collection単位のロックは未実装。
    (おそらく、JavaScriptへの依存が問題であり、v2.0〜2.2で改善予定とのこと。)

ただしトランザクションがあるわけではないので、例えば1万ドキュメントをinsertする処理を走らせている最中に全く他のこと(書き込み系の処理)ができないわけではなく、insertの間に処理が割り込むために「ロック」をRDBMSほど気にしなくても大丈夫。

mongosについて
  • Sharding環境におけるルーティングサーバー
  • 自身はデータをストレージしない
databaseについて
  • 64bitを推奨
  • 32bitモードでは全てのdatabaseで2GBに容量が制限されてしまうため非現実的
BSONについて
  • 3種類の整数表現を持っている(int32, int64, double)故の曖昧さ
  • オブジェクト要素の出現順序に依存
    {'a': 1, 'b': 2} != {'b': 2, 'a': 1}(この特徴が後述のDot Notationの推奨にもつながる)
  • ただし上記の曖昧さ、順序問題は各種言語ドライバが吸収してくれている
Capped Collection
  • サイズが固定で、古いドキュメントから順に削除される
  • 書き込みのパフォーマンスが高い(ファイル書き込みレベル)
  • Capped Collection内のデータを削除することはできず、削除するならCollection自体を削除
  • デフォルトでは_idにもindexが作成されない
  • Readも挿入順 .sort({$natural: 1})で行うのが高速
  • Sharding不可能

上記のように尖った機能です。要はログファイルに最適とのこと。
MongoDBってアプリケーションのDBというよりはログ解析に本当に向いているなぁ、という印象がこのへんでさらに強くなります。

Buk Insert
  • 複数のドキュメントを配列としてinsertの引数として渡すと、高速にinsertしてくれる
  • ただし、最適な配列内のデータ数はデータに依存し、多く渡し過ぎるとエラーも
insertの注意
  • デフォルトは "Fire & Forget" なので、insertが成功している保証はない
  • しかし、ドライバ側が "safe write" などの名称で実装済
  • 複数のコマンドに対するtransactionは無いので注意が必要
getLastError()

オペレーションの最後に起こったエラーを返すコマンド。(重要)

> db.getLastErrorObj() // 全てのエラーのオブジェクトを返す
> db.getLastErrorObj().ok // true or falseを返す
fsync
  • メモリ上の全てのデータをディスクにフラッシュ
  • デフォルトでは60秒毎にfsyncコマンドが実行されている
  • コマンドラインオプション --syncdelay で変更可能だが、変更は推奨されていない
  • ロックをかけるために使うこともある
Journalファイル
  • 自動的にローテーションされる
  • クリーンシャットダウン時には削除されるが、ダウン時には残る
  • journalファイルがある状態でサーバーが起動された場合、未実装の処理が自動的に行われる
  • journalファイルへの書き込みは100ms毎
Query
  • db.users.find().snapshot();
    同じデータを重複して取ってきてしまうことがあるため、それがまずい場合はshapshot()を使う。
    (updateがかかってドキュメントの容量が大きくなり、移動が起こったケース)
  • db.users.distinct('column')はインデックスがないと重いかも。
Dot NotationとSub Object
  • [Dot Notation] > db.blog.findOne({'author.name': 'Jane'})
  • [Sub Object] > db.blog.findOne({'author': {'name': 'Jane', 'id': 1}})

Shellからの比較はBSONにおける比較なので、出現順序と要素数がともに一致しないといけない。
そのため、Dot Notationが推奨される。

JavaScript表現

以下の4つは同じクエリ

> db.myCollection.find( { a: {$gt: 3} });
> db.myCollection.find( { $where: "this.a > 3"} )
> db.myColleciton.find("this.a > 3");
> f = function() { return this.a > 3; } db.myCollection.find(f);
削除系
> use myDB
> db.dropDatabase();

> db.mycoll.remove({}); // 全削除
> db.mycoll.remove({n: 1});
> db.videos.remove({rating: {$lt: 3.0}, $atomic: true});
  // ドキュメントの削除の途中に他の処理が割り込んでしまうのを$atomicでブロック
> db.mycoll.drop(); // 非常に高速な全削除
update

シングルdocumentに対してatomicな操作をするmodifierが用意されている
$set, $unset, $inc, $push, $pushAll, $pull, $pullAllなど

オブジェクトサイズが増えない場合はin-place updateという高速な処理が行われる

1回のupdateで複数のdocumentを同時に更新するときに他の書き込みをブロックするための$atomicが用意されている

> db.students.update({score: {$gt: 60}, $atomic: true}, {$set: {pass: true}});
index

バックグラウンドでのindexの生成が可能で、基本的にはバックグラウンドでやると良い

> db.things.ensureIndex({x: 1}, {background: true});

埋め込みdocumentに対してはDot NotationでのensureIndexが良い

> db.factories.ensureIndex({'metro.city': 1});

複合インデックス

> db.mycoll.ensureIndex({'a': 1, 'b': 1, 'c': 1});

Sparseインデックス
sparseインデックスで登録されたキーを持っていないドキュメントは対象外
現在はsparse indexは1つのフィールドに対してのみ

Unique index
_idはunique index

> db.things.ensureIndex({firstname: 1, lastname: 1}, {unique: true});

indexが効いているかどうかはexplain()を使う。

> db.comments.find({tags: 'mongodb'}).sort({created_at: -1}).explain();

Sharding環境ではunique indexの一意性が保証されない。

Replication

MongoDBのレプリケーションは2種類ある。

  1. Master-Slave
    マスタースレーブ
  2. Replica Sets
    自動フェイルオーバー・自動リカバリ機能あり
    primary - secondary間は非同期通信

local データベースにレプリケーション関連の情報が入っている。また、oplogというCapped Collectionにdbに対して実行されたオペレーションが記録される。

Replica Setsを構成するには最低でも3台のサーバーが必要。これは障害時に次のprimaryを選出する『投票』という機能があるが、2台では投票によって全体の過半数の票を獲得できないため。 ノードタイプには以下の3種類があるため、最低3台の条件をクリアするためにArbiterタイプを使うのも有効。

  • Standard
    普通のノード
  • Passive
    primaryにならない
  • Arbiter 次のprimaryを決める際にだけ使われるノード

Shardingとの連携で大規模データの運用もできるが、必要なサーバー数も跳ね上がる。
それぞれのshardに対してReplica Setを構成する。さらにmongosやconfigサーバーも必要。

フェイルオーバーには最低30秒〜数分かかる。

バックアップ

ロックする方法

  1. fsyncコマンドでdbをロック
  2. バックアップを行う(ディレクトリコピー、cloneコマンドなど)
  3. アンロック

ロックしない方法

  • mongoexport(json, csvでエクスポート) - mongoimport
  • mongodump(bsonでエクスポート) - mongorestore
  • wordnik-oss

「ソーシャルアプリのプロトタイプ制作にMongoDBを活用」

@bibrostさん
資料:http://www.slideshare.net/fungoing/mongodb-7948933

ソーシャルアプリのプロトタイプにMongoDBを使っているというお話でした。

毎週毎週動作するアプリケーションをリリースするアジャイルな開発を行うにあたって、プロトタイプにMongoDBを使う方法で進めているそうです。

大きなポイントは2つかと思います。

  1. PHPからはSleepy.Mongooseを使って構築されたRESTのインターフェイスに対し、json_encode/decode + php_curlでアクセスしている。
  2. 最終的(ある程度固まってきた時点)にはMySQLに全面移行する。

1に関しては、アプリケーションサーバーに余計なモジュールを入れたくないことと、ソーシャルアプリという特性からjson_encode/decode + php_curlは入っているために手間がかからないことが理由とのことです。

2に関しては質問、懇親会でも盛り上がりました。以下のような内容で参加者間でもやりとりがされていました。

  • どの時点をもってMySQLへ移行するか。
  • 何をもって完成形のスキーマとみなすか。
  • MySQLへの移行は具体的にどのように行うか。(現在のDBアクセスライブラリで抽象化して吸収?ある程度書きなおし?)
  • そもそも本当に全部MySQLに移行できるのか。

MongoDBであることを意識させずに、RESTのインターフェイスを構築しそれに対してcurlでアクセス、という作りは疎結合で魅力があるな、と感じました。また、MySQLへの移行に関しては最終的にどうなったのか、今後続報が知りたいところです。
(MongoDBをメンテナンスできるエンジニアが少ない、とのことでオトナの事情もあり頑張ってMySQLに置き換える、に一票。)

また、資料のP.47のUse caseの表が適切である、と当日も評判でした。

MongoDBチューニング

@matsuou1さん
資料: http://www.slideshare.net/matsuou1/20110514-mongo-db

以下の4つを目標として話してくださいました。

  1. オプティマイザの概要を理解する。
  2. 実行計画を取得し、読めるようになる。
  3. 遅いクエリを特定できる。
  4. 簡単なクエリのチューニングが出来る。

MongoDBではRDBMSオプティマイザとは全く違う方法で実行計画を選択するそうです。

資料にはslowクエリの記録の方法やexplain()の結果の読み方も掲載されています。

NoSQLとは言え、チューニングの方法は意外とRDBMSと似たところがあるんだな、と個人的には感じました。

MongoDBを使用したモバイルゲーム開発について

@ygenkiさん
資料: http://www.slideshare.net/ygenki/mongodb-7962043

MongoDB + Javaで開発した東京ガールズスナップというアプリの開発事例をご紹介してくださいました。

まずMongoDB採用の理由として以下の3点を挙げられていました。 1. ドキュメントの階層構造 2. Replica Set & Sharding 3. MySQL + memcachedからの移行

今回のアプリの構成上、ユーザーの作成した写真をユーザデータの配列に$push, $pullする設計にしたそうです。

また、アプリの特性上(小さめの)バイナリデータを頻繁に出し入れすることになりますが、画像データのキャッシュストアとしてもMongoDBを使っているそうです。

当初はShardingを行うことも視野に入れていましたが、Sharding無しでも十分にパフォーマンスが出たこと、データ量が少ないこと、Shardingを行うと必要なサーバ台数が増加することから、現状では行っていないそうです。

スキーマレスゆえに仕様変更や機能追加が行いやすいことからモバイルゲームとの相性が良く、例えShardingを行わないとしても十分にメリットは多いとのことでした。

感想

懇親会などで色々な方に話を聞くと、アプリケーションのDBとしてMongoDBを採用したという例は少なく社内の管理アプリやログ解析に使われている、という方が多いようでした。(それ以上に、使ってみたいけど会社ではそこまで勇気を持って踏み出せない、という方が多いみたい。)

今回の勉強会の内容からも、どうもMongoDBはそういった裏側のシステムに特性があるのかなぁ、という感想です。

例えばRailsでアプリを開発することを考えたとき、スキーマの変更や開発者間でのスキーマの同期などはmigrationの仕組みでカバーされています。(もちろん本番環境でのスキーマ変更に当たっては停止時間など考慮に入れる必要がありますが)

すると、MongoDBを使うことによるメリット・デメリットはどのへんにあるのかなぁ、とちょっと疑問に思ってしまいました。
データの階層構造もActiveRecordで表現できますし・・・。

要は使いどころなんだと思います。なんでもかんでもMongoDBとかMySQLという風に思考停止するのではなく、要件に合わせて適切なミドルウェアを選択できるよう、しっかり勉強したいと思いました。