問題の発生MongoDBで$text演算子を使用して全文検索を実行しようとした際に、結果の代わりに赤文字のエラーが表示されることがあります。通常、以下のようなエラーメッセージが表示されます。
MongoServerError: text index required for $text query
私は最近、ドキュメントサイトの検索バーを構築しているときにこの問題に遭遇しました。タイトルと本文の両方からキーワードを見つけるために、次のようなクエリを使用しました。
db.articles.find({ $text: { $search: "パフォーマンスの最適化" } })
一見正しく動作するように見えますが、MongoDBは全文検索を標準的なフィールド検索とは異なる方法で処理します。{ status: "active" }のようなクエリは、インデックスがなくても(低速ながら)実行可能ですが、$textクエリはテキストインデックスを定義しない限り、物理的に実行することができません。
エラーの原因内部では、$text演算子が複雑な処理を行っています。ステミング(「running」を「run」に変換するなど)を実行し、「the」や「and」などのストップワードを除去し、文字列をトークン化します。これらを効率的に行うために、MongoDBは専用のデータ構造を必要とします。データベースに対して、どのフィールドをこの構造に含めるかを指定していない場合、参照すべきマップが存在しないため、処理を断念してエラーをスローします。
ステップバイステップの解決策### 1. 既存のインデックスを確認するまず、現在どのようなインデックスが存在するかを確認します。MongoDB Shell (mongosh) で以下のコマンドを実行してください。
db.articles.getIndexes()
結果を確認してください。値が"text"となっているキーが見当たらない場合、それが原因です。_id_やcategoryに対するインデックスは表示されるかもしれませんが、それらはテキスト検索には利用できません。
2. テキストインデックスを作成する検索対象とするフィールドをMongoDBに明示的に伝える必要があります。titleとcontentの両方のフィールドを検索したい場合は、次のコマンドを実行します。
db.articles.createIndex({
title: "text",
content: "text"
})
MongoDB 4.2以降では、この処理はデフォルトでバックグラウンドで実行されます。データベースをロックすることはありませんが、I/Oを消費します。100万件の小さなドキュメントを含むコレクションの場合、ハードウェアの性能にもよりますが、30秒から60秒ほどかかる場合があります。
3. すべてを検索するワイルドカードプロトタイプを作成中で、すべての文字列フィールドを個別に指定せずに検索したい場合は、ワイルドカード指定子を使用できます。
db.collection.createIndex({ "$**": "text" })
この方法は慎重に使用してください。ワイルドカードによるテキストインデックスは、ドキュメント内のすべての文字列をインデックス化するため、ストレージ使用量が30%以上増加する可能性があります。
Mongoose (Node.js) での修正Mongooseを使用している場合は、シェルで修正するだけでなく、本番環境へのデプロイ時にインデックスが維持されるようSchemaを更新する必要があります。以下はクリーンな設定方法です。
const articleSchema = new mongoose.Schema({
title: String,
content: String,
author: String
});
// スキーマレベルでテキストインデックスを定義
articleSchema.index({ title: 'text', content: 'text' });
const Article = mongoose.model('Article', articleSchema);
注意: Mongooseはアプリの起動時にインデックスを作成します。そのコレクションにすでに別のインデックスが存在する場合、Mongooseは新しいインデックスの作成に静かに失敗することがあります。エラーが解消されない場合は、シェルでdb.articles.dropIndexes()を実行して既存のインデックスを削除し、Nodeサーバーを再起動してください。
「インデックスは1つ」のルールよくある罠は、複数のテキストインデックスを作成しようとすることです。MongoDBでは、1つのコレクションにつき1つのテキストインデックスしか許可されません。次のように2つの別々のコマンドを実行しようとすると、2つ目は失敗します。
db.posts.createIndex({ title: "text" })
db.posts.createIndex({ description: "text" }) // エラー!
両方のフィールドを検索対象にするには、ステップ2で示したように、それらを1つの複合インデックスにまとめる必要があります。
修正の確認方法インデックスを作成した後、クエリが実際にそれを使用しているかを確認します。クエリの末尾に.explain("executionStats")を追加してください。
db.articles.find({ $text: { $search: "データベース" } }).explain("executionStats")
出力結果の中でwinningPlanを確認してください。TEXT_MATCHというステージがあれば、設定は完了です。エラーは消え、検索結果が即座に表示されるはずです。

