mongodb全機能解説2

85
MongoDB 全機能解説② doryokujin 第4回 MongoDB 勉強会

Upload: takahiro-inoue

Post on 15-Jan-2015

6.203 views

Category:

Technology


0 download

DESCRIPTION

 

TRANSCRIPT

MongoDB 全機能解説②

doryokujin

第4回 MongoDB 勉強会

[名前] doryokujin ( 井上 敬浩 )

[年齢] 26歳

[専攻] 数学(統計・確率的アルゴリズム)

[会社] 芸者東京エンターテインメント

[職業] データマイニングエンジニア

[趣味] マラソン ( 42.195km: 2時間33分 )

[コミュニティ]

・MongoDB JP: もっとMongoDBを日本に!

・TokyoWebMining: 統計解析・データマイニングの各種方法論、WEB上のデータ活用に関する勉強会

・おしゃれStatistics: 名著「statistics」を読み進めながら統計を学ぶ勉強会

自己紹介

[動機]

・@okuyamaoo さんと意気投合

[目的・内容]

・NoSQLの実運用で生じた問題や解決策・Tipsを共有(主に分散処理周り)

・分散FSについて勉強したい

・計算モデルとかもっとちゃんと勉強したい

・座談会形式で参加者も気軽に参加してもらえるように

[予定]

・7月8日(金) の夜を予定、まずはNoSQL(キーバリュー・列・ドキュメント・グラフ)について2、3回行う予定

分散系勉強会(座談会)

[定期開催]

・引き続き、毎月開催を続けていきます

・続けられるのはフューチャーアーキテクトの皆さんのおかげ!

[分科会]

・ソースコードリーディング分科会が発足

・今後もスキーマデザイン勉強会やオラ本読書会など積極的に

[特典]

・勉強会参加者にはMongoDB マグカップを、発表者にはMongoDB Tシャツを配れるように尽力中…(できれば次回から!)

※随時発表者を募集しています、お気軽にお声かけください!

今後のMongoDB勉強会の取組み

[開発元] 10gen http://www.10gen.com/[実装] C++[OS] Linux, Mac, Windows, Solaris[由来] “Humongous”(ばかでかい)amounts of data[ライセンス] ・Database: GNU AGPL v3.0 License・Drivers: Apache License v2.0

イントロダクション

[ドキュメント指向DB] JSON(内部ではBSONで保持)

[完全なインデックスサポート] あらゆる属性でインデックス作成

[Replication] 自動フェイルオーバー・高可用性

[Sharding] 自動シャーディング

[Map/Reduce] フレキシブルな記述が可能

[クエリ] あらゆる条件で条件指定可能

[高速 In-Place Update] 高速なAtomic Modifiers

[GridFS] 巨大なファイルも保存可能http://www.mongodb.org/

MongoDB の特徴

1. GridFSについて

2. 地理情報インデックスについて

3. Sharding チュートリアル

4. MapReduce の要点とはまりどころ

アジェンダ

1. GridFSについて

1. GridFSについて[特徴]

・MongoDBは、バイナリデータの格納を BSON でサポート

・ただし1ドキュメント当たり16MBの壁(v1.6では4MB)

・GridFSはそれ以上の単一ファイルの保存を可能に

・ファイルを複数のドキュメントに透過的に分割することで実現

・画像、音楽、動画ファイルも保存可能

・Sharding 可能(ただしfile単位で)

・ここ1年くらい更新が無い

1. GridFSについて[仕組み]

・デフォルトで256KBのChunkに分割

・chunksコレクションにそれぞれ異なるドキュメント(バイナリ)として格納

・filesコレクションでオブジェクトのメタデータを保存

・(1つのファイルのGridFS構成)=

(fileコレクションの1ドキュメント) +

(chunkコレクションの複数ドキュメント)

1. GridFSについて

[fs.filesコレクション]

・デフォルトで持つフィールド

{ "_id" : <unspecified>, //fileのユニークID "length" : data_number, //ファイルサイズ(bytes) "chunkSize" : data_number, //各チャンクサイズ(デフォ:256KB) "uploadDate" : data_date, //file格納日 "md5" : data_string //"filemd5"コマンドを実行して返る値}

1. GridFSについて

[fs.filesコレクション]

・任意にフィールドを追加可能:以下はその例

{

"filename" : data_string,

"contentType" : data_string,

"aliases" : data_array of data_string,

"metadata" : data_object,

}

1. GridFSについて[fs.chunksコレクション]

・デフォルトで持つフィールド

{

"_id" : <unspecified>, //chunk集合内でユニークなID

"files_id" : <unspecified>, //fileコレクションの該当する_id

"n" : chunk_number, //0から始まるchunk番号でファイル情報は先頭

から順に0,1,2...のchunk番号に格納されていく

"data" : data_binary, // BSONバイナリとしてのデータ

}

1. GridFSについて[インデックス]

・chunkコレクションに対してfiles_idとnの複合インデックスがセオリー

・検索は必ずfindOneコマンドで。カーソルは使用しない。ドライバ側ではサポート、範囲検索なども可能

> db.fs.chunks.ensureIndex( {files_id:1, n:1}, {unique: true});

> db.fs.chunks.findOne({files_id: myFileID, n: 0});

1. GridFSについて

➜ ~ mongofiles --helpusage: mongofiles [options] command [gridfs filename]command: one of (list|search|put|get) list - fileのリストアップ

search - fileの検索

put - fileの追加

get - fileの取得

delete - fileの削除

[mongofiles]

1. GridFSについて➜ ~ mongofiles list connected to: 127.0.0.1

➜ ~ mongofiles put ByeForNow.mp3 connected to: 127.0.0.1added file: { _id: ObjectId('4df17f8d9d47ba5c0247e72e'), filename: "ByeForNow.mp3", chunkSize: 262144, uploadDate: new Date(1307672462538), md5: "9ee9472200a2e18bf376ce622c3b0055", length: 11183104 }done!

➜ ~ mongofiles list -v // -vオプションで詳細な出力Fri Jun 10 11:21:05 creating new connection to:127.0.0.1Fri Jun 10 11:21:05 BackgroundJob starting: connected to: 127.0.0.1ByeForNow.mp3 11183104

1. GridFSについて➜ ~ mongofiles put Maria.mp3connected to: 127.0.0.1added file: { _id: ObjectId('4df181fc5e354129e833193f'), filename: "Maria.mp3", chunkSize: 262144, uploadDate: new Date(1307673086293), md5: "9d4f424fa1843711e196e502d8a00183", length: 12225353 }done! ➜ ~ mongofiles listconnected to: 127.0.0.1ByeForNow.mp3 11183104Maria.mp3 12225353➜ ~ mongofiles list Mconnected to: 127.0.0.1Maria.mp3 12225353➜ ~ mongofiles search .mp3connected to: 127.0.0.1ByeForNow.mp3 11183104Maria.mp3 12225353

1. GridFSについて➜ ~ mkdir tmp //別の場所でgetしてファイルの整合性を確認

➜ ~ cd tmp➜ mongofiles get Maria.mp3connected to: 127.0.0.1done write to: Maria.mp3➜ lsMaria.mp3➜ mongofiles get ByeERROR: file not found➜ mongofiles get ByeForNow.mp3done write to: ByeForNow.mp3➜ md5 Maria.mp3MD5 (Maria.mp3) = 9d4f424fa1843711e196e502d8a00183➜ md5 ../Maria.mp3MD5 (../Maria.mp3) = 9d4f424fa1843711e196e502d8a00183

1. GridFSについて> db.fs.files.find().forEach(printjson){ "_id" : ObjectId("4df17f8d9d47ba5c0247e72e"), "filename" : "ByeForNow.mp3", "chunkSize" : 262144, "uploadDate" : ISODate("2011-06-10T02:21:02.538Z"), "md5" : "9ee9472200a2e18bf376ce622c3b0055", "length" : 11183104}{ "_id" : ObjectId("4df181fc5e354129e833193f"), "filename" : "Maria.mp3", "chunkSize" : 262144, "uploadDate" : ISODate("2011-06-10T02:31:26.293Z"), "md5" : "9d4f424fa1843711e196e502d8a00183", "length" : 12225353}

1. GridFSについて

> db.fs.chunks.findOne( {n:0,files_id:ObjectId("4df181fc5e354129e833193f")}){ "_id" : ObjectId("4df181fcd40994ca1de28d09"), "files_id" : ObjectId("4df181fc5e354129e833193f"), "n" : 0, "data" : BinData(0,"SUQzAwAAAABQdlRSQ0sAAAACAAAAM1RJVDIAAAAHAAAAg32DioNBVUZJRAAAAGYAAGh0dHA6Ly93d3cuY2RkYi5jb20vaWQzL3RhZ2luZm8xLmh0bWwAM0NEM00xMTBRMjA3NTYwOTc4VjY4MTBCQTlBNjUzN0JCQUQ1QTgyOUE4NTRCRkQ2QTdBRjNQOAAAAAAAAAAAAEdFT0IAACNgAAAAYXBwbGljYX...)}

1. GridFSについて[Sharding]

・filesコレクションは分割不可能(1つのShardに集中)

・chunksコレクションは分割可能、ただしfile単位

・ShardKeyは”files_id”がセオリー、files_idではデフォルトでObjectIdで時間増加なので工夫する必要あり

・全てのchunkを同じshardに納めるのはfileの整合性を保持するため。分割配置するとmd5値がオリジナルと合わない可能性がある

> db.fs.chunks.ensureIndex({files_id: 1});> db.runCommand({ shardcollection : "test.fs.chunks", key : { files_id : 1 }}){ "collectionsharded" : "test.fs.chunks", "ok" : 1 }

1. GridFSについて

[まとめ]

・mongofiles コマンドかドライバを通じてファイル操作がメイン

・mongoシェルはfilesコレクションのフィールド追加などを行うのみ

・1つのfileを分割して複数Shardに配置するのは非推奨

・GridFS周りの開発があまり活発では無い

・Write性能はそれほど高くない → MongoDB の GridFS の性能を調べてみた

1. GridFSについて[実用に向けて]

・chunksコレクションをReplicationさせれば分散FSライク

・メタデータが分離されているのは素晴らしい

・最低でもメタデータを持つfilesコレクションはReplicationする(HA)

・NFSからGridFSへ → Storing and Serving Dynamic Assets with MongoDB's GridFS

・AmazonS3からGridFSヘ → Replace Amazon S3 with MongoDB GridFS and Grails

・ドライバのサポートするchunksコレクションへの範囲検索により、動画の先頭の1/nのみ取得するといった事が可能

2. 地理情報インデックスについて

[特徴]

・MongoDBは位置をベースにしたクエリをサポート

・そのために二次元の地理空間のインデックスが存在

・「自分の場所に近いN個のデータを取得」といった検索が可能

・v1.8では球面を考慮した検索が可能になった

・v1.8ではSharding環境でも使用できるようになった

・制限:コレクションあたり1つの地理情報インデックス

2. 地理情報インデックスについて

[ロケーションオブジェクト]

・配列かサブオブジェクトで単位の同じx,yの2値をフィールドに

・ただし、x,yの順序が入れ替わるようなハッシュはダメ

・以下のロケーションオブジェクトは全て有効

2. ロケーションオブジェクト

{ loc : [ 50 , 30 ] } { loc : { x : 50 , y : 30 } } { loc : { foo : 50 , y : 30 } } { loc : { long : 40.739037, lat: 73.992964 } }

[インデックスの作成]

・キーに対して値を(±1ではなく) ”2d” とすることで地理情報インデックスを作成

・デフォルトは経度/緯度の座標と認識、i.e. [-180,180]の範囲の値を想定

・min/maxオプションで範囲の拡張が可能

・この場合の範囲は [min, max) であり、この範囲外のロケーションデータのinsertはエラー

2. インデックス

db.places.ensureIndex( { loc : "2d" } )

db.places.ensureIndex( { loc : "2d" } , { min : -500 , max : 500 } )

[完全一致]

[近傍検索]:$near クエリ

・(平面の意味で)指定した地点に距離が近い順に取得

・sort オプションは不要。デフォルトでは100件取得

・$maxDistanceで最大距離を指定、この距離以内の条件で検索

2. クエリ

db.places.find( { loc : { $near : [50,50] } } )db.places.find( { loc : { $near : [50,50] } } ).limit(20)db.places.find( { loc : { $near : [50,50] , $maxDistance : 5 } } ).limit(20)

db.places.find( { loc : [50,50] } )

[領域検索]:$within クエリ + $box、$center、$polygon

・指定した長方形・円・多角形内に入っているオブジェクトを検索

2. クエリ

// v1.9 でサポート> polygonA = [ [ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ] ]> polygonB = { a : { x : 10, y : 20 }, b : { x : 15, y : 25 }, c : { x : 20, y : 20 } }> db.places.find({ "loc" : { "$within" : { "$polygon" : polygonA } } })> db.places.find({ "loc" : { "$within" : { "$polygon" : polygonB } } })

// 中心と半径を指定> center = [50, 50]> radius = 10> db.places.find({"loc" : {"$within" : {"$center" : [center, radius]}}})

// 左下と右上の座標を指定> box = [[40.73083, -73.99756], [40.741404, -73.988135]]> db.places.find({"loc" : {"$within" : {"$box" : box}}})

[geoNearコマンド]

・検索結果に距離などの詳細な情報を付与して表示してくれる

2. クエリ

> db.runCommand( { geoNear : "places" , near : [ 50 , 50 ], num : 10, query : { type : "museum" } } );

{ "ns" : "test.places", "near" : "1100110000001111110000001111110000001111110000001111", "results" : [ { "dis" : 69.29646421910687, "obj" : { "_id" : ObjectId("4b8bd6b93b83c574d8760280"), "y" : [ 1, 1 ], "category" : "Coffee" } }, { "dis" : 69.29646421910687, "obj" : { "_id" : ObjectId("4b8bd6b03b83c574d876027f"), 1...}

[Sphereクエリ]

・球面の距離(弧の長さ)に基づいて検索

・球面では経度と緯度、同じ1度の違いでも軸の方向で距離が異なる

2. クエリ

MongoDBと位置情報 ~地理空間インデックスの紹介

[Sphereクエリ]

・$nearSphere、$centerSphere が使える

・(経度, 緯度)の順で必ずロケーションを保持すること!

・距離はラジアンで指定する( arcLength[km] / 6378[km] )

・地球の平均半径は約6378km

・例えば半径0.1ラジアン以内の検索は中心からの弧の長さが637km以内のロケーションオブジェクトを抽出することに

・n-vector formula を使用

2. クエリ

[Sphereクエリ]

・距離が0.4ラジアン(弧の長さが2551km)以内のnear検索

2. クエリ

> db.points.insert({ pos : { long : 30, lat : 30 } })> db.points.insert({ pos : { long : -10, lat : -20 } })> db.points.ensureIndex({ pos : "2d" })

> db.points.find({ pos: { $nearSphere: [0,0], $maxDistance : 0.4 } }){ "_id" : ObjectId("4df11e47b8e84370f84afdd3"), "pos" : { "long" : -10, "lat" : -20 } }

[Sphereクエリ + geoNearコマンド]

・getNearコマンドでは ”spherical : true” とする

2. クエリ

> var earthRadius = 6378 // km> var range = 3000 // km> distances = db.runCommand({ geoNear : "points", near : [0, 0], spherical : true, maxDistance : range / earthRadius }).results[ { "dis" : 0.3886630122897946, // ラジアン

"obj" : { "_id" : ObjectId("4df11e47b8e84370f84afdd3"), "pos" : { "long" : -10, "lat" : -20 } } }]> pointDistance = distances[0].dis * earthRadius2478.89269238431 // km

[まとめ]

・まずは@madapajaさんのわかりやすい資料を読んでください

・Sphereクエリの登場によってgeo検索の精度が大幅に上昇

・v1.9ではマルチロケーションに対応予定

・しかしまだまだ機能面ではPostGISには及ばない

2. 地理情報インデックスについて

3. Shardingチュートリアル

3. Shardingについて

mongod 1

DatabaseA DatabaseB

CollectionA Coll CollDoc Doc DocDoc Doc DocDoc Doc DocDoc Doc Doc

Doc Doc DocDoc Doc Doc

Doc Doc DocDoc Doc Doc

DocDocDocDoc

Doc

Doc Doc DocDoc Doc Doc

EnableSharding

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 2

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 3

mongod 1

DatabaseA DatabaseB

CollectionA Coll CollDoc Doc DocDoc Doc Doc

DocDocDocDoc

Doc

分割

[Sharding とは]

Shard1

Shard2 Shard3

[特徴]

・クライアントの接続を mongos サーバーが仲介することによってクラスタ全体の情報を意識せず扱える

・クライアントは Sharding していない状況と同じようにクエリを発行し、結果を得ることができる

・Shardの 追加・撤退が容易にできる

・自動 Sharding 機能 が指定した Shard Key でデータ振り分けルールを自動決定・随時更新

・自動 Balancing 機能 が Chunk の移動を行い、Shard 間でデータの均質性を保つ

3. Shardingについて

3. 自動Sharding

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 2

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 3mongod 1

DatabaseA DatabaseB

CollectionA Coll CollDoc Doc DocDoc Doc Doc

DocDocDocDoc

Doc

mongosconfigshard 情報

ClientClient Client

データの自動振り分け

[自動 Sharding]

Shard1 Shard2 Shard3

DocDoc

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 2

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 3mongod 1

DatabaseA DatabaseB

CollectionA Coll CollDoc Doc DocDoc Doc Doc

DocDocDocDoc

Doc

mongosconfigshard 情報

ClientClient Client

Doc Doc DocDoc Doc DocDoc Doc DocDoc Doc Doc

shardの偏り!

[自動 Balancing 1]

3. 自動BalancingShard1 Shard2 Shard3

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 2

DatabaseACollectionADoc Doc DocDoc Doc DocDoc Doc Doc

mongod 3mongod 1

DatabaseA DatabaseB

CollectionA Coll CollDoc Doc DocDoc Doc Doc

DocDocDocDoc

Doc

mongosconfigshard 情報

ClientClient Client

Doc Doc DocDoc Doc Doc Doc Doc Doc Doc Doc Doc

マイグレーション※マイグレーションは Chunk 単位

Shard1 Shard2 Shard3

[自動 Balancing 2]

3. 自動Balancing

[注意点]

※ Sharding 環境では様々な問題が起こる

・Unique なキーが重複してしまう

・常に振り分けルールを更新するため、パフォーマンスが低下

・single update operation でエラー

・マイグレーション中は Count() コマンドに誤差

・マイグレーションには多大なメモリ消費とサーバー負荷が

・メモリが不足しているとマイグレーションは起こらない

3. Shardingについて

[参考資料]

・機能・仕組みの詳細についてはこちらを参照してください

3. Shardingについて

http://www.slideshare.net/doryokujin/mongo-sharding

3. Shardingチュートリアル

Client

mongosconfig

Shard01Shard00 Shard02

[目的]

・最小限な構成例でセットアップ

・( Shard ) × 3 + config × 1 + mongos × 1

server1 server2 server3

[注意]

・自動Sharding、自動Balancingの効用はすぐに確認できない

・これらは本当に自動で行ってくれるので各自で試してください

・今回は事前にChunkingルールを作成

・実際にデータを挿入して正しくChunkingされているか確認

・Shardingで必要なコマンドは一通り網羅しているはず

・より発展的な内容はこちらで → Sharding を使いこなすための5つのTips

3. Shardingチュートリアル

[初期設定 from console]

・dbpath の作成。mongosは不要

・mongod、mongos サーバーの起動

~ mongod --shardsvr --port 27017 --dbpath shard/shard00~ mongod --shardsvr --port 27018 --dbpath shard/shard01~ mongod --shardsvr --port 27019 --dbpath shard/shard02~ mongod --configsvr --port 27020 --dbpath shard/config~ mongos --configdb localhost:27020 --port 27021

~ mkdir -p shard/shard00~ mkdir -p shard/shard01~ mkdir -p shard/shard02~ mkdir -p shard/config

3. Shardingチュートリアル

[初期設定 from mongo shell]

・Shardの追加➜ ~ mongo localhost:27021 // mongosに接続

MongoDB shell version: 1.8.0

connecting to: localhost:27021/test

> show dbs

config 0.1875GB // shardingのメタ情報を格納するデータベース

> db.adminCommand( { addshard: "localhost:27017", name: "shard00" } )

{ "shardAdded" : "shard00", "ok" : 1 }

> db.adminCommand( { addshard: "localhost:27018", name: "shard01" } )

{ "shardAdded" : "shard01", "ok" : 1 }

> db.adminCommand( { addshard: "localhost:27019", name: "shard02" } )

{ "shardAdded" : "shard02", "ok" : 1 }

3. Shardingチュートリアル

[初期設定 from mongo shell]

・データベース/コレクションのShard有効化

・moveprimary コマンドでPrimary Shard の変更可能

・myshardコレクションを “ShardKey: n” としてShard化

// 引き続きmongosより

> db.adminCommand( { enablesharding : "test" } )

{ "ok" : 1 }

> db.adminCommand( { moveprimary : "test", to : "shard02" } );

{ "primary " : "shard02:localhost:27019", "ok" : 1 }

> db.adminCommand( { shardcollection : "test.myshard", key : { n : 1 } } )

{ "collectionsharded" : "test.myshard", "ok" : 1 }

3. Shardingチュートリアル

[事前Chunking]

・データ挿入前にChunkingルールを作成

・middle フィールドで指定した値を境に分割するように設定

・split / middle コマンドはデータ挿入前しか使えない

// 引き続きmongosより

> db.adminCommand({split : "test.myshard", middle : { n: 0 } } )

{ "ok" : 1 }

> db.adminCommand({split : "test.myshard", middle : { n: 1 } } )

{ "ok" : 1 }

> db.adminCommand({split : "test.myshard", middle : { n: 2 } } )

{ "ok" : 1 }

3. Shardingチュートリアル

[事前Chunking]

・この状態ではまだ全てのChunkがPrimary Shardに配置されてしまう

> db.printShardingStatus() // 現在の設定を確認

--- Sharding Status ---

sharding version: { "_id" : 1, "version" : 3 }

shards:

{ "_id" : "shard00", "host" : "localhost:27017" }

{ "_id" : "shard01", "host" : "localhost:27018" }

{ "_id" : "shard02", "host" : "localhost:27019" }

databases:

{ "_id" : "admin", "partitioned" : false, "primary" : "config" }

{ "_id" : "test", "partitioned" : true, "primary" : "shard02" }

test.myshard chunks:

shard02 4

{ "n" : { $minKey : 1 } } -->> { "n" : 0 } on : shard02 { "t" : 1000, "i" : 1 }

{ "n" : 0 } -->> { "n" : 1 } on : shard02 { "t" : 1000, "i" : 3 }

{ "n" : 1 } -->> { "n" : 2 } on : shard02 { "t" : 1000, "i" : 5 }

{ "n" : 2 } -->> { "n" : { $maxKey : 1 } } on : shard02 { "t" : 1000, "i" : 6 }

[事前Chunking]

・Chunkingの事前移動を行い、Chunkingルール設定完了

・{ n: x } のオブジェクトは “shard0x” へ移動させている

// 引き続きmongosより

> db.adminCommand({moveChunk: "test.myshard", find: { n: 0 }, to: "shard00" });

{ "millis" : 1051, "ok" : 1 }

> db.adminCommand({moveChunk: "test.myshard", find: { n: 1 }, to: "shard01" });

{ "millis" : 1046, "ok" : 1 }

> db.adminCommand({moveChunk :"test.myshard", find: { n: 2 }, to: "shard02" });

{ "ok" : 0, "errmsg" : "that chunk is already on that shard" }

3. Shardingチュートリアル

[事前Chunking]

・{ n: x }のフィールドを持つオブジェクトは”shard0x”に配置される

> db.printShardingStatus()

--- Sharding Status ---

sharding version: { "_id" : 1, "version" : 3 }

shards:

{ "_id" : "shard00", "host" : "localhost:27017" }

{ "_id" : "shard01", "host" : "localhost:27018" }

{ "_id" : "shard02", "host" : "localhost:27019" }

databases:

{ "_id" : "admin", "partitioned" : false, "primary" : "config" }

{ "_id" : "test", "partitioned" : true, "primary" : "shard02" }

test.myshard chunks:

shard02 2

shard00 1

shard01 1

{ "n" : { $minKey : 1 } } -->> { "n" : 0 } on : shard02 { "t" : 3000, "i" : 1 }

{ "n" : 0 } -->> { "n" : 1 } on : shard00 { "t" : 2000, "i" : 0 }

{ "n" : 1 } -->> { "n" : 2 } on : shard01 { "t" : 3000, "i" : 0 }

{ "n" : 2 } -->> { "n" : { $maxKey : 1 } } on : shard02 { "t" : 1000, "i" : 6 }

[データ挿入]// 引き続きmongosより

> for(var m=0; m<100; m++){ db.myshard.insert({n: m % 3}) } // n =0,1,2,0,1,...

> printShardingSizes() // Shard舞いのデータサイズ・数(概算)を確認

...

test.myshard chunks:

{ "n" : { $minKey : 1 } } -->> { "n" : 0 } on : shard02 { "estimate" :

false, "size" : 0, "numObjects" : 0 }

{ "n" : 0 } -->> { "n" : 1 } on : shard00 { "estimate" : false, "size" :

1224, "numObjects" : 34 }

{ "n" : 1 } -->> { "n" : 2 } on : shard01 { "estimate" : false, "size" :

1188, "numObjects" : 33 }

{ "n" : 2 } -->> { "n" : { $maxKey : 1 } } on : shard02 { "estimate" :

false, "size" : 1188, "numObjects" : 33 }

3. Shardingチュートリアル

[Shardごとに確認]➜ ~ mongo localhost:27017

> db.myshard.count()

34

> db.myshard.distinct("n")

[ 0 ]

➜ ~ mongo localhost:27018

> db.myshard.count()

33

> db.myshard.distinct("n")

[ 1 ]

➜ ~ mongo localhost:27019

> db.myshard.count()

33

> db.myshard.distinct("n")

[ 2 ]

3. Shardingチュートリアル

3. Replica Set/Shard

config

Shard01 Shard02

config

Client

mongos

Shard00

arbiter02

mongos

Shard02

arbiter01

mongos

Shard01

arbiter00

config

Shard00

set00 set01 set02

Primary

Secondary

3. Replica Set/Shard

config

Shard01 Shard02

config

Client

mongos

Shard00

arbiter02

mongos

Shard02

arbiter01

mongos

Shard01

arbiter00

config

Shard00

set00 set01 set02

Primary

Secondary

mongosは各Replica SetからPrimaryを確認して接続

[初期設定 from mongo shell]

・Shardの追加(Sharding + Replica Set の場合)

・addshard: ”<Replica Set Name>/<menber1>,<member2>,...”

> db.adminCommand( { addshard: "set00/delta1:27017,delta2:27017", name:

"shard00" } )

{ "shardAdded" : "shard00", "ok" : 1 }

> db.adminCommand( { addshard: "set01/delta3:27018,delta4:27018", name:

"shard01" } )

{ "shardAdded" : "shard01", "ok" : 1 }

> db.adminCommand( { addshard: "set02/delta5:27019,delta6:27019", name:

"shard02" } )

{ "shardAdded" : "shard02", "ok" : 1 }

3. Shardingチュートリアル

[まとめと所感]

・事前にShardKeyの分布がわかる場合はChunkingしておく方が賢明

・Shardingの設定はとっても簡単♪ただし運用はそれなりに大変…

・Sharding + Replica SetではSecondaryノードを活用することができない

・1Server - 1Shard である必要は無い。Write LockやMapReduceの効率性を考えるとむしろ非効率

・1Server - N Shard [N=core数] の構成で構わない

・Mongo Shardingはうまく構成・運用すれば最強

3. Shardingチュートリアル

Shard00

Shard01

Shard02

Shard03

Shard04

Shard05

Shard06

Shard07

Shard04

Shard05

Shard06

Shard07

Shard20

Shard21

Shard22

Shard23

Shard16

Shard17

Shard18

Shard19

mongos

...

Shard24 SSD

mongos ... mongos

Primary

Secondary

3. N Shards/Server

Shard00

Shard01

Shard02

Shard03

config

config

Mongo Redis Model (for middle data)

...

global redis

local redis local redis local redis

ResultCollection

② ②

③③③

④④④

⑤MongoDBで作るデータ解析基盤

4. MapReduce の要点とはまりどころ

4. MapReduceの要点[特徴]

・Sharding環境でMapReduceを実行する事で分散データ処理が可能

・ただしShuffle機能が無い等、Hadoop等に比べて簡易的実装

・実行はJavaScriptエンジンの制約を受ける:1スレッド/Shard

・ある程度仕組みを知っておかないと正しい結果が得られない

・集約関数の拡張として捉えてもらった方が気軽に扱える

・v1.8でoutputオプションが充実して扱いやすくなった

4. MapReduceとは(一般論)

【クラウドコンピューティング】 MapReduceの復習

“Primary”に指定した Shard

ResultCollection

3. Reduce

out option1. replace2. merge3. reduce4. memory

4. Result

[Mongo Map Reduce Model]

mongos ※shuffle 機能は無い1.ルーティング・指示

Shard1

2. Map Reduce

Shard2

2. Map Reduce

Shard3

2. Map Reduce

Shard4

2. Map Reduce

Shard5

2. Map Reduce

Shard6 2. Map Reduce

Shard13. Reduce

4. MapReduceとは(Mongo)

[mapReduceコマンド]

db.mycoll.mapReduce(

 map : <map ファンクション名>,

 reduce : <reduce ファンクション名>

 [, finalize : <finalize ファンクション>]

 [, query : <クエリーフィルタオブジェクト>]

 [, sort : <入力オブジェクトに対して、ここで指定されたキーに対してソートを行い

ます。reduceへ渡すキーを少なくするための最適化として有用です>]

 [, out : <出力先のコレクション名>]

 [, scope : <object where fields go into javascript global scope >]

);

4. MapReduceの要点

[例で使用するデータ]

・ゴールはtype値ごとに属するユーザーの平均年齢の算出

db.people.insert({userId:1, age:24, type:"A"})

db.people.insert({userId:2, age:47, type:"B"})

db.people.insert({userId:3, age:34, type:"C"})

db.people.insert({userId:4, age:4, type:"B"})

db.people.insert({userId:5, age:14, type:"A"})

db.people.insert({userId:6, age:29, type:"C"})

db.people.insert({userId:7, age:39, type:"C"})

db.people.insert({userId:8, age:42, type:"B"})

db.people.insert({userId:9, age:56, type:"A"})

db.people.insert({userId:10, age:12, type:"A"})

4. MapReduceの要点

[mapファンクション]

・mapReduceコマンドが実行されると、指定したコレクション全ての(または”query”オプションでフィルタした)ドキュメントがmapファンクションによって順に評価されていく

・emitコマンドでemit( key,value )という形で、reduceファンクションに渡す際のキーとバリューを指定

・emitは1回以上の呼び出しが必要。扱えるドキュメントサイズはドキュメント制限の半分:16MB/2 = 8MB

m = function() { emit(this.type, { age: this.age, num: 1 } ); }

4. MapReduceの要点

[reduceファンクション]

・reduceファンクションではemitされたキーと値の配列のペアを受け取り、それを1つの値にreduceする

・ここではキー(type)ごとにageについて和を取り、numについても和をとる

r = function(key, values) { var result = { age: 0, num: 0 }; values.forEach( function(value){ result.age += value.age; // ageについて和 result.num += value.num; // キーの出現数をカウント } ); return result;}

4. MapReduceの要点

[reduceファンクション]

・次の記述は2点の誤り

r = function(key, values) { var num = 0; var sum = 0; values.forEach( function(value){ sum += value.age; // ageについて和 num += value.num; // キーの出現数をカウント } ); return sum/num;}

4. MapReduceのはまりどころ

[ポイント1]: reduceファンクションが返すドキュメントはmapファンクションのemitが返すドキュメントと同じ構造でないとダメ

[理由]: あるキーがreduce処理された後もmap側からそのキーを受け取る場合がある。その場合に同じ構造を持っていないと”NaN”になる

4. MapReduceのはまりどころ

m = function() { emit(this.type, { age: this.age, num: 1 } ); }r = function(key, values) { var result = { age: 0, num: 0 }; values.forEach( function(value){ result.age += value.age; // ageについて和 result.num += value.num; // キーの出現数をカウント } ); return result;}

[ポイント2]: reduceファンクションは何回も繰り返される。実行順序や実行回数に依存して異なる(冪等な)計算は行ってはいけない。

[理由]: 例えば平均の計算 。(2,4,6) に対して先に2つの値で平均を求めて、その値と最後の値で平均を求めると4.5。一度に平均を求めると4。

4. MapReduceのはまりどころ

m = function() { emit(this.type, { age: this.age, num: 1 } ); }r = function(key, values) { var result = { age: 0, num: 0 }; values.forEach( function(value){ result.age += value.age; // 実行回数・順序に依らない result.num += value.num; // 〃 } ); return result;}

[finalizeファンクション]

・全てのreduce処理が完了した後に(キーごとに)一度だけ呼ばれるコマンド。個々で先ほどの平均や分散などを計算する

f = function( key, result ){ result.avg = result.age / result.num; return result;}

4. MapReduceのはまりどころ

[実行]

4. MapReduceの要点> res = db.people.mapReduce( m , r , { finalize : f , out : "mr_out", verbose: true });{ "result" : "mr_out", // 出力先(コレクション名) "timeMillis" : 18, // 実行時間 "timing" : { "mapTime" : NumberLong(1), "emitLoop" : 12, "total" : 18 }, "counts" : { "input" : 10, // 10件の入力 "emit" : 10, // 10回のemitでの評価 "output" : 3 // 3件の出力 }, "ok" : 1,}

[確認]

・”_id”にキーが、”value”の中に所望の計算結果が入っている

4. MapReduceの要点

> db.mr_out.find()

{ "_id" : "A",

"value" : { "age" : 106, "num" : 4, "avg" : 26.5 } }

{ "_id" : "B",

"value" : { "age" : 93, "num" : 3, "avg" : 31 } }

{ "_id" : "C",

"value" : { "age" : 102, "num" : 3, "avg" : 34 } }

[query オプション]

・指定しない場合はコレクション全てのデータを読み込む

・指定するとmapに渡すデータをフィルタリングできる

4. MapReduceの要点

> res = db.people.mapReduce( m , r , { finalize : f , out : "mr_out", query: { age: { $gte: 20 } } });{ "result" : "mr_out", "timeMillis" : 35, "counts" : { "input" : 7, // 3件が除外されている "emit" : 7, "output" : 3 }, "ok" : 1,}

[確認]

4. MapReduceの要点

> db.mr_out.find() //平均年齢が上がった

{ "_id" : "A",

"value" : { "age" : 80, "num" : 2, "avg" : 40 } }

{ "_id" : "B",

"value" : { "age" : 89, "num" : 2, "avg" : 44.5 } }

{ "_id" : "C",

"value" : { "age" : 102, "num" : 3, "avg" : 34 } }

[outputオプション]

・{ replace : "collectionName" }: 既存の同名コレクションと出力を置き換える。過去のコレクションの内容は消去される。コレクション名だけ指定した場合もこの処理が行われる

・{ merge : "collectionName" }: 以前の出力コレクションに今回の結果をマージする。既存のコレクション内にある同名のキーは新出力の値で上書きされ、新出力に存在しないキーは以前の値を保持する

4. MapReduceの要点

[outputオプション]

・{ reduce : "collectionName" }: もし以前の出力コレクションに同名のキーがあった場合、reduceファンクションをそのキーに対して実行。i.e. 新出力のキーの値と以前のキーの値の2つに対してreduceが行われる。finalize ファンクションはこの操作の後に行われる

・{ inline : 1}: このオプションではコレクションは作成されず、mapReduceコマンドはRAM内で行われる。また、処理結果は result object として返る。このオプションは出力セットが(1ドキュメントのサイズ上限である)16MB以内でないと使えないことに注意。唯一Secondaryに対しても実行できるコマンド

4. MapReduceの要点

[replaceを使用する例]

・”2011-06-01”で集計しておいた売り上げに”2011-06-02”で集計した売り上げを合算する

4. MapReduceの要点

db.items.insert({ date: "2011-06-01", item: "apple", price: 100 })

db.items.insert({ date: "2011-06-01", item: "banana", price: 200 })

db.items.insert({ date: "2011-06-01", item: "apple" , price :100})

db.items.insert({ date: "2011-06-02", item: "orange", price: 50 })

db.items.insert({ date: "2011-06-02", item: "orange", price: 50 })

db.items.insert({ date: "2011-06-02", item: "apple", price: 100 })

4. MapReduceの要点m = function() { emit(this.item, this.price); }r = function(key,values) { var result = 0; values.forEach( function(value){ result += value } ); return result;}> res = db.items.mapReduce( m, r, { query: {date: "2011-06-01"} , out: {replace: "mr_out2"}} ); { "result" : "mr_out2", "timeMillis" : 10, "counts" : { "input" : 3, "emit" : 3, "output" : 2 }, "ok" : 1,}>db.mr_out2.find() { "_id" : "apple", "value" : 200 }{ "_id" : "banana", "value" : 200 }

[“replace” outputオプション]

4. MapReduceの要点

> res = db.items.mapReduce( m, r, { query: {date: "2011-06-02"} , out: {reduce: "mr_out2"}} ); { "result" : "mr_out2", "timeMillis" : 23, "counts" : { "input" : 3, "emit" : 3, "output" : 3 }, "ok" : 1,}> db.mr_out2.find() { "_id" : "apple", "value" : 300 } //既存のコレクションの出力に合算された{ "_id" : "banana", "value" : 200 } //既存のコレクションの出力に合算された{ "_id" : "orange", "value" : 100 } //既存のコレクションの出力に合算された

4. MapReduceの要点[その他・まとめ]

・emit()はmap内で何回も呼び出せる。例えばドキュメント内の配列やサブオブジェクトの要素毎にemit()を呼び出せる

・冪等性を理解することは重要

・reduceが何回も呼び出されるのでappend()などを使うと多重配列ができてしまう。flattenメソッドを実装してfinalizeで実行するなど

・MongoShellから実行する時は、Underscore.jsを読み込んで使うのが便利

・v2.0以降でAggregation、MapReduceは格段に進化する

ありがとうございました