Computer >> 컴퓨터 >  >> 프로그래밍 >> MongoDB

$elemMatch 사용 시 MongoDB 인덱스가 작동하지 않나요? explain()으로 확인하는 방법


MongoDB에서 $elemMatch 연산자를 사용할 때 인덱스가 제대로 작동하는지 확인하려면 explain() 메서드를 활용하는 것이 가장 확실한 방법입니다. 이번 글에서는 인덱스 생성부터 쿼리 실행 계획 분석까지 전 과정을 실제 예제와 함께 단계별로 살펴보겠습니다.

인덱스 생성 및 테스트 데이터 준비

먼저 컬렉션에 인덱스를 생성합니다. 아래 예제에서는 Information.StudentDetails.StudentName 필드에 대해 sparse 옵션과 background 옵션을 적용한 인덱스를 생성합니다. sparse 인덱스는 해당 필드가 존재하는 문서만 색인하므로 저장 공간을 절약할 수 있고, background 옵션은 인덱스 생성 중에도 읽기·쓰기 작업을 차단하지 않습니다.

> db.workingOfIndexesDemo.createIndex({"Information.StudentDetails.StudentName":1},{ sparse : true, background : true } );
{
   "createdCollectionAutomatically" : true,
   "numIndexesBefore" : 1,
   "numIndexesAfter" : 2,
   "ok" : 1
}
> db.workingOfIndexesDemo.insertOne({"Information":{"StudentDetails":{"StudentName":"Chris"}}});
{
   "acknowledged" : true,
   "insertedId" : ObjectId("5e06f94825ddae1f53b621f7")
}
> db.workingOfIndexesDemo.insertOne({"Information":{"StudentDetails":{"StudentName":"David"}}});
{
   "acknowledged" : true,
   "insertedId" : ObjectId("5e06f94f25ddae1f53b621f8")
}
> db.workingOfIndexesDemo.insertOne({"Information":{"StudentDetails":{"StudentName":"Mike"}}});
{
   "acknowledged" : true,
   "insertedId" : ObjectId("5e06f95325ddae1f53b621f9")
}

실행 결과를 보면 컬렉션이 자동으로 생성되었고(createdCollectionAutomatically: true), 기본 _id 인덱스 1개에 새 인덱스 1개가 추가되어 총 2개의 인덱스가 된 것을 확인할 수 있습니다.

저장된 문서 확인하기

find() 메서드를 사용하면 컬렉션에 저장된 모든 문서를 조회할 수 있습니다.

> db.workingOfIndexesDemo.find();

실행 결과는 다음과 같습니다.

{ "_id" : ObjectId("5e06f94825ddae1f53b621f7"), "Information" : { "StudentDetails" : { "StudentName" : "Chris" } } }
{ "_id" : ObjectId("5e06f94f25ddae1f53b621f8"), "Information" : { "StudentDetails" : { "StudentName" : "David" } } }
{ "_id" : ObjectId("5e06f95325ddae1f53b621f9"), "Information" : { "StudentDetails" : { "StudentName" : "Mike" } } }

$elemMatch 쿼리에 explain() 적용하기

이제 $elemMatch 조건이 포함된 쿼리에 explain()을 붙여 실행 계획을 확인해 보겠습니다.

> db.workingOfIndexesDemo.find({"Information.StudentDetails": { $elemMatch: { "StudentName" : "David"} } } ).explain();

실행 결과는 다음과 같습니다.

{
   "queryPlanner" : {
      "plannerVersion" : 1,
      "namespace" : "test.workingOfIndexesDemo",
      "indexFilterSet" : false,
      "parsedQuery" : {
         "Information.StudentDetails" : {
            "$elemMatch" : {
               "StudentName" : {
                  "$eq" : "David"
               }
            }
         }
      },
      "winningPlan" : {
         "stage" : "FETCH",
         "filter" : {
            "Information.StudentDetails" : {
               "$elemMatch" : {
                  "StudentName" : {
                     "$eq" : "David"
                  }
               }
            }
         },
         "inputStage" : {
            "stage" : "IXSCAN",
            "keyPattern" : {
               "Information.StudentDetails.StudentName" : 1
            },
            "indexName" : "Information.StudentDetails.StudentName_1",
            "isMultiKey" : false,
            "multiKeyPaths" : {
               "Information.StudentDetails.StudentName" : [ ]
            },
            "isUnique" : false,
            "isSparse" : true,
            "isPartial" : false,
            "indexVersion" : 2,
            "direction" : "forward",
            "indexBounds" : {
               "Information.StudentDetails.StudentName" : [
                  "[\"David\", \"David\"]"
               ]
            }
         }
      },
      "rejectedPlans" : [ ]
   },
   "serverInfo" : {
      "host" : "DESKTOP-QN2RB3H",
      "port" : 27017,
      "version" : "4.0.5",
      "gitVersion" : "3739429dd92b92d1b0ab120911a23d50bf03c412"
   },
   "ok" : 1
}

실행 결과 해석하기

출력 결과에서 주목해야 할 핵심 포인트는 다음과 같습니다.

  • IXSCAN 스테이지: winningPlan의 inputStage가 IXSCAN으로 표시되어 있습니다. 이는 컬렉션 전체를 훑는 COLLSCAN(컬렉션 스캔)이 아니라 인덱스 스캔으로 쿼리가 실행되었음을 의미하며, $elemMatch 조건에서도 인덱스가 정상적으로 활용되었음을 보여줍니다.
  • indexBounds: ["David", "David"] 범위로 표시된 것은 인덱스가 StudentName 값이 "David"인 구간만 한정하여 스캔했다는 뜻입니다. 필요한 문서만 정확히 찾아내므로 매우 효율적입니다.
  • FETCH 스테이지: 인덱스로 대상 문서를 먼저 좁힌 뒤, FETCH 단계에서 해당 문서를 가져오고 남은 $elemMatch 필터를 최종적으로 적용합니다.
  • isSparse: true: 앞서 sparse 옵션으로 생성한 인덱스가 그대로 사용되었음을 나타냅니다.

정리

$elemMatch를 사용한다고 해서 반드시 인덱스가 무시되는 것은 아닙니다. 인덱스가 설정된 하위 필드 경로가 $elemMatch 내부 조건과 일치하면 MongoDB는 해당 인덱스를 활용해 쿼리 성능을 크게 높일 수 있습니다. 인덱스 동작 여부가 궁금하다면 항상 explain()을 실행하여 winningPlan에 IXSCAN이 포함되어 있는지 확인하는 습관을 들이는 것이 좋습니다.