今回は、MongoDB v2.2リリースノート をもとにv2.2で追加/改善された機能を紹介します。機能についてより詳しく知りたい方は第3回丸の内勉強会の資料 を参照ください。コマンドレベルでの手順や、一部機能の検証が載っています。
新機能ダイジェスト
1.並列処理の強化(Concurrency Improvements)
ロックの粒度がGlobalロックからDBロックになりました。
PageFaultアーキテクチャが改善されロック時間が減りました。
2.Aggregation Framework
集計処理がコマンドで可能になりました。
3.Replica SetsのReadノード選択
一貫性レベルに応じて、どのノードからデータをReadするかを選択可能になりました。
4.Tagを使用したSharding(Improved Data Center Awareness)
データ保存先のShardをTagで指定可能になりました。
5.TTL(Time To Live)Collections
一定時間で削除されるCollectionを定義可能になりました。
6.その他の変更点
今回は省略します。第3回丸の内勉強会の資料 を参照ください。
並列処理の強化(Concurrency Improvements)
並列処理の強化のために2つの大きな改善が入りました。ロックレベルに関するものとPage Faultに関するものです。どちらも利用者が意識することなく利用できる機能です。
ロックレベル
ロックの粒度がv2.0までのGlobalロック(Mongodインスタンス全体のロック)からDBレベルロックとなり、並列処理がより効率よく行えるようになりました。複数のDBで運用しているシステムには恩恵がありますが、それほど対象ユーザは多くはないかもしれません。
次のステップであるCollectionレベルロックでは多くのユーザに恩恵があると思われますが、実装は次バージョン以降となります。JIRAにチケット登録 されており、2012年11月末現在でFix Version/s: Planning Bucket Aとなっており実装バージョンは未定となってます。
図1 ロックレベル
Page Faultアーキテクチャ
MongoDBはメモリキャッシュ機能を持っています。高速なReadを実現するためにpageと言う単位でデータをメモリに保持しています。メモリ内に無いデータはディスクからロードすることになりますが、このイベントをPage Faultと呼びます。Page FaultはメモリからReadする場合と比較して時間がかかってしまいます。
v2.2ではロック中にPage Faultが発生してロックが長引くことを避けるための仕組みが追加されました。更新系の処理が多いシステムではこの改善によってスループットが向上する可能性があります。
図2 Page Faultアーキテクチャ
Aggregation Framework
Aggregation Frameworkは、データに対して集計処理を行うコマンド集です。v2.0系まではユーザがMap/Reduceでの実装が必要だった処理のうち、よく使うものをコマンドとして使えるようになりました。SQLでいうGROUP BY句に似た機能を提供します。コマンドは$から始まるオペレータと呼ばれ、パイプラインとして処理を組み合わせることが可能です。
たとえば、以下のSQL句をAggregation Frameworkで実現すると、図3 のようになります。
SQL
SELECT name as '_id', AVG(score) as 'average' FROM scores
WHERE year = 'junior'
GROUP BY = name
Aggregation Framework
db.scores.aggregate(
{ $match : { "year" : "junior" } },
{ $project : { "name" : 1, "score" : 1 } },
{ $group : { "_id" : "$name",
"average" : { "$avg" : "$score" } } }
);
図3 Aggregation Frameworkのパイプライン処理
パイプラインに組み合わせることができるオペレータは以下のものがあります。
オペレータ名
処理
$match
条件で絞り込みを実施(SQLのWHERE句)
$project
集計処理を行うフィールドの選択/除外、リネーム(SQLのAS句) 、計算結果のInsertを実施
$unwind
指定された配列の展開を実施
$group
$sum, $avgなどを使い集計処理を実施
$sort
指定されたsortキーによるソートを実施
$skip
指定された数字分スキップして次の処理へ渡す
$limit
指定された数字分の結果を次の処理へ渡す
Readノードの選択
システムで保証する一貫性のレベルに応じて、Replica Setsのノード群の中でどのノードからReadするかを選択可能となりました。一貫性レベルが強い順に以下の5段階が設定できます。システムにデータの強い一貫性が求められる場合はPRIMARYに、結果整合性[1] でも十分な場合にはそれ以外に設定してReadの性能と可用性を向上させることが可能となりました。
設定名
意味
PRIMARY
PRIMARYノードのみからReadする
PRIMARY PREFERRED
可能であればPRIMARYノードからReadする
SECONDARY
SECONDARYノードのみからReadする
SECONDARY PREFERRED
可能であればSECONDARYノードからReadする
NEAREST
レイテンシの小さいノードからReadする※
[1] 書き込みが行われたPRIMARYノードからReadを保証せず、状況や設定によってSECONDARYノードからREADする。最新の状態が反映されているかどうかは保証されないが、可用性と性能は向上する(例:DNS) 。
※ NEARESTに設定すると、以下のアルゴリズムでReadするノードを決定します。
「ドライバからReplica Setsにpingをし、15ms以内で返ってきたノード群から1台選択」
設定はアプリケーションのコード中で行います。
Rubyでの設定例
@collection.find({:doc => 'foo'}, :read => :primary) #primaryノードからReadする
@collection.find({:doc => 'foo'}, :read => :secondary) #secondaryノード群からReadする
Tagを利用したSharding
Shardingに参加しているノードにTagを追加しTagRangeを設定することで、指定したレンジのデータを任意のノードに格納することが可能になりました。この機能により、uid=1~100のデータは東京データセンターのノード、uid=101~200のデータはNew Yorkデータセンターのノード、というDR(ディザスタリカバリ)を考慮したデータ配置が可能となります。2.2の目玉機能のひとつで、前述のReadノードの設定と合わせて"Data Center Awareness"[2] と表現されています。
設定例
> sh.addShardTag("shard0000", "TokyoDC");
> sh.addTagRange("appdb.users", { "uid" : 1 }, { "uid" : 100 }, "TokyoDC");
図4 大陸間をまたがったHA構成の例
TTL(Time To Live) Collections
一定時間が経過した後、データが削除されるTTL Collectionsが定義可能になりました。
起点とするフィールドに{"expireAfterSeconds":数値}というハッシュを第二引数に入れてensureIndex()でインデックスを作成することで、起点に入力されたdate-type型から計測して、先ほどexpireAfterSecondsのvalueとして入れた数値秒後にドキュメントデータが削除されます。
制限として、データサイズ上限が決められるCapped Collectionsでは使用できません。
TTL Collectionsの例
// eventsコレクションのデータを、created_atフィールドを起点に30秒後に
// 削除されるように設定
> db.events.ensureIndex( { “created_at”: 1 }, { expireAfterSeconds: 30 } )
// statusにはdate-type型を入れる。new Date()でOK
// statusがdata-type型以外、またはcreated_atが無いデータは消えない
> db.events.insert( { "myid" : 1, "created_at" : new Date() } );
> db.events.insert( { "myid" : 2, "created_at" : "String" } );
> db.events.insert( { "myid" : 3, "time_field" : new Date() } );
> db.events.count();
// => 3
//30秒後
> db.events.count();
// => 2
> db.events.find({},{"_id":0})
{ "myid" : 2, "created_at" : "String" }
{ "myid" : 3, "time_field" : ISODate("2012-11-28T16:04:19.781Z") }
// "myid" : 1 のデータが消えている
次回のテーマ
今回はv2.2で追加/改善された主な機能を紹介しました。次回はMongoDBのクエリに関して説明します。
MongoDBと他のNoSQLを比較した場合の特徴のひとつとして、JOINを除くほぼすべてのSQLを再現できることが挙げられます。スムーズな理解につながるよう、SQLと比較しながらMongoDBのクエリを紹介していきます。