Upload
yuichi-matsuo
View
38.894
Download
2
Embed Size (px)
DESCRIPTION
第3回MongoDB勉強会の発表資料です。
Citation preview
MongoDBチューニング ~ スロークエリ撲滅までの道筋~
id:matsuou1
アジェンダ ! 自己紹介
! 目標
! オプティマイザの概要
! 実行計画について
! スロークエリの特定
! クエリチューニングTips
! まとめ
自己紹介
! ID:matsuou1
! 名前:松尾 裕一(まつお ゆういち)
! 職業:音楽配信サービスの会社のエンジニア
! システムアーキテクト? ! プロジェクトマネージャー? ! 元はOracleやPostgreSQLでチューニング等を。
! 興味があること:MongoDB、Hadoop、R、統計解析、機械学習、データマイニング
目標 1. オプティマイザの概要を理解する。
2. 実行計画を取得し、読めるようになる。
3. 遅いクエリを特定できる。
4. 簡単なクエリのチューニングができる。
1.オプティマイザの概要
オプティマイザとは? ! オプティマイザとは、「クエリの最適化を行う機能」のこと。オプティマイザが実行計画を立てる。
! RDBMSでは、大別してルールベースとコストベースのオプティマイザがあり、コストベースが主流。
! コストベースオプティマイザは、事前に取得された統計情報を使用し、最適な実行計画を決定する。
従来のオプティマイザの問題点 ! コンスタントに悪い実行計画を選択する可能性がある。
! 本番環境と検証環境では統計情報が異なるため、実行計画が異なる可能性がある。
MongoDBのオプティマイザ ! コストベースは使用しない。
! 統計情報は使用しない。(割とCPUコストも高い)
! 新しいクエリを実行する場合は、複数の実行計画を並行に実施し、一つが終わったら他のクエリを停止させる。その際、早い実行計画を学習する。 ! JOINがないからこそ、可能なやり方。
! オーバーヘッドがあるが、最悪のケースは回避可能
実行計画の有効期限 • 一度評価された実行計画は、以下のイベントが発生するまで、使用される。 • 100回書き込まれた場合(insert、update、delete) • インデックスが追加、削除された場合 • 最初の評価時と比較し、nscannedの値が10倍を超えた場合
• 再評価された後も、同じ実行計画が使われる可能性ももちろんある。
オプティマイザ詳細 db/queryoptimizer.cpp がオプティマイザのソースなので、これ以上の詳細は、ソースコードリーディング分科会の方々が明らかにしてくれるはず!! と期待しています。
2.実行計画
実行計画の取得方法 ! 実行計画の取得には、explainコマンドを使用する。
> db.logs.find({uid:"a10Sty5jqqiyxxxx2"}).explain(); {
"cursor" : "BasicCursor", "nscanned" : 2669511, "nscannedObjects" : 2669511, "n" : 3, "millis" : 6100, "nYields" : 0, "nChunkSkips" : 0, "isMultiKey" : false, "indexOnly" : false, "indexBounds" : { }
}
実行計画の詳細 項目 例 説明
cursor BasicCursor カーソルタイプ (BasicCursor or BtreeCursor)
nscanned 14 スキャンされたインデックスまたはオブジェクトの数
nscannedObjects 14 スキャンされたオブジェクトの数
n 14 処理結果数
millis 9 処理にかかった時間(ms)
nYields 0
nChunkSkips 0 Chunkマイグレーション中にスキップしたドキュメント数
isMultiKey false
indexOnly false
indexBounds
scanAndOrder
allPlans
実行計画例(インデックス未使用) > db.logs.find({uid:"a10Sty5jqqiyxxxx2"}).explain(); {
"cursor" : "BasicCursor", "nscanned" : 2669511, "nscannedObjects" : 2669511, "n" : 3, "millis" : 6100, "nYields" : 0, "nChunkSkips" : 0, "isMultiKey" : false, "indexOnly" : false, "indexBounds" : { }
}
・インデックスが使えていない ・クエリの実行に6100ms ・3件の結果を取得するのに266万オブジェクトスキャン
実行計画例(インデックス使用) > db.logs.find({uid:"b3iVxMGIEc3xxxxY"}).explain(); { "cursor" : "BtreeCursor uid_1", "nscanned" : 3, "nscannedObjects" : 3, "n" : 3, "millis" : 6, "nYields" : 0, "nChunkSkips" : 0, "isMultiKey" : false, "indexOnly" : false, "indexBounds" : { "uid" : [ [ "b3iVxMGIEc3xxxxY", "b3iVxMGIEc3xxxxY" ] ] } }
・”uid_1”のインデックスを使用 ・クエリの実行に6ms ・3件の結果を取得するのに3オブジェクトのスキャン
3.スロークエリの特定
スロークエリを特定するには? ! 大きく分けて、以下の3つの方法がある。
! slowmsオプションをつけて起動する。
! プロファイルレベルを設定する。 ! db.setProfilingLevel(level,slowms)
! 動作中のクエリを確認する。
slowmsオプション ! mongod起動時にslowmsオプションを指定することで、指定したms以上の遅いクエリをファイルに出力することができる。
Sat May 14 02:25:53 [conn16] query logdb.$cmd ntoreturn:1 command: { count: "logs", query: { path: "/index.html" }, fields: {} } reslen:64 3116ms
mongod --logpath=/var/log/mongodb/mongodb.log --logappend –slowms 500
mongod起動
ログファイル
setProfilingLevel プロファイリングを有効にするには、setProfilingLevel
(level,slowms)を設定します。
> db.setProfilingLevel(1,500); { "was" : 1, "slowms" : 100, "ok" : 1 }
レベル 対象クエリ
0 対象なし
1 指定時間より遅いクエリのみ(デフォルト100ms)
2 全てのクエリ
setProfilingLevel 結果はsystem.profileコレクションにて確認ができます。
> db.system.profile.find(); { "ts" : ISODate("2011-05-01T03:40:04.462Z"), "info" : "query logdb.logs reslen:192 nscanned:2669511 \nquery: { uid: \"b2kwyYCXn3b5Scpr\" } nreturned:1 6686ms", "millis" : 6686 }
項目 説明
ts タイムスタンプ
millis 実行にかかった時間
info
query
query query詳細
nscanned スキャンしたオブジェクト数
reslen クエリの結果のバイト数
nreturned クエリの結果のオブジェクト数
動作中のクエリの参照
項目 説明
opid オペレーションID(KillOpで指定する)
op 操作の種類(query、update等)
ns 実行されているネームスペース(データベース+コレクション)
query クエリの中身
lockType ロックタイプ
waitingForLock ロック待ち
client クライアント情報
desc コネクションタイプ
db.currentOpで現在動作中のクエリの参照が可能
動作中のクエリの参照 > db.currentOp(); {
"inprog" : [ { "opid" : 272, "active" : true, "lockType" : "read", "waitingForLock" : false, "secs_running" : 20, "op" : "query", "ns" : "logdb.logs", "query" : { "count" : "logs", "query" : { "month" : "2010/02" }, "fields" : { } }, "client" : "127.0.0.1:59394", "desc" : "conn" }]}
スロークエリ特定方法まとめ 方法 メリット デメリット
slowms ・ファイルに出力されるため、後から確認が可能。
・起動時に指定する必要がある。 ・適切な指定を行わないと、ログファイルサイズが大きくなる&パフォーマンスに影響がある。
db.setProfilingLevel ・collectionに出力されるため、扱いが簡単。 ・簡単にレベルや閾値を変更可能
・system.profileは、capped collectionなので、古い情報は削除される。 ・適切な指定を行わないと、パフォーマンスに影響がある。
db.currentOp ・クエリの実行完了まで待つ必要がない。 ・現在実行されているクエリの情報を取得できる。
・実行完了後の特定は不可。
※ これらはスロークエリの特定には有用だが、少しだけ遅くて、実行回数が多いクエリの特定には向かない。 (MongoDBにはその手の問題を特定できる術がまだない?)
4.チューニングTips
複合インデックス ! 複合インデックスのキーの順序は重要です。
• db.c.find( {x:10,y:20} ), using index {x:1,y:1} • db.c.find( {x:10,y:20} ), using index {x:1,y:-1} • db.c.find( {x:{$in:[10,20]},y:20} ), using index {x:1,y:1} • db.c.find().sort( {x:1,y:1} ), using index {x:1,y:1} • db.c.find().sort( {x:-1,y:1} ), using index {x:1,y:-1} • db.c.find( {x:10} ).sort( {y:1} ), using index {x: 1,y:1}
hint ! hintを使うことで、特定のインデックスを使わせることができる。
! 複数のインデックスが張られているコレクションに対し、複数のフィールドを条件にクエリを実行する場合に使用する。
hint > db.logs.find({uid:"f2RNwtj34m3xxxxB" , path : "/index.html"}).hint({uid:1}).explain(); {
"cursor" : "BtreeCursor uid_1", "nscanned" : 95, "nscannedObjects" : 95, "n" : 35, "millis" : 3, "nYields" : 0, "nChunkSkips" : 0, "isMultiKey" : false, "indexOnly" : false, "indexBounds" : { "uid" : [ [ "f2RNwtj34m3xxxxB", "f2RNwtj34m3xxxxB" ] ] }
}
hint > db.logs.find({uid:"f2RNwtj34m3xxxxB" , path : "/index.html"}).hint({uid:1 , path:1}).explain(); {
"cursor" : "BtreeCursor uid_1_path_1", "nscanned" : 35, "nscannedObjects" : 35, "n" : 35, "millis" : 0, "nYields" : 0, "nChunkSkips" : 0, "isMultiKey" : false, "indexOnly" : false, "indexBounds" : { "uid" : [ [ "f2RNwtj34m3xxxxB", "f2RNwtj34m3xxxxB" ] ],
(略) }
}
mongostat mongostatで統計情報を確認することができます。
[matsuou1@testsvr ̃]$ mongostat connected to: 127.0.0.1 insert query update delete getmore command flushes mapped vsize res faults locked % idx miss % qr¦qw ar¦aw netIn netOut conn repl time 0 0 0 0 0 1 0 2.16g 2.39g 397m 0 0 0 0¦0 0¦0 62b 1k 2 M 03:16:44 0 0 0 0 0 1 0 2.16g 2.39g 397m 0 0 0 0¦0 0¦0 62b 1k 2 M 03:16:45 0 0 0 0 0 1 0 2.16g 2.39g 397m 0 0 0 0¦0 0¦0 62b 1k 2 M 03:16:46 0 0 0 0 0 1 0 2.16g 2.39g 397m 0 0 0 0¦0 0¦0 62b 1k 2 M 03:16:47 0 1 0 0 0 1 0 2.16g 2.39g 396m 203 0 0 0¦0 1¦0 62b 1k 2 M 03:16:48 0 0 0 0 0 1 0 2.16g 2.39g 396m 323 0 0 0¦0 1¦0 62b 1k 2 M 03:16:49 0 0 0 0 0 1 0 2.16g 2.39g 397m 252 0 0 0¦1 1¦0 62b 1k 2 M 03:16:50 0 0 0 0 0 1 0 2.16g 2.39g 397m 232 0 0 0¦0 1¦0 62b 1k 2 M 03:16:51 0 0 0 0 0 1 0 2.16g 2.39g 399m 320 0 0 0¦0 1¦0 62b 1k 2 M 03:16:52 0 0 0 0 0 1 0 2.16g 2.46g 405m 261 0 0 0¦0 1¦0 62b 1k 2 M 03:16:53
mongostat詳細 項目 説明
inserts insert数/秒
query query数/秒
update update数/秒
delete delete数/秒
getmore getmore数/秒
command command数/秒
flushes fsync flush数/秒
mapped Mapされだデータ量(MB)
visze 仮想プロセスのサイズ(MB)
res プロセスのサイズ(MB)
faults ページフォルト回数/秒
locked 書き込みロック待ちのパーセンテージ
Idx miss インデックスが使用されなかったパーセンテージ(サンプル値)
qr|qw 待ちクライアント数(read | write)
ar|aw アクティブクライアント(read | write)
netIn Inネットワークトラフィック(bits)
netOut Outネットワークトラフィック(bits)
conn コネクション数
collectionのインデックスの確認 ! getIndexKeys()で、インデックスを持った全てのフィールドを取得できます。
> db.logs.getIndexKeys(); [ { "_id" : 1 }, { "uid" : 1 }, { "uid" : 1, "path" : 1 } ]
動作中のクエリの停止 ! db.currentOpでopidを特定し、db.killOp(opid) で処理中のクエリを停止できる。
> db.currentOp(); {
"inprog" : [ { "opid" : 308, "active" : true, "lockType" : "read", "waitingForLock" : false, "secs_running" : 11, "op" : "query", "ns" : "logdb.logs",
(略) } ]
} > db.killOp(308); { "info" : "attempting to kill op" }
まとめ ! 基本はインデックススキャンさせる
! クエリを書いたら、実行計画を確認する習慣をつける ! explain
! スロークエリの特定 ! slowmsオプション
! setProfilingLevel
! db.currentOP
! チューニングTips ! 複合インデックス
! Hint
! mongostat
ご清聴ありがとうございました。