【ITニュース解説】Understanding MongoDB $lookup performance
2025年09月05日に「Dev.to」が公開したITニュース「Understanding MongoDB $lookup performance」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
MongoDBの `$lookup` のパフォーマンス問題について解説。遅い原因は `$expr` とpipeline内の処理。`$lookup` で外部コレクションと結合する際、`localField` と `foreignField` を使い、pipelineを避けると高速化する。複雑な条件が必要な場合のみ、pipelineと組み合わせるのがおすすめ。インデックスも有効活用しよう。
ITニュース解説
この記事は、MongoDBの$lookupという機能のパフォーマンスについて解説している。特に、$lookupの中で$exprやpipelineを使うと、クエリが非常に遅くなる場合があることを指摘し、その原因と改善策を説明している。
まず、$lookupは、SQLにおけるJOINのような役割を果たす機能だ。複数のコレクション(SQLでいうテーブルのようなもの)を関連付けて、一つの結果として取得できる。基本的な$lookupの構文は以下のようになる。
1{ 2 $lookup: 3 { 4 from: "<結合先のコレクション>", 5 localField: "<入力ドキュメントのフィールド>", 6 foreignField: "<結合先コレクションのフィールド>", 7 as: "<出力配列のフィールド>" 8 } 9}
fromは結合先のコレクションの名前、localFieldは入力ドキュメント(結合元のドキュメント)のフィールド、foreignFieldは結合先コレクションのフィールド、asは結合結果を格納する配列の名前を指定する。localFieldとforeignFieldを使うと、指定したフィールドの値が一致するドキュメント同士が結合される。
しかし、今回の記事で問題になっているのは、letとpipelineを使った$lookupだ。letを使うと、変数を定義してpipelineの中で利用できる。pipelineは、複数のステージ(処理)を順番に実行する機能で、$matchなどを使って複雑な条件でフィルタリングできる。
記事の例では、removedAtというフィールドがnullであるかどうかを条件に加えるために、pipelineと$exprを使っている。ソフトデリートを実現するために削除されたデータを物理的に削除せず、removedAtフラグで管理している場合に、有効なデータのみを抽出するためにこのような条件が必要になる。しかし、$exprはコレクション内のドキュメントを一つ一つ評価するため、インデックスが効率的に利用できず、コレクションが大きくなるにつれてパフォーマンスが著しく低下する。
改善策としては、可能な限りlocalFieldとforeignFieldを使うことが推奨されている。localFieldとforeignFieldを使うことで、MongoDBはインデックスを効率的に利用できるため、クエリのパフォーマンスが向上する。もしpipelineを使う必要がある場合でも、不要な処理を減らし、できる限りシンプルなpipelineを心がけることが重要だ。
記事では、以下の例を使って改善方法を説明している。元のクエリでは、$exprを使ってremovedAtがnullであるかどうかを判定していた。
1{ 2 "$lookup": { 3 "from": "Company", 4 "let": { 5 "company": "$company" 6 }, 7 "pipeline": [ 8 { 9 "$match": { 10 "removedAt": null, 11 "$expr": { 12 "$and": [ 13 { 14 "$eq": [ 15 "$_id", 16 "$$company" 17 ] 18 } 19 ] 20 } 21 } 22 } 23 ], 24 "as": "company" 25 } 26}
このクエリを、以下のようにpipelineの中で$matchを使う形に変更することで、パフォーマンスを改善している。
1{ 2 "$lookup": { 3 "from": "Company", 4 "localField": "company", 5 "foreignField": "_id", 6 "pipeline": [ 7 { 8 "$match": { 9 "removedAt": null 10 } 11 } 12 ], 13 "as": "company" 14 } 15}
さらに、pipeline自体が不要な場合は、以下のようにlocalFieldとforeignFieldだけを使うことで、最も効率的なクエリを実現できる。
1{ 2 "$lookup": { 3 "from": "Company", 4 "localField": "company", 5 "foreignField": "_id", 6 "as": "company" 7 } 8}
このように、MongoDBの$lookupを使う際には、$exprやpipelineの使用を避け、可能な限りlocalFieldとforeignFieldを使うことが、パフォーマンス向上のための重要なポイントとなる。特に大規模なデータを扱うシステムでは、わずかな変更でも大きな効果が得られるため、これらの点を意識してクエリを設計することが重要だ。