はじめに
この実験では、クエリパフォーマンスを最適化するための MongoDB インデックスの使用の基本を学びます。インデックスは、コレクションのデータの小さく検索しやすい部分を保持する特別なデータ構造であり、MongoDB がコレクション全体をスキャンするよりもはるかに高速にドキュメントを見つけることを可能にします。
まず、インデックスなしでのクエリのパフォーマンスを観察します。次に、単一フィールドインデックスと複合インデックスを作成し、それらがクエリとソートの速度を劇的に向上させる様子を確認します。最後に、インデックスのリスト表示と削除によってインデックスを管理する方法を学びます。この実験の終わりには、MongoDB アプリケーションをより効率的にするためにインデックスを作成および使用する方法についての実践的な理解が得られるでしょう。
インデックスなしでのクエリ
インデックスを作成する前に、インデックスがない場合に MongoDB がどのように動作するかを理解することが重要です。このステップでは、サンプルコレクションを設定し、クエリを実行し、その実行計画を分析して、コレクション全体スキャンのパフォーマンスへの影響を確認します。
まず、MongoDB Shell (mongosh) を開いてデータベースと対話します。このコマンドラインインターフェイスを使用すると、MongoDB インスタンスに対して直接コマンドを実行できます。
mongosh
シェルに入ると、> プロンプトが表示されます。indexlab という新しいデータベースに切り替え、users コレクションにいくつかのサンプルドキュメントを挿入しましょう。データベースまたはコレクションが存在しない場合、MongoDB はそれらを自動的に作成します。
use indexlab
db.users.insertMany([
{ name: "Alice", age: 28, city: "New York" },
{ name: "Bob", age: 35, city: "San Francisco" },
{ name: "Charlie", age: 42, city: "Chicago" },
{ name: "David", age: 25, city: "New York" },
{ name: "Eve", age: 31, city: "San Francisco" }
]);
次に、30 歳より上のすべてのユーザーを見つけましょう。MongoDB がこのクエリをどのように実行するかを確認するために、.explain("executionStats") メソッドを使用します。このメソッドは、クエリの実行計画に関する詳細な統計情報を提供します。
db.users.find({ age: { $gt: 30 } }).explain("executionStats");
出力には、クエリの実行に関する詳細な統計情報が表示されます。winningPlan と executionStats のセクションを探してください。
例(一部抜粋):
{
"queryPlanner": {
"winningPlan": {
"stage": "COLLSCAN",
"filter": { "age": { "$gt": 30 } },
"direction": "forward"
}
},
"executionStats": {
"executionSuccess": true,
"nReturned": 3,
"executionTimeMillis": 0,
"totalKeysExamined": 0,
"totalDocsExamined": 5
}
}
ここで重要な情報は stage: "COLLSCAN" と totalDocsExamined: 5 です。
COLLSCANは "Collection Scan" の略です。これは、MongoDB がクエリに一致するドキュメントを見つけるために、コレクション内のすべてのドキュメントを検査する必要があったことを意味します。totalDocsExamined: 5は、コレクション内のすべての 5 つのドキュメントがスキャンされたことを確認します。
これは小さなコレクションでは高速ですが、数百万のドキュメントに対するコレクションスキャンは非常に遅くなります。次のステップでは、インデックスを追加してこれを修正します。
単一フィールドインデックスの作成と使用
コレクションスキャンの非効率性を確認したところで、インデックスを作成してパフォーマンスを向上させましょう。age フィールドにインデックスを作成することで、MongoDB はコレクション全体をスキャンすることなく、関連するドキュメントを迅速に見つけることができるようになります。
前のステップから引き続き mongosh シェルを使用してください。
age フィールドに昇順でインデックスを作成します。1 は昇順インデックスを指定し、-1 は降順を指定します。
db.users.createIndex({ age: 1 });
MongoDB はインデックスが正常に作成されたことを確認します。このインデックスのデフォルト名は age_1 になります。
次に、前のステップと同じクエリを実行し、その実行計画を確認します。
db.users.find({ age: { $gt: 30 } }).explain("executionStats");
例(一部抜粋):
{
"queryPlanner": {
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": { "age": 1 },
"indexName": "age_1"
}
}
},
"executionStats": {
"executionSuccess": true,
"nReturned": 3,
"executionTimeMillis": 0,
"totalKeysExamined": 3,
"totalDocsExamined": 3
}
}
実行計画における重要な変更点に注目してください。
stageがIXSCAN("Index Scan" の略)になりました。これは、MongoDB がage_1インデックスを使用して一致するドキュメントを見つけたことを示しています。totalKeysExaminedとtotalDocsExaminedが 5 ではなく 3 になりました。MongoDB は、インデックスを通じてクエリに一致した 3 つのドキュメントのみを確認し、残りの 2 つは無視しました。これがパフォーマンス向上の源です。
複合インデックスを使用したソート
インデックスはクエリを高速化するためだけではなく、効率的なソートにも不可欠です。インデックスが作成されていないフィールドでソートする場合、MongoDB はメモリ内でソートを実行する必要があり、これは遅く、かなりの RAM を消費する可能性があります。複数のフィールドを含む複合インデックスは、それらのフィールドでフィルタリングおよびソートを行うクエリを最適化できます。
city(昇順)と age(降順)フィールドに複合インデックスを作成しましょう。インデックス内のフィールドの順序は、それがどのように使用できるかにとって重要です。
db.users.createIndex({ city: 1, age: -1 });
次に、都市でソートし、次に年齢でソートするクエリを実行します。インデックスがソートに使用されていることを確認するために、再度 .explain() を使用します。
db.users.find().sort({ city: 1, age: -1 }).explain("executionStats");
例(一部抜粋):
{
"queryPlanner": {
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": { "city": 1, "age": -1 },
"indexName": "city_1_age_-1"
}
}
}
}
IXSCAN ステージは、MongoDB が新しい city_1_age_-1 インデックスを使用したことを示しています。データはインデックス内でソート基準に従って既に並べ替えられているため、MongoDB はメモリ内で別途コストのかかるソートステップを実行する必要がありません。
実際のソート結果を確認するには、.explain() なしでクエリを実行します。
db.users.find().sort({ city: 1, age: -1 });
出力:
[
{ _id: ObjectId("..."), name: 'Charlie', age: 42, city: 'Chicago' },
{ _id: ObjectId("..."), name: 'Alice', age: 28, city: 'New York' },
{ _id: ObjectId("..."), name: 'David', age: 25, city: 'New York' },
{ _id: ObjectId("..."), name: 'Bob', age: 35, city: 'San Francisco' },
{ _id: ObjectId("..."), name: 'Eve', age: 31, city: 'San Francisco' }
]
ドキュメントは、まず city でアルファベット順に、次に各都市内で age で最も古いものから最も若いものへと正しくソートされており、複合インデックスの定義と一致しています。
インデックスの管理と削除
インデックスは読み取りパフォーマンスを向上させますが、無料ではありません。ストレージスペースを消費し、書き込み操作(挿入、更新、削除)にわずかなオーバーヘッドを追加します。したがって、使用されなくなったインデックスを定期的にレビューして削除することは良い習慣です。
まず、getIndexes() メソッドを使用して、コレクション上のすべてのインデックスを一覧表示できます。
db.users.getIndexes();
出力:
[
{ "v": 2, "key": { "_id": 1 }, "name": "_id_" },
{ "v": 2, "key": { "age": 1 }, "name": "age_1" },
{ "v": 2, "key": { "city": 1, "age": -1 }, "name": "city_1_age_-1" }
]
これは、すべてのコレクションに自動的に作成される _id フィールドのデフォルトインデックスと、作成した 2 つのインデックスを示しています。
複合インデックス city_1_age_-1 が不要になったと仮定します。インデックス名を引数として渡す dropIndex() メソッドを使用して削除できます。
db.users.dropIndex("city_1_age_-1");
MongoDB は、ドロップ操作の前に存在したインデックスの数を示すオブジェクトを返します。
{ "nIndexesWas": 3, "ok": 1 }
次に、インデックスを再度一覧表示して、インデックスが削除されたことを確認します。
db.users.getIndexes();
出力:
[
{ "v": 2, "key": { "_id": 1 }, "name": "_id_" },
{ "v": 2, "key": { "age": 1 }, "name": "age_1" }
]
ご覧のとおり、city_1_age_-1 インデックスはなくなりました。適切なインデックス管理は、健全でパフォーマンスの高いデータベースを維持するための重要な部分です。
MongoDB シェルを終了するには、exit と入力するか、Ctrl+D を押します。
exit;
まとめ
この実験では、MongoDB インデックスを使用するための基本的なテクニックを学びました。まず、インデックスのないクエリで COLLSCAN を観察し、そのパフォーマンスの限界を理解しました。次に、単一フィールドインデックスを作成し、クエリプランをはるかに効率的な IXSCAN に変更しました。
さらに、複合インデックスを調査し、それらがソート操作を最適化し、コストのかかるインメモリソートを回避するためにどのように使用できるかを確認しました。最後に、getIndexes() でインデックスを一覧表示し、dropIndex() で未使用のインデックスを削除することによって、インデックスを管理する方法を学びました。これらのスキルは、MongoDB で高速かつスケーラブルなアプリケーションを構築するための基本です。

