MongoDBの32MBソートメモリ制限エラーを解決する方法

intermediate🍃 MongoDB2026-04-25

シナリオ:本番データがクエリを破壊するとき誰にでも起こり得ることです。ステージング環境では1,200件のモックデータで完璧に動作していても、本番環境にデプロイした瞬間にすべてが停止してしまうことがあります。最近、アクティビティログのダッシュボードを構築している際に、この問題に直面しました。ログの収集件数が約45,000ドキュメントに達するまで、アプリは何週間も正常に動作していましたが、その時点でダッシュボードが500エラーを返し始めたのです。

この問題は、標準的な .find().sort({ createdAt: -1 }) 呼び出しに起因していました。createdAt フィールドにインデックスが作成されていなかったため、MongoDBは一致するすべてのドキュメントをRAMに読み込み、その場でソートしようとしました。その一時データセットが32MBのしきい値に達した時点で、エンジンはサーバーを保護するためにプロセスを強制終了しました。

なぜ32MBの制限が存在するのかMongoDBは意地悪をしているわけではありません。保護しているのです。デフォルトでは、エンジンはインメモリのソート操作用に、正確に33,554,432バイト(32MB)を割り当てます。この安全策により、最適化されていない単一のクエリがシステムで使用可能なすべてのメモリを使い果たし、mongod インスタンス全体をクラッシュさせるのを防いでいます。

クエリがインデックスで処理できないソートを必要とする場合、MongoDBは「ブロッキングソート(Blocking Sort)」を実行します。最初のドキュメントを返す前に、結果セット全体をメモリに保持しなければなりません。ドキュメントのサイズが大きい場合(例えば1つ500KBなど)、わずか65レコード程度でこの制限に達してしまいます。

Executor error during find command: Operation failed because the sort operation used more than the maximum 33554432 bytes of RAM.

ステップ1:explain() でクエリを調査するボトルネックがどこにあるか推測しないでください。シェルまたは MongoDB Compass で .explain("executionStats") メソッドを使用して、内部で何が起こっているかを確認しましょう。

db.activity_logs.find({ status: "active" }).sort({ createdAt: -1 }).explain("executionStats")

winningPlan セクションを確認してください。SORT ステージが表示されている場合、MongoDBはRAM内で手動でデータをソートしています。本来見たいのは IXSCAN(インデックススキャン)です。効率的なクエリは、インデックスを使用してすでに正しい順序になっているドキュメントを取得し、32MBの制限を完全に回避します。

解決策1:ターゲットを絞ったインデックスを作成する(推奨)インデックスは最も効果的な修正方法です。ソートフィールドにインデックスを作成すると、MongoDBはデータを事前にソートされたツリー構造で保存します。これにより、CPU/RAMに負荷のかかるタスクが、単純なポインタの読み取りに変わります。

// 新しい順にソートするための降順インデックスを作成
db.activity_logs.createIndex({ createdAt: -1 })

1つのフィールドでフィルタリングし、別のフィールドでソートする場合は、**ESR(Equality, Sort, Range)**ルールに従ってください。これは複合インデックスの黄金律です。完全に一致させるフィールド(Equality)を最初に配置し、次にソートフィールド(Sort)、最後に範囲フィルタ(Range、$gt$lt など)を配置します。

// 最適化対象: .find({ status: "active" }).sort({ createdAt: -1 })
db.activity_logs.createIndex({ status: 1, createdAt: -1 })

解決策2:重い集計には allowDiskUse を使用する動的なレポートダッシュボードなどでは、考えられるすべての組み合わせにインデックスを作成できないことがあります。集計パイプラインでは、ハードドライブをRAMの「オーバーフロー」用として使用するようにMongoDBに指示できます。

db.activity_logs.aggregate([
  { $match: { status: "active" } },
  { $sort: { createdAt: -1 } }
], { allowDiskUse: true })

これにより32MB制限によるクラッシュは防げますが、パフォーマンスとのトレードオフが発生します。ディスクへのデータの出し入れは、RAMよりも大幅に遅くなります。これはトラフィックの多いユーザー向け機能ではなく、バックグラウンドの管理タスクに使用してください。

解決策3:グローバルメモリ制限を調整するサーバーに64GBのRAMがあり、32MBの制限が厳しすぎると感じる場合は、制限を増やすことができます。ただし、慎重に行ってください。これをグローバルに引き上げると、適切に記述されていないすべてのクエリが、より多くのリソースを消費できるようになります。

制限を即座に64MB(67,108,864バイト)に引き上げるには:

db.adminCommand({ setParameter: 1, internalQueryExecMaxBlockingSortBytes: 67108864 })

サーバーの再起動後もこの変更を維持するには、mongod.conf ファイルを更新します:

setParameter:
  internalQueryExecMaxBlockingSortBytes: 67108864

結果の検証インデックスを適用した後、最後にもう一度 explain() コマンドを実行してください。SORT ステージが消えているはずです。私の場合、複合インデックスを追加したことで、実行時間がエラーになる1.2秒から、わずか4ミリ秒に短縮されました。totalKeysExaminednReturned とほぼ一致するようになり、健全でパフォーマンスの高いクエリであることを示しています。

重要なまとめ- スケールによって隠れたバグが明らかになる: データセットが小さいとインデックスの欠如は隠れますが、本番環境のボリュームではそれが露呈します。- ESRルールは極めて重要: 複合インデックスは、常に Equality(等価)、Sort(ソート)、Range(範囲)の順に並べてください。- ディスクは安全網: 複雑なレポートには allowDiskUse を使用しますが、コアなユーザー体験にはインデックスに頼ってください。- プロアクティブに監視する: MongoDB Atlas Profiler やスロークエリログを使用して、32MBの制限に近づいているクエリを失敗する前に見つけ出しましょう。

Related Error Notes