エラーの発生シナリオ先日、クライアントのために「近くのコーヒーショップを検索する」機能を構築しました。設計上は完璧でした。GeoJSONオブジェクトを保存し、$near演算子の準備も整っていました。しかし、5万件の場所データを含むコレクションに対して検索をテストした瞬間、データベースから次のようなエラーが返されました:
MongoServerError: planner returned error :: internal error: $near requires a 2d or 2dsphere index
通常の検索では、MongoDBは低速なコレクションスキャンで妥協することもありますが、地理空間クエリは別物です。MongoDBは、利用できる専用のインデックスがない限り、$nearや$nearSphereクエリの実行を拒否します。推測で実行しようとすることさえありません。
なぜMongoDBはインデックスを要求するのか1,000万件のドキュメントにわたって2点間の距離を計算することは、CPUに非常に大きな負荷をかけます。パフォーマンスを保護するため、MongoDBは、平面のユークリッド平面には2dインデックスを、地球のような球体ジオメトリには2dsphereインデックスを必須としています。インデックスがない場合、クエリプランナーはデータベース全体の速度低下を招くリスクを避けるため、即座に実行を停止します。
このエラーが発生している主な原因は以下の通りです:
- 本番環境のデータベースにインデックスが作成されていない。- コレクションが削除・再作成され、インデックスが消失した。-
locationフィールドに対してクエリを実行しているが、実際のインデックスはaddress.locationに貼られている。- MongooseでautoIndexが無効になっており、スキーマ定義がサーバーに反映されていない。## 「今すぐ」の解決策:シェルを使用するすぐにアプリをオンラインに戻す必要がある場合は、MongoDBシェル(mongosh)またはMongoDB Compassを使用して手動でインデックスを作成します。locationという名前のフィールドに保存されているGeoJSONデータの場合、次のコマンドを実行します:
db.places.createIndex({ location: "2dsphere" })
[経度, 緯度]のようなレガシーな座標ペアの場合、2dインデックスが機能します。しかし、地球の曲率を考慮する2dsphereが、現代のウェブマッピングにおける標準的な選択肢です。
恒久的な解決策:コードによるインデックスの自動化手動での修正は緊急時には有効ですが、開発、ステージング、本番環境の同期を保つために、コード内でインデックス作成を処理すべきです。
1. Mongoose (Node.js) を使用する場合Mongooseでは、スキーマ内で直接インデックスを定義します。フィールドの型がGeoJSONオブジェクトとして定義されていることを確認してください。
const placeSchema = new mongoose.Schema({
name: String,
location: {
type: {
type: String,
enum: ['Point'],
required: true
},
coordinates: {
type: [Number],
required: true
}
}
});
// 明示的に2dsphereインデックスを定義
placeSchema.index({ location: "2dsphere" });
const Place = mongoose.model('Place', placeSchema);
プロのヒント: パフォーマンス向上のために接続文字列で autoIndex: false を設定している場合、Mongooseはこれを自動的に構築しません。アプリの起動シーケンス中に await Place.createIndexes() を呼び出す必要があります。
2. PyMongo (Python) を使用する場合Python開発者は、最初のクエリが実行される前にインデックスが存在することを確認できます:
from pymongo import MongoClient, GEOSPHERE
client = MongoClient('mongodb://localhost:27017/')
db = client['store_locator']
collection = db['branches']
# インデックスが存在することを確認します。既に存在する場合は何もしません。
collection.create_index([("location", GEOSPHERE)])
複合インデックスの使用特定の地点の近くにある「寿司レストラン」を探したい場合はどうすればよいでしょうか?高トラフィックのアプリでは、単純な地理空間インデックスだけでは不十分です。複合インデックスが必要になります。これが機能するためには、地理空間フィールドがインデックスの最初のキーである必要があります。
db.places.createIndex({ location: "2dsphere", category: 1 })
この構造により、MongoDBはカテゴリでフィルタリングする前に、まず地理的な領域を絞り込むことができます。
検証:正しく機能しているか?エラーが出なくなっただけで満足しないでください。次のコマンドを実行して、インデックスがアクティブであることを確認します:
db.places.getIndexes()
"key": { "location": "2dsphere" } というエントリを探します。実際の動作を確認するには、クエリの末尾に .explain("executionStats") を追加してください。winningPlan に GEO_NEAR_2DSPHERE ステージが表示されていれば、インデックスが正常に使用され、クエリが最適化されています。

