はじめに
百科事典と同様に、データベースはアクセス可能な豊富な情報の宝庫です。百科事典で特定の情報を見つけるには、探しているものが見つかるまで全ページを丹念に調べる必要があります。この非効率性こそ、百科事典にインデックスがある理由であり、インデックスは探している情報が載っている正確なページを教えてくれます。
データベースのインデックスも同様に、情報をより効率的に見つけるための適切な場所を示してくれます。MongoDBでは、「本全体を検索する」インデックスのないクエリは、コレクションスキャンと呼ばれます。
インデックスは、探しているものを見つけるためにデータベース全体をスキャンする必要がないように、データにアクセスするためのショートカットと考えることができます。この記事では、MongoDBのインデックスを紹介し、いつ使用すべきか、そしてそれらを管理する方法について説明します。
MongoDBを使用している場合は、PrismaのMongoDBコネクタをチェックしてみてください!Prisma Clientを使用して、本番環境のMongoDBデータベースを自信を持って管理できます。
MongoDBとPrismaの使用を開始するには、ゼロから始めるガイド、または既存のプロジェクトに追加する方法をご覧ください。
いつインデックスを使用すべきか
百科事典のアナロジーを続けると、本のすべての単語にインデックス行を設けることを考えるかもしれません。インデックスを使用する方が常に速いのであれば、これは有益に思えます。しかし、想像できるように、インデックス内の単語の行数が増えるほど、本は大きくなります。ある時点で、すべての単語のインデックス作成に対応するために必要な本のサイズは非効率になります。「the」や「because」など、検索するのに「カバ」ほど有用でない単語がたくさんあります。
これは、MongoDBおよび一般的なデータベースのインデックスと同様です。確かに、クエリが使用する可能性のあるすべてのデータにインデックスがある方が速いですが、インデックス作成が必要ないデータも確かに存在します。本のサイズと同様に、データベースにインデックスを過剰に追加すると、スペースも占有し、適切に管理しないとデータベースの書き込み操作に悪影響を及ぼします。
インデックスは、クエリの選択基準として頻繁に使用される特定のデータへのアクセスを最適化するための非常に便利な方法です。いつインデックスを使用するかを知ることは重要であり、頻繁にクエリされるデータベースフィールドにインデックスを追加することで、データベースのサイズと書き込み効率に悪影響を与えることなく、読み取りを効率的に保つことができます。
インデックスの作成方法
インデックスが何であるか、そしていつ使用すべきかを理解できたので、インデックスを作成する方法について説明できます。
インデックス作成の恩恵を受ける可能性のあるフィールドを特定したら、MongoDBのcreateIndex()
メソッドを使用します。基本的な構文は次のとおりです
db.COLLECTION_NAME.createIndex( { "FIELD_NAME": 1 } )
FIELD_NAME
は、インデックスを作成するフィールドの名前であり、1
は昇順を指定します。
メソッドの使用例は次のようになります
db.mycoll.createIndex( { "country": 1 } )
createIndex()
メソッドを使用して、次のようなカンマ区切りのリストを作成することにより、複数のフィールドにインデックスを作成することもできます
db.COLLECTION_NAME.createIndex( { "FIELD_NAME_1": 1, "FIELD_NAME_2": -1 } )
インデックスの表示方法
インデックスの作成を開始したら、データベースインスタンスに存在するインデックスを確認したい場合があります。MongoDBでは、getIndexes()
メソッドを使用して、コレクション内のすべてのインデックスの説明を返すことができます。
コレクションのすべてのインデックスを表示するための基本的な構文は次のとおりです
db.COLLECTION_NAME.getIndexes()
前にインデックスを作成した例を使用すると、以下にメソッドとそれが返すものが表示されます。
db.mycoll.getIndexes()
戻り値:
[{"v" : 2,"key" : {"country" : 1},"name" : "country"}]
インデックス情報には、インデックスの作成に使用されたキーとオプションが含まれます。
MongoDBでフルテキストインデックスを使用することに興味がある場合は、PrismaにはfullTextIndex
プレビュー機能があり、これにより、型安全性と検証エラーからの保護のために、フルテキストインデックスをPrismaスキーマに簡単に移行できます。
インデックスのパフォーマンスを理解する方法
これで、コレクションにインデックスを作成して確認する機能ができたので、インデックスが期待どおりに機能しているかどうかを確認する必要があります。
例を始めるにあたり、約50.3kのドキュメントを持つsample_mflix
データベースとcomments
コレクションを使用します。これは、MongoDB Universityが提供する映画やテレビのコメントのデータストアをシミュレートしたサンプルコレクションです。
インデックスのパフォーマンスを理解するために、最初にインデックスがないクエリを実行します。次のクエリは、コレクション内のRamsay Bolton
によって作成されたコメントである273個のドキュメントすべてを返します
db.comments.find( { "name" : "Ramsay Bolton" } )
ここで、MongoDBの説明プランをクエリに追加すると、このクエリのパフォーマンスが表示されます。
db.comments.find( { "name" : "Ramsay Bolton" } ).explain("executionStats")
これにより、次の結果が得られます
{queryPlanner: {plannerVersion: 1,namespace: 'sample_mflix.comments',indexFilterSet: false,parsedQuery: { name: { '$eq': 'Ramsay Bolton' } },winningPlan: {stage: 'COLLSCAN',filter: { name: { '$eq': 'Ramsay Bolton' } },direction: 'forward'},rejectedPlans: []},executionStats: {executionSuccess: true,nReturned: 273,executionTimeMillis: 23,totalKeysExamined: 0,totalDocsExamined: 50303,executionStages: {stage: 'COLLSCAN',filter: { name: { '$eq': 'Ramsay Bolton' } },nReturned: 273,executionTimeMillisEstimate: 6,works: 50305,advanced: 273,needTime: 50031,needYield: 0,saveState: 50,restoreState: 50,isEOF: 1,direction: 'forward',docsExamined: 50303}}}
この出力で注目すべきいくつかの重要な結果があります。まず、winningPlan
で、このクエリのstage
がCOLLSCAN
であることがわかります。これは、totalDocsExamined
が50,303、executionTimeMillis
が23ミリ秒で、このクエリを完了するためにコレクションスキャンが発生したことを意味します。クエリはコレクション内のすべてのドキュメントを調べる必要があり、nReturned
がわずか273ドキュメントであっても23ミリ秒かかりました。23ミリ秒は大したことないように聞こえるかもしれませんが、100万件のドキュメントを格納するコレクションでは、これははるかに長くなる可能性があります。
name
に対するクエリが、このコレクションにアクセスするアプリケーションにとって反復的なパターンになる場合、このフィールドにインデックスを作成することをお勧めします。そうするには、次のように記述します
db.comments.createIndex( {"name":1} )
以前と同じ説明プランで同じクエリを実行すると
db.comments.find( { "name" : "Ramsay Bolton" } ).explain("executionStats")
{queryPlanner: {plannerVersion: 1,namespace: 'sample_mflix.comments',indexFilterSet: false,parsedQuery: { name: { '$eq': 'Ramsay Bolton' } },winningPlan: {stage: 'FETCH',inputStage: {stage: 'IXSCAN',keyPattern: { name: 1 },indexName: 'name_1',isMultiKey: false,multiKeyPaths: { name: [] },isUnique: false,isSparse: false,isPartial: false,indexVersion: 2,direction: 'forward',indexBounds: { name: [ '["Ramsay Bolton", "Ramsay Bolton"]' ] }}},rejectedPlans: []},executionStats: {executionSuccess: true,nReturned: 273,executionTimeMillis: 0,totalKeysExamined: 273,totalDocsExamined: 273,executionStages: {stage: 'FETCH',nReturned: 273,executionTimeMillisEstimate: 0,works: 274,advanced: 273,needTime: 0,needYield: 0,saveState: 0,restoreState: 0,isEOF: 1,docsExamined: 273,alreadyHasObj: 0,inputStage: {stage: 'IXSCAN',nReturned: 273,executionTimeMillisEstimate: 0,works: 274,advanced: 273,needTime: 0,needYield: 0,saveState: 0,restoreState: 0,isEOF: 1,keyPattern: { name: 1 },indexName: 'name_1',isMultiKey: false,multiKeyPaths: { name: [] },isUnique: false,isSparse: false,isPartial: false,indexVersion: 2,direction: 'forward',indexBounds: { name: [ '["Ramsay Bolton", "Ramsay Bolton"]' ] },keysExamined: 273,seeks: 1,dupsTested: 0,dupsDropped: 0}}}}
インデックスなしのクエリと比較すると、winningPlan.inputstage
がIXSCAN
になり、インデックスが使用されたことがわかります。
さらに、totalDocsExamined
が、name
が"Ramsay Bolton"
である273個のドキュメントのみになり、50,303個のドキュメント全体ではないことがわかります。この効率の向上は、特にexecutionTimeMillis
が合計0ミリ秒になったことで確認できます。name
の新しいインデックスは、クエリが探しているデータを見つけるためにどこを探すべきかを正確に伝えました。
最も重要なクエリの説明プランを分析すると、インデックスのパフォーマンスが表示されるか、アプリケーションの効率を高めるためにインデックスを作成する必要がある場合が強調表示されます。
インデックスの削除方法
説明プランはインデックスの必要性を示す場合がありますが、逆のこともあります。たとえば、インデックスが不要になったり、パフォーマンスがそれほど向上しなかったりする場合は、スペースを確保したり、書き込みパフォーマンスを向上させたりするために、そのインデックスを削除することが最善の策となる場合があります。
コレクションのインデックスを削除するには、dropIndexes()
メソッドを使用して、基本的な構文は次のとおりです
db.COLLECTION_NAME.dropIndex( { "FIELD_NAME": 1 } )
以前のcountry
インデックスの例を削除したい場合は、次のように記述します
db.mycoll.dropIndex( { "country":1 } )
結論
このガイドでは、データベースを効率的にクエリすることで、アプリケーションのユーザーエクスペリエンスがどのように向上するかについて説明しました。さらに、分析やその他の社内業務にデータを使用する人は、より高速なパフォーマンスとデータベースの操作の容易さを実現できます。インデックスを作成する方法とインデックスの仕組みを知ることは、クエリ効率を達成するための鍵となります。
MongoDBでのインデックスの作成、分析、および削除の基本について説明しました。これらのインデックスの基礎知識は、MongoDBでのより高度なインデックス作成に進むための適切な基盤を提供します。
MongoDBを使用している場合は、PrismaのMongoDBコネクタをチェックしてみてください!Prisma Clientを使用して、本番環境のMongoDBデータベースを自信を持って管理できます。
MongoDBとPrismaの使用を開始するには、ゼロから始めるガイド、または既存のプロジェクトに追加する方法をご覧ください。
FAQ
2d
インデックスは、2次元平面上の点として格納されたデータに使用します。これは、古いMongoDBバージョンでのレガシー座標ペアを対象としています。
2d
インデックスは、2つのフィールドを参照できます。最初のフィールドはロケーションフィールドである必要があります。2d
複合インデックスは、最初にロケーションフィールドで選択し、追加の基準でそれらの結果をフィルタリングするクエリを構築します。
小規模なコレクションでも大規模なコレクションでも、createIndex()
メソッドを使用します。
大規模なコレクションでインデックスを作成する際に問題が発生している場合は、水平スケーリングを検討して、より管理しやすくすることをお勧めします。
MongoDBは、ローリングインデックスビルドアプローチも推奨しています。
MongoDBで埋め込みオブジェクトフィールドにインデックスを作成するには、ドット表記を使用できます。
たとえば、読んだ本を追跡するアプリがある場合、次のような構造のユーザーごとのコレクションがあるかもしれません
db.users.insertOne({"first_name": "Alex","last_name": "Emerich","books": {"first_book": {"title": "Flights","author": "Olga Tokarczuk"},"second book": {"title": "The Master and Margarita","author": "Mikhail Bulgakov"},"total": 2}})
埋め込みtotal
フィールドにインデックスを作成するには、次のステートメントを記述します
db.users.createIndex( {"books.total": 1 } )
複合インデックスは、コレクションのドキュメント内の複数のフィールドへの参照を保持する単一のインデックス構造です。
複合インデックスを作成するための基本的な構文は次のとおりです
db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )
ユニークインデックスは、テーブルの2つの行が、インデックス付きの列または列に重複した値を持たないようにします。MongoDBの場合、ドキュメントのフィールドまたはフィールドに重複した値があることです。
非ユニークインデックスはこの制限を課しません。