db tech showcase: 噂のmongodbその用途は?
DESCRIPTION
TRANSCRIPT
噂のMongoDBその用途は?
About me
{名前: 窪田博昭会社: Cookpad.Inc肩書: MongoDBJP代表趣味: Golf,フットサル悩み: 30代は体が・・・twitter:@crumbjpgithub: github.com/crumbjp
}
We're hiring !!
-5 -
-6 -
-7 -
-8 -
-9 -
-10 -
-13 -
RDBMSから機能を削り スループットや並列性を確保している
比較的低機能ではあるが一点豪華主義で 部分的にRDBMSを凌駕する機能を持つ
大抵は速度重視
NoSQL全般
-16 -
1. スキーマレス 2. 高機能Index 3. 高速 4. 並列性(& automatic failover) ・耐障害性 ・スケーラビリティー 5.トランザクション無し ・代わりのAtomic処理 6.JOIN無し
MongoDBの特徴
-18 -
Index
食料品で\200以下の商品を取得
MongoDBのIndex
商品名 タグ 値段
ボールペン 日用品, 筆記用具 198包丁 日用品, 刃物, キッチン 2980バナナ 食料品, 果物 348ほうれん草 食料品, 野菜 98牛乳 食料品, 148
食料品で\200以下の商品■RDBMS 苦手な処理 ・Index用のテーブルを 別途用意しJOIN 値段が重複管理で筋が悪い。。
MongoDBのIndex商品名 タグ 値段
ボールペン 日用品, 筆記用具 198包丁 日用品, 刃物, キッチン 2980バナナ 食料品, 果物 348ほうれん草 食料品, 野菜 98牛乳 食料品, 148
クエリーSELECT * FROM 商品 INNER JOIN タグ ON タグ.商品id = 商品.idWHERE タグ名 = '食料品' AND 値段 <= 200;
商品・テーブルCREATE TABLE 商品 ( id INT PRIMARY, 商品名 VARCHAR(100));タグ・テーブルCREATE TABLE タグ ( 商品id INT, タグ名 VARCHAR(100), 値段 INT, KEY(タグ名,値段));
商品投入INSERT INTO 商品 VALUES (1,'ボールペン');INSERT INTO タグ VALUES (1,'日用品',198), (1,'筆記用具',198); :
食料品で\200以下の商品を取得■MongoDB データ形式、Index共に 完全にサポート!
タグ構造にもってこい!!
MongoDBのIndex商品名 タグ 値段
ボールペン 日用品, 筆記用具 198包丁 日用品, 刃物, キッチン 2980バナナ 食料品, 果物 348ほうれん草 食料品, 野菜 98牛乳 食料品, 148
インデックスdb.商品.ensureIndex({ 'タグ':1, '値段':1 });
クエリーdb.商品.find({ 'タグ':'食料品', '値段': { $lte : 200 } });
商品投入db.商品.save({ '商品名' : 'ボールペン', 'タグ': ['日用品', '筆記用具'], '値段':198 }); : :
食料品で果物の商品を取得
MongoDBのIndex
商品名 タグ 値段
ボールペン 日用品, 筆記用具 198包丁 日用品, 刃物, キッチン 2980バナナ 食料品, 果物 348ほうれん草 食料品, 野菜 98牛乳 食料品, 148
食料品で果物の商品■RDBMS SQLでの表現は困難 self join する?
MongoDBのIndex商品名 タグ 値段
ボールペン 日用品, 筆記用具 198包丁 日用品, 刃物, キッチン 2980バナナ 食料品, 果物 348ほうれん草 食料品, 野菜 98牛乳 食料品, 148
クエリー(2タグ限定版)SELECT * FROM タグ a INNER JOIN タグ b ON a.商品id = b.商品id AND a.タグ名 != b.タグ名 INNER JOIN 商品 c ON a.商品id = c.idWHERE a.タグ名 = '食料品' AND b.タグ名 = '果物'GROUP BY a.商品id;
食料品で果物の商品■MongoDB $all が使える 今の実装では最初の 要素のみIndex scan
MongoDBのIndex商品名 タグ 値段
ボールペン 日用品, 筆記用具 198包丁 日用品, 刃物, キッチン 2980バナナ 食料品, 果物 348ほうれん草 食料品, 野菜 98牛乳 食料品, 148
クエリーdb.商品.find({ 'タグ': { $all : ['食料品', '果物']}});
主なIndex ・配列へのIndex(tag) ・複合Index ・平面/球面座標Index ・図形Index(交点,内包,近傍など各種検索) ・円, 線, 多角形, etc
covered indexesも搭載 NoSQLの弱点を多機能なIndexで補う戦略
MongoDBのIndex
-26 -
高スループット
計測条件・3 core 3GB memory
・構文解析込み(SQL, JS) ・lo経由通信 (localhost) ・1レコード、4kb程度 ・1,000,000 レコード = 4GB ・11 column ・MySQLはInnoDB
MongoDBのスループット
id int(11) PRIvalue0 int(11)value1 int(11)value2 int(11)value3 varchar(50)value4 varchar(50)value5 varchar(50)value6 varchar(50)value7 varchar(255)value8 int(11)value9 text
Insert・MySQL : 345 sec (5m 45s)
・MongoDB : 123 sec (2m 03s)
・(MyIsam ) : 240 sec (4m 00s)
MongoDBのスループット
Range fetch (10,000件 x 100)・MySQL : 202 sec
・MongoDB : 3.7 sec
Range count (10,000件 x 100)・MySQL : 37 sec
・MongoDB : 0.4 sec
MongoDBのスループット
SELECT *FROM mytblWHERE id BETWEEN 0 AND 9999;
SELECT COUNT(*)FROM mytblWHERE id BETWEEN 0 AND 9999;
MongoDB 範囲検索は特に速い! ・B-tree実装が良い
Countが速い! ・カーソルの両端を先に見る
Read >>> Writeな
WEBシステムに最適
MongoDBのスループット
-31 -
並列性
MongoDBは2つのスケールアウト方式 が用意されている
・Replica-set(ミラーリング)Read系の並列化、対障害性
・Sharding(パーティショニング) Write系の並列化、大量データ対策
MongoDBの並列性
-33 -
Replica-set
-34 -
replica-set
Primary
Secondary SecondarySecondarySecondary
Primary: 書き込み可能ノードSecondary: 読み取り専用ノード
全てのノードは同じデータを持っている
同期
-35 -
replica-set
Primary
Secondary SecondarySecondarySecondary
Primary: 書き込み可能ノードSecondary: 読み取り専用ノード
Primaryが死んでも自動Failover
Primary昇格 同期
-36 -
replica-set
Primary
Secondary SecondarySecondarySecondary
Primary: 書き込み可能ノードSecondary: 読み取り専用ノード
自動Failoverではsplit brainが怖いのでvotingで対策(以下の例では4/5 で当選)
Primary
投票
mongo A
www.mongodb.jp
httpd
PHP
mongodsecondary
mongo B
mongodprimary
replica-set
replica-set
mongo A
www.mongodb.jp
httpd
PHP
mongodsecondary
mongo B
mongodprimary
mongo C
mongodsecondary
replica-set
replica-setwww.mongodb.jp
httpd
PHP
-39 -
Sharding
-40 -
Sharding
mongos
Replica-set1
mongos:クエリーゲートウェイ mapping情報に基づき振り分ける config: mapping情報を保持
key = 'abc' configconfigconfig
Replica-set2 Replica-set3
key = 'xyz'key => replicaset mapa.. ~ hz.. => Replica-set1ia.. ~ rc.. => Replica-set2rd..~ z.. => Replica-set3
RDBMSのパーティショニングと同じ発想
複数のReplica-setを束ねるイメージ 並列性や冗長性はReplica-setに任せる
Shardingキーの選択さえ適切ならば Auto migrationでデータを適度に分散
Sharding
詳細はコチラSharding
Read負荷が高いならReplica-set MongoDBは元々高速なので殆どの問題は Secondaryを増やせば解決する
Write負荷やデータ量の問題はSharding Write負荷:非時系列のShard-key データ量 :時系列Shard-key (扱いやすいから)
MongoDBの並列性
-44 -
トランザクションが無い
Webシステムでは、殆どの場合 トランザクションを使わない。使えない。
HTTPはstatelessなプロトコルで statefulなトランザクションと相性が悪い。 掛けたまま帰っちゃう! いつ戻って来るのか? もう来ないのか? 不明!!
MongoDBの排他
とはいえ、ある程度の排他処理は必要
NoSQLでは楽観ロックが主流 並列化を目指すと悲観ロックは機能しない memcached の CASが有名 (Check and Set)
MongoDBの排他
CAS 1. aがデータAをget
MongoDBの排他
memcachedA (cas=1)
A (cas=1)
CAS 1. aがデータAをget
2. ほぼ同時にbがデータAをget
MongoDBの排他
memcachedA (cas=1)
A (cas=1)A (cas=1)
楽観ロックなので取れて良い
CAS 1. aがデータAをget
2. ほぼ同時にbがデータAをget 3. aがデータAを更新
MongoDBの排他
memcachedA (cas=1)
A (cas=1)A (cas=1)
A' (cas=1)
cas値が一致するので更新成功
A' (cas=2)
CAS 1. aがデータAをget
2. ほぼ同時にbがデータAをget 3. aがデータAを更新
4. bがデータAを更新
MongoDBの排他
memcached
A (cas=1)
cas値が不一致で更新失敗
A' (cas=2)
A''(cas=1)
-51 -
atomic操作
MongoDBにはCASが無い もっと強力なatomic操作がある
特に便利なatomic操作 $inc フィールドをインクリメントする $setOnInsert upsertの際、insert時だけ値を設定する
atomic操作
-53 -
簡易CAS
CASの実装は簡単
・update時に常にcas値を1つ増加させる。
・update時のクエリにドキュメントのcas値を使えば
衝突した際にはcas値が合わずupdateが失敗する
PUTdb.myData.update({ _id: data1._id, cas: data1.cas },{ $inc : { cas : 1 }, $set { field1 : 'updated' }});
GETdata1 = db.myData.findOne({ _id : 'FOO' });
配列系のatomic操作 $pop / $pullAll / $pull 配列フィールドから値を削除 $pushAll / $push 配列フィールドに値を追加 capped array 配列フィールドに値を追加する際 任意の配列長に保つよう切り捨てる機能
atomic操作
-55 -
findAndModify
MongoDBにはfindAndModifyがある
findAndModify atomicにデータ取得と更新が出来る
≒ SELECT ~ FOR UPDATE +UPDATE +COMMIT or ROLLBACK
atomic操作
-57 -
簡易MQ
tm = 0 のドキュメントを拾うと同時にtm = 1 に更新してしまう。他のワーカに拾われないという寸法!
インデックスdb.myMQ.ensureIndex({ tm: 1});
投入(アプリ)側db.myMQ.save({ tm: 0, Jobデータ});
ワーカー側db.myMQ.findAndModify({ query: { tm : 0 }, update: { $set : { tm : 1 }}});
-58 -
簡易Jobスケジューラ
tm に時刻(3600秒後)を入れれば、狙った時間にFireできる。$inc : 3600の様にすれば定期的なJobにも出来る。結構自由自在!
インデックスdb.myMQ.ensureIndex({ tm:1});
投入(アプリ)側db.myMQ.save({ tm: Number(ISODate())+3600, Jobデータ});
ワーカー側db.myMQ.findAndModify({ query: { tm : { $lte :Number(ISODate())}}, update: { $set : { tm : Number.MAX_VALUE }}});
-59 -
簡易MapReduce■単純なJobキュー
■MapReduce
-60 -
Monmoちゃん
findAndModifyを上手く使って MapReduce環境を作ってみた。
V8 が優秀で思ったより使える! MongoDB 以外何も要らない (ロジックはDBの外で動作)
Monmoちゃん
-62 -
ついでに
-63 -
MapReduceMongoDB付属のMapReduceは危険
●Primaryで処理が走る『DBでロジックが走る』ってどうなの?
●並列化が出来ない
●NaN等で下手打つとDBが死ぬ原則noscriptが基本!!
-64 -
Joinが出来ない
-65 -
Joinあきらめよう・・・
-66 -
MongoDB何に使おう?
・Readが非常に高速 ・多機能なIndex ・高い並列性(&対障害性) ・高い拡張性
WEBシステムのバックエンドDBに最適 DBキャッシュ層を省略し システムを簡略化できる
まとめ
・多機能なIndex 配列Index(タグそのもの)
タグ機能 DB自体に機能があるので 特別なコードが必要ない
まとめ
・多機能なIndex geohash :2次元座標Index 3dsphere :球面座標Index
地図機能近隣の施設検索などがDBで完結
まとめ
・強力なAtomic操作機能 ・対障害性
MQなど非同期系のバックエンドにも良い システム間のセマフォ的な使い方ができる 非Javaへの親和性も貴重
まとめ
-71 -
最後に地雷の話
■必須オプション ・notablescan ・noscript
■地雷機能 ・MapReduce ・Aggregate ・Textサーチ ・Background indexing
地雷の話 ■難易度が高い機能 ・Sharding
■チューニングポイント ・oplog ・migration (Sharding) ・コネクション数
確かにMongoDBには幾つか地雷がある (そして踏み易い)
巷にあるネガティブな情報は 地雷の爆発した跡 正しい使い方をすれば機能的にも性能的にも 非常に優秀なプロダクト
地雷の話
おしまい
有難う御座いました。