解決済み:MongoDBの「$search is only allowed as the first stage」エラー

中級🍃 MongoDB2026-07-23| MongoDB Atlas (バージョン 4.2+), Node.js, Mongoose, MongoDB Compass

Error Message

PlanExecutor error during aggregation :: caused by :: $search is only allowed as the first stage in the pipeline
#mongodb#atlas-search#集計パイプライン#データベース最適化

アグリゲーションパイプラインが失敗する理由

この問題は、$search オペレーターをアグリゲーション配列の先頭以外で使用しようとすると発生します。標準的なオペレーターとは異なり、Atlas Search ではドライバがクエリを即座に専用のエンジンに渡す必要があります。

PlanExecutor error during aggregation :: caused by :: $search is only allowed as the first stage in the pipeline

技術的な理由:mongot と mongod

内部的には、MongoDB Atlas は2つの別々のプロセスを実行しています。標準の mongod エンジンが通常のクエリを処理し、mongot と呼ばれるプロセスが Lucene ベースの Atlas Search を管理します。

アグリゲーションを実行すると、MongoDB はすぐに mongot プロセスを関与させる必要があるかどうかを判断する必要があります。もし $match$project ステージを最初に配置すると、mongod エンジンが自らデータの処理を開始してしまいます。$search に到達する頃には、操作を検索エンジンに引き渡すには遅すぎて、パイプラインがクラッシュしてしまいます。

よくある間違い

開発者は、検索を実行する前に $match ステージを使用してデータを絞り込もうとすることがよくあります。これはパフォーマンスの観点からは直感的に思えますが、このエラーの主な原因となります。こちらの誤った例を見てみましょう。

db.products.aggregate([
  { $match: { status: "active" } }, // ❌ エラー!これは $search の前に置くことはできません
  {
    $search: {
      index: "default",
      text: {
        query: "mechanical keyboard",
        path: "name"
      }
    }
  }
]);

解決策1:ステージの順序を入れ替える

最も手っ取り早い解決策は、$search を配列の先頭に移動することです。フィルタリング、制限、ソートなどは、その後に実行する必要があります。

db.products.aggregate([
  {
    $search: {
      index: "default",
      text: {
        query: "mechanical keyboard",
        path: "name"
      }
    }
  },
  { $match: { status: "active" } } // ✅ これで動作します
]);

パフォーマンスに関する注意: これでエラーは解消されますが、必ずしも効率的とは限りません。1,000万件のドキュメントがあり、そのうち1,000件だけが "active" である場合、MongoDB は $match ステージでアクティブでないドキュメントを破棄する前に、インデックス全体を検索する必要があります。

解決策2:compound オペレーターを使用する(ベストプラクティス)

クエリの高速性を維持するには、compound オペレーターを使用します。これにより、検索語句とフィルターを1つの操作にまとめ、mongot エンジン内で処理を完結させることができます。500万件のドキュメントがあるコレクションにおいて、この方法を使用すると実行時間を数秒から100ミリ秒未満に短縮できる場合があります。

db.products.aggregate([
  {
    $search: {
      index: "default",
      compound: {
        must: [{
          text: {
            query: "mechanical keyboard",
            path: "name"
          }
        }],
        filter: [{
          text: {
            query: "active",
            path: "status"
          }
        }]
      }
    }
  }
]);

これがより優れたパフォーマンスを発揮する理由は以下の通りです:

  • must: 検索条件です。関連性スコアに寄与します。
  • filter: $match と全く同じように機能します。検索ランキングに影響を与えることなく、ドキュメントを含めたり除外したりします。

解決策3:Mongoose ミドルウェアに注意する

コードが正しく見えるのにエラーが発生し続ける場合は、Mongoose プラグインを確認してください。「ソフトデリート(論理削除)」用のグローバルプラグインは、すべてのクエリの開始時に { deleted: false } という match ステージを自動的に挿入することがよくあります。この隠れたステージが Atlas Search を妨げます。

これを回避するには、パイプラインを手動で構築し、$search がインデックス 0 にあることを確認する必要があります:

const pipeline = [
  { $search: { /* ここに設定を記述 */ } },
  { $limit: 10 }
];

// 必要に応じて、ミドルウェアの干渉を避けるためにベースモデルを使用します
await Product.aggregate(pipeline);

修正を確認する方法

MongoDB Compass Aggregation Pipeline Builder でクエリを実行してみてください。Compass は各ステージのリアルタイムプレビューを提供します。最初のステージが $search でない場合、プレビューに即座に PlanExecutor エラーが表示されます。さらに、クエリに .explain("executionStats") を追加してみてください。実行ツリーの最上部に COLLSCANIXSCAN ではなく、SEARCH ステージが表示されるはずです。

重要なポイント

  • 配置: $search は「主役」です。常に最初に配置する必要があります。
  • フィルタリング: 最大限の速度を得るには、検索ステージ内の compoundfilter を使用してください。
  • ホスティング: $search は MongoDB Atlas でのみ動作することに注意してください。ローカルの Community Edition を使用している場合は、$text を使用する必要があります(これにも特定の配置ルールがあります)。

Related Error Notes