[解決済み] 500万レコードを超えるMongoDBクエリのパフォーマンス
質問
最近、主要なコレクションの1つで200万レコードを達成し、そのコレクションで大きなパフォーマンスの問題に悩まされ始めています。
コレクション内のドキュメントには、UI を使用してフィルタリングできる約 8 つのフィールドがあり、結果は、レコードが処理されたタイムスタンプ フィールドでソートされることになっています。
私は、フィルタリングされたフィールドとタイムスタンプを持ついくつかの複合インデックスを追加しました。 例えば
db.events.ensureIndex({somefield: 1, timestamp:-1})
また、一度に複数のフィルタを使用するためのインデックスをいくつか追加し、より良いパフォーマンスが得られることを期待しています。しかし、いくつかのフィルタはまだ実行に非常に長い時間がかかります。
クエリが私が作成したインデックスを使用することを説明することを確認しましたが、性能はまだ十分ではありません。
シャーディングが今行くべき道なのかと思っていましたが、そのコレクションに1日あたり約100万件の新しいレコードがすぐに入るようになるので、うまくスケールするかどうかわかりません......。
EDIT: クエリの例です。
> db.audit.find({'userAgent.deviceType': 'MOBILE', 'user.userName': {$in: ['[email protected]']}}).sort({timestamp: -1}).limit(25).explain()
{
"cursor" : "BtreeCursor user.userName_1_timestamp_-1",
"isMultiKey" : false,
"n" : 0,
"nscannedObjects" : 30060,
"nscanned" : 30060,
"nscannedObjectsAllPlans" : 120241,
"nscannedAllPlans" : 120241,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 1,
"nChunkSkips" : 0,
"millis" : 26495,
"indexBounds" : {
"user.userName" : [
[
"[email protected]",
"[email protected]"
]
],
"timestamp" : [
[
{
"$maxElement" : 1
},
{
"$minElement" : 1
}
]
]
},
"server" : "yarin:27017"
}
deviceTypeは私のコレクションでは2つの値しかないことに注意してください。
どのように解決するのですか?
これは干し草の中の針を探すようなものです。の出力が必要です。
explain()
の出力が必要です。残念ながら、それさえもその特定のクエリについてのみ問題を解決することになるので、ここにどのようにアプローチするかの戦略を示します。
- RAM の不足と過剰なページングが原因でないことを確認する
-
DB プロファイラーを有効にする (
db.setProfilingLevel(1, timeout)
ここでtimeout
はクエリやコマンドにかかるミリ秒数の閾値で、これより遅いものはログに記録されます) -
で遅いクエリを検査します。
db.system.profile
で遅いクエリを調べ、そのクエリを手動でexplain()
-
の中で遅い操作を特定してみてください。
explain()
のような出力でscanAndOrder
または大きなnscanned
など。 - クエリの選択性、およびインデックスを使用してクエリを改善することが可能かどうかについての理由 全く . そうでない場合、エンドユーザーに対してフィルタ設定を無効にするか、操作が遅くなる可能性があることを警告するダイアログを表示することを検討します。
重要な問題は、明らかにユーザーが自由にフィルターを組み合わせることを許可していることです。インデックスの交差がなければ、必要なインデックスの数は劇的に膨れ上がります。
また、すべての可能なクエリに対してやみくもにインデックスを投げることは、非常に悪い戦略です。クエリを構造化し、インデックスされたフィールドに十分な 選択性 .
を持つすべてのユーザーを対象とするクエリがあるとします。
status
"active" を持つすべてのユーザーと、他のいくつかの基準に対するクエリがあるとします。しかし、500万人のユーザーのうち、300万人がアクティブで、200万人がそうでないため、500万件以上のエントリーのうち、異なる値は2つだけです。このような索引は通常役に立ちません。まず他の条件で検索し、その結果をスキャンするのがよいでしょう。平均して、100文書を返す場合、167文書をスキャンする必要があり、それほどパフォーマンスは落ちない。しかし、そんなに単純な話ではない。もし第一の基準が
joined_at
の日付であり、時間の経過とともにユーザーが使用を中止する可能性が高い場合、最終的にスキャンしなければならないのは
数千
のドキュメントをスキャンしなければならないかもしれません。
つまり、最適化はデータ(だけでなく、その 構造 だけでなく データ自体も )、その内部相関、そしてあなたの クエリパターン .
なぜなら、インデックスを持つことは素晴らしいことですが、結果をスキャンする(あるいは単に返す)には、ディスクからランダムに多くのデータをフェッチする必要があり、それには多くの時間がかかるからです。
これを制御する最善の方法は、異なるクエリタイプの数を制限し、選択性の低い情報に対するクエリを禁止し、古いデータへのランダムアクセスを防止するようにすることです。
もし他のすべてが失敗し、フィルターに本当に多くの柔軟性が必要な場合は、インデックスの交差をサポートする別の検索DBを検討する価値があるかもしれません。
$in
. しかし、それはそれなりの危険をはらんでいる。
-- EDIT --
投稿された説明は、低選択性フィールドのスキャンの問題点を示す美しい例です。どうやら、"[email protected]" のドキュメントが大量にあるようです。これらのドキュメントを見つけ、タイムスタンプの降順でソートすることは、高選択性インデックスによってサポートされているため、かなり高速です。残念ながら、デバイスの種類が 2 つしかないため、mongo は 'mobile' に一致する最初のドキュメントを見つけるために 30060 個のドキュメントをスキャンする必要があります。
これはある種の Web トラッキングであり、ユーザーの使用パターンがクエリを遅くしていると推測されます (彼が日常的にモバイルと Web を切り替えていたら、クエリは高速になります)。
この特定のクエリを高速化するには、デバイスの種類を含む複合インデックスを使用することができます。
a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})
または
b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})
残念ながら、これは
find({"username" : "foo"}).sort({"timestamp" : -1});
はもう同じインデックスを使うことはできません。
というように、記述されているように、インデックスの数は非常に早く増えていきます。
mongodbを使ったこの件に関しては、現時点ではあまり良い解決策がないようです。
関連
-
[解決済み] E: mongodb-org パッケージが見つかりません。
-
[解決済み] MongoDB の重複レコードを検索する
-
[解決済み] Mongo Restart Error -- /var/run/mongodb/mongod.pid が存在する
-
[解決済み] 配列フィールドが空でない MongoDB レコードを検索する
-
[解決済み] MongoDBの命名規則とは何ですか?
-
[解決済み] MongoDBとその逆の上にCouchDBを使用するとき
-
[解決済み] mongodbの最後のN個のレコードを取得する方法は?
-
[解決済み】MongoDB: 1つのフィールドですべてのドキュメントを更新する
-
[解決済み] 値が NULL ではないマングースクエリ
-
[解決済み] MongoDBのマルチテナントデータベースへの推奨されるアプローチとは?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】MongoError: 最初の接続でサーバー [localhost:27017] への接続に失敗しました。
-
[解決済み】SocketException: アドレスはすでに使用中です。
-
[解決済み】MongoDbがコード100でシャットダウンする【終了しました
-
[解決済み] MongoDB: 大文字小文字を区別しないクエリを作ることはできますか?
-
[解決済み] or' 条件を含む MongoDB クエリ
-
[解決済み】シェルスクリプトでmongoのコマンドを実行するには?
-
[解決済み] 日付に基づいたクエリを返す
-
[解決済み] なぜMongoDBではインデックスの方向が重要なのか?
-
[解決済み] MongoDB: フィールドがNULLまたは未設定のレコードを検索するには?
-
[解決済み] Mac OS のターミナルからリモートの Mongo サーバーに接続するにはどうしたらいいですか?