1. ホーム
  2. mongodb

[解決済み] 500万レコードを超えるMongoDBクエリのパフォーマンス

2023-06-18 14:58:58

質問

最近、主要なコレクションの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() の出力が必要です。残念ながら、それさえもその特定のクエリについてのみ問題を解決することになるので、ここにどのようにアプローチするかの戦略を示します。

  1. RAM の不足と過剰なページングが原因でないことを確認する
  2. DB プロファイラーを有効にする ( db.setProfilingLevel(1, timeout) ここで timeout はクエリやコマンドにかかるミリ秒数の閾値で、これより遅いものはログに記録されます)
  3. で遅いクエリを検査します。 db.system.profile で遅いクエリを調べ、そのクエリを手動で explain()
  4. の中で遅い操作を特定してみてください。 explain() のような出力で scanAndOrder または大きな nscanned など。
  5. クエリの選択性、およびインデックスを使用してクエリを改善することが可能かどうかについての理由 全く . そうでない場合、エンドユーザーに対してフィルタ設定を無効にするか、操作が遅くなる可能性があることを警告するダイアログを表示することを検討します。

重要な問題は、明らかにユーザーが自由にフィルターを組み合わせることを許可していることです。インデックスの交差がなければ、必要なインデックスの数は劇的に膨れ上がります。

また、すべての可能なクエリに対してやみくもにインデックスを投げることは、非常に悪い戦略です。クエリを構造化し、インデックスされたフィールドに十分な 選択性 .

を持つすべてのユーザーを対象とするクエリがあるとします。 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を使ったこの件に関しては、現時点ではあまり良い解決策がないようです。