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

MongoDB explain()로 db.collection.find() 쿼리 실행 계획 해석하기

MongoDB explain()으로 쿼리 실행 계획 해석하기

MongoDB에서 find() 메서드가 어떤 방식으로 쿼리를 실행하는지, 즉 쿼리 실행 계획(query plan)을 확인하려면 explain() 메서드를 사용합니다. 이 글에서는 실제 예제를 통해 컬렉션을 만들고, 정규식 조건에 따라 인덱스 스캔 범위(index bounds)가 어떻게 달라지는지 살펴보겠습니다.

1. 컬렉션 생성 및 문서 삽입

먼저 ClientName 필드에 오름차순 인덱스를 생성한 뒤, 세 개의 문서를 삽입합니다.

> db.demo637.ensureIndex({ClientName:1});
{
    "createdCollectionAutomatically" : true,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
> db.demo637.insert({ClientName:"John"});
WriteResult({ "nInserted" : 1 })
> db.demo637.insert({ClientName:"Bob"});
WriteResult({ "nInserted" : 1 })
> db.demo637.insert({ClientName:"Johnson"});
WriteResult({ "nInserted" : 1 })

2. 저장된 문서 확인

find() 메서드를 사용해 컬렉션의 모든 문서를 조회합니다.

> db.demo637.find();

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

{ "_id" : ObjectId("5e9c26916c954c74be91e6db"), "ClientName" : "John" }
{ "_id" : ObjectId("5e9c26936c954c74be91e6dc"), "ClientName" : "Bob" }
{ "_id" : ObjectId("5e9c269d6c954c74be91e6dd"), "ClientName" : "Johnson" }

Case 1 — 정규식 /john/ 사용 시

문자열 중간에 포함된 패턴을 찾는 /john/ 정규식 쿼리에 대해 실행 계획을 확인해 보겠습니다.

> db.demo637.find({ClientName:/john/}).explain();

출력 결과:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.demo637",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "ClientName" : {
                "$regex" : "john"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "filter" : {
                    "ClientName" : {
                        "$regex" : "john"
                    }
                },
                "keyPattern" : {
                    "ClientName" : 1
                },
                "indexName" : "ClientName_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "ClientName" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "ClientName" : [
                        "[\"\", {})",
                        "[/john/, /john/]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "DESKTOP-QN2RB3H",
        "port" : 27017,
        "version" : "4.0.5",
        "gitVersion" : "3739429dd92b92d1b0ab120911a23d50bf03c412"
    },
    "ok" : 1
}

핵심 포인트: /john/처럼 앵커(^) 없이 문자열 중간을 검색하는 정규식은 인덱스 전체 범위인 ["", {})를 스캔합니다. 즉, 인덱스를 사용하긴 하지만 모든 키를 훑어야 하므로 대용량 컬렉션에서는 비효율적일 수 있습니다.

Case 2 — 정규식 /^john/ 사용 시

이번에는 문자열 시작 부분을 고정하는 /^john/ 정규식 쿼리의 실행 계획을 확인합니다.

> db.demo637.find({ClientName:/^john/}).explain();

출력 결과:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.demo637",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "ClientName" : {
                "$regex" : "^john"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "ClientName" : 1
                },
                "indexName" : "ClientName_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "ClientName" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "ClientName" : [
                        "[\"john\", \"joho\")",
                        "[/^john/, /^john/]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "DESKTOP-QN2RB3H",
        "port" : 27017,
        "version" : "4.0.5",
        "gitVersion" : "3739429dd92b92d1b0ab120911a23d50bf03c412"
    },
    "ok" : 1
}

핵심 포인트: /^john/은 접두사(prefix) 검색이므로 인덱스 경계가 ["john", "joho")로 좁혀집니다. 이는 사전 순으로 "john"부터 "joho" 직전까지만 스캔한다는 의미로, 전체 인덱스를 훑는 Case 1보다 훨씬 적은 범위만 읽어 성능이 크게 향상됩니다.

정리

  • explain()winningPlan 항목에서 선택된 실행 계획과 각 단계(stage)를 확인할 수 있습니다.
  • IXSCAN은 인덱스 스캔, FETCH는 해당 인덱스 키에 매칭되는 실제 문서를 가져오는 단계입니다.
  • indexBounds를 보면 정규식 패턴에 따라 스캔 범위가 얼마나 달라지는지 알 수 있습니다.
  • 성능 최적화를 위해서는 가능한 한 ^ 앵커를 활용해 접두사 기반 검색으로 인덱스 활용도를 높이는 것이 좋습니다.