MongoDB - 인덱스 생성과 삭제 - 데이터 조회

Share

Last Updated on 1월 23, 2024 by Jade(정현호)

안녕하세요 
이번 포스팅은 인덱스 생성과 비교/논리 연산자, 컬렉션 정보 확인에 관련하여 MongoDB in Action(몽고디비 인 액션) 2nd Edition 책의 내용 정리와 MongoDB 공식 문서를 참조한 글입니다

테스트 컬렉션 생성

조회 성능을 높이기 위해서는 인덱스를 생성하는 것이 가장 일반적인 방법입니다.
MongoDB도 인덱스를 사용할 수 있습니다.

인덱스에 대해 이미 알고 있다면 MongoDB 에서 인덱스를 생성하고 explain() 명령을 사용하여 인덱스에 대한 쿼리를 프로파일 하는 것이 얼마나 쉬운지 알게 될 것입니다.

인덱스에 대해서 확인해보기 위해서는 컬렉션에 많은 도큐먼트가 있어야만 인덱싱에 대한 예제로 의미가 있습니다.

그래서 먼저 numbers 라는 컬렉션에 20,000개의 간단한 도큐먼트를 추가하도록 하겠습니다.
MongoDB 쉘은 자바스크립트 인터프리터이므로 이 작업은 다음과 같이 진행하도록 하겠습니다.

> for(i = 0; i<20000; i++) {
    db.numbers.insertOne({num: i});
    }


컬렉션 수를 확인해보겠습니다.

> db.numbers.count()
20000


들어가 있는 데이터 일부분만 확인해 보겠습니다.

> db.numbers.find()
{ "_id" : ObjectId("61c495078cfe17c8df46790e"), "num" : 0 }
{ "_id" : ObjectId("61c495088cfe17c8df46790f"), "num" : 1 }
{ "_id" : ObjectId("61c495088cfe17c8df467910"), "num" : 2 }
{ "_id" : ObjectId("61c495088cfe17c8df467911"), "num" : 3 }
{ "_id" : ObjectId("61c495088cfe17c8df467912"), "num" : 4 }
{ "_id" : ObjectId("61c495088cfe17c8df467913"), "num" : 5 }
{ "_id" : ObjectId("61c495088cfe17c8df467914"), "num" : 6 }
{ "_id" : ObjectId("61c495088cfe17c8df467915"), "num" : 7 }
{ "_id" : ObjectId("61c495088cfe17c8df467916"), "num" : 8 }
{ "_id" : ObjectId("61c495088cfe17c8df467917"), "num" : 9 }
{ "_id" : ObjectId("61c495088cfe17c8df467918"), "num" : 10 }
{ "_id" : ObjectId("61c495088cfe17c8df467919"), "num" : 11 }
{ "_id" : ObjectId("61c495088cfe17c8df46791a"), "num" : 12 }
{ "_id" : ObjectId("61c495088cfe17c8df46791b"), "num" : 13 }
{ "_id" : ObjectId("61c495088cfe17c8df46791c"), "num" : 14 }
{ "_id" : ObjectId("61c495088cfe17c8df46791d"), "num" : 15 }
{ "_id" : ObjectId("61c495088cfe17c8df46791e"), "num" : 16 }
{ "_id" : ObjectId("61c495088cfe17c8df46791f"), "num" : 17 }
{ "_id" : ObjectId("61c495088cfe17c8df467920"), "num" : 18 }
{ "_id" : ObjectId("61c495088cfe17c8df467921"), "num" : 19 }
Type "it" for more


count() 쿼리를 통해서 컬렉션의 건수를 확인할 수 있으며 그 다음 find() 쿼리는 처음 20개의 결과를 보여 주며
it 명령어를 사용하면 추가 결과를 확인할 수 있습니다.

충분한 양의 도큐먼트를 만들었으니 이제 몇 가지 쿼리를 실행해보겠습니다
    

비교 연산자와 논리 연산자

쿼리를 실행 하기전에 조회와 관련된 비교 연산자와 논리 연산자에 대해서 확인해보겠습니다.

먼저 비교 연산자 입니다.

조회 시 $gt 와 $lt 라는 특수한 연산자를 통해서 범위에 대한 쿼리를 실행할 수 있습니다.
각각 greater than(보다 큰) 과 less than(보다 작은)을 뜻 합니다.

num의 값이 19,995 보다 큰 도큐먼트를 모두 가져오기 위한 쿼리는 다음과 같습니다.

> db.numbers.find( {num: { "$gt":19995 }})
{ "_id" : ObjectId("61ba194d41d04a0d3764d565"), "num" : 19996 }
{ "_id" : ObjectId("61ba194d41d04a0d3764d566"), "num" : 19997 }
{ "_id" : ObjectId("61ba194d41d04a0d3764d567"), "num" : 19998 }
{ "_id" : ObjectId("61ba194d41d04a0d3764d568"), "num" : 19999 }


두 연산자를 같이 사용해서 상한값과 하한값을 설정할 수도 있습니다.

> db.numbers.find( {num: {"$gt":20, "$lt": 25}})
{ "_id" : ObjectId("61ba194841d04a0d3764875e"), "num" : 21 }
{ "_id" : ObjectId("61ba194841d04a0d3764875f"), "num" : 22 }
{ "_id" : ObjectId("61ba194841d04a0d37648760"), "num" : 23 }
{ "_id" : ObjectId("61ba194841d04a0d37648761"), "num" : 24 }


위와 같이 간단한 JSON 도큐먼트를 사용하여 SQL에서와 같은 방식으로 정교한 범위 쿼리 언어를 만들 수 있습니다.

$gt 와 $lt는 MongoDB 쿼리에서 사용할 수 있는 키워드 중 하나이며 아래와 같은 비교 연산자가 있습니다.

$eq : (equals) 비교 값과 일치하는 값
$gt : (greater than) 비교 값 보다 큰 값
$gte : (greather than or equals) 비교 값 보다 크거나 같은 값
$lt : (less than) 비교 값 보다 작은 값
$lte : (less than or equals) 비교 값 보다 작거나 같은 값
$ne : (not equal) 비교 값과 일치하지 않는 값
$in : 비교 값 배열 안에 속하는 값
$nin : 비교 값 배열 안에 속하지 않는 값


그 다음은 논리 연산자 입니다.

$or : 여러 개의 조건 중에 적어도 하나를 만족하는 도큐먼트를 찾음
$and : 여러 개의 조건을 모두 만족하는 도큐먼트를 찾음
$not : 해당 조건이 맞지 않는 경우와 해당 필드가 없는 경우를 찾음
      {filed : { $not: { <operator-expression> } } }
$nor : 여러 개의 조건을 모두 만족 하지 않는 도큐먼트를 찾음
     { $nor: [ { <expression1> }, { <expression2> }, ... { <expressionN> } ] }


실행 예시)

> db.numbers.find( { $or: [{ num:5},{num:7} ] } )
{ "_id" : ObjectId("61c495088cfe17c8df467913"), "num" : 5 }
{ "_id" : ObjectId("61c495088cfe17c8df467915"), "num" : 7 }

      

인덱싱과 explain()

관계형 데이터베이스로 작업한 경험이 있다면 디버깅 또는 쿼리 최적화를 위한 여러가지 유용한 도구인 explain 이 익숙할 수도 있습니다.

데이터베이스가 쿼리를 받았을 때 이를 실행하는 방법에 대해서 계획을 세워야 하며, 이를 쿼리 플랜이라고 부릅니다.

EXPLAIN은 쿼리에서 인덱스를 사용한 경우 어떤 인덱스를 사용하였는지를 확인할 수 있으며 쿼리 경로에 대한 정보를 제공해줍니다.


EXPLAIN

쿼리는 여러 다양한 방식으로 수행되며, 이는 때때로 예상치 못한 결과를 만들어 내기도 하게 됩니다.
EXPLAIN 은 이러한 것들을 설명을 해주는 도구입니다.


EXPLAIN 은 아래와 같이 사용을 하면 됩니다.

> db.numbers.find({ num: {"$gt": 19995}}).explain("executionStats")
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "test.numbers",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "num" : {
                                "$gt" : 19995
                        }
                },
                "winningPlan" : {
                        "stage" : "COLLSCAN",  <-- COLLSCAN: 전체 스캔
                        "filter" : {
                                "num" : {
                                        "$gt" : 19995
                                }
                        },
                        "direction" : "forward"
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 4,
                "executionTimeMillis" : 6,
                "totalKeysExamined" : 0,
                "totalDocsExamined" : 20000,
                "executionStages" : {
                        "stage" : "COLLSCAN",  <-- COLLSCAN: 전체 스캔
                        "filter" : {
                                "num" : {
                                        "$gt" : 19995
                                }
                        },
                        "nReturned" : 4,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 20002,
                        "advanced" : 4,
                        "needTime" : 19997,
                        "needYield" : 0,
                        "saveState" : 20,
                        "restoreState" : 20,
                        "isEOF" : 1,
                        "direction" : "forward",
                        "docsExamined" : 20000
                }
        },
        "serverInfo" : {
                "host" : "wmp",
                "port" : 27017,
                "version" : "4.4.10",
                "gitVersion" : "58971da1ef93435a9f62bf4708a81713def6e88c"
        },
        "ok" : 1
}

위의 내용은 인덱스를 사용하지 못한 쿼리에 대한 explain("executionStats") 의 일반적인 결과입니다.

executionStats 는 MongoDB 3.0 에서 새롭게 추가된 명령어로 더 상세화 된 결과물을 확인할 수 있습니다


explain 결과를 살펴보면 4개의 결과(nReturned) 을 반환하는 쿼리입니다.

위의 예제에서는 단지 4개의 결과를 반환하기 위해서 컬렉션 전부인 20,000개의 도큐먼트(docsExamined) 를 스캔하였음을 알 수 있습니다.

totalKeysExamined 는 스캔한 인덱스 엔트리의 개수를 보여주게 되며, 위의 예제에서는 0 인 것을 확인할 수 있습니다.

스캔한 도큐먼트 수와 결과값의 큰 차이는 이 쿼리가 비효율로 실행되었음을 보여 주게 됩니다.


인덱스 생성

위의 예제 컬렉션(numbers)은 인덱스가 필요하며, createIndex() 명령을 사용하여 num 키에 대한 인덱스를 생성하도록 하겠습니다.

> db.numbers.createIndex({num: 1},{name:"ix_num"})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}


MongoDB 3.0 부터 createIndex() 는 기존의 ensureIndex() 를 대체합니다.
ensureIndex()는 여전히 유효하지만 createIndex() 의 alias 로 존재합니다.

createIndex() 메소드에 인덱스를 생성할 키 값을 입력하여 생성을 합니다.

위의 예제에서는 num 키에 대해서 인덱스를 생성하는 것이며 :1 은 오름차순 정렬을 의미하며 :-1은 내림차순 정렬을 의미합니다
{name: "인덱스명} 을 통해서 인덱스명을 지정하면서 인덱스를 생성할 수 있으며 위의 예제에서는 인덱스 이름은 ix_num 으로 생성하였습니다.

numIndexesBefore 는 인덱스를 생성하기 전의 인덱스 수를 의미하고, numIndexesAfter 는 생성 후 인덱스 수를 나타냅니다.
컬렉션이 생성되면 _id 에 대해서 프라이머리 키가 생성되기 때문에 생성 전이 1이고, 인덱스 생성 후가 2 인 것을 확인할 수 있습니다.

인덱스가 정상적으로 생성되었는지는 getIndexes() 메서드를 통해서 확인할 수 있습니다.

> db.numbers.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "key" : {
                        "num" : 1
                },
                "name" : "ix_num" <-- 생성한 인덱스명
        }
]


위의 결과는 이 컬렉션은 두개의 인덱스를 가지고 있습니다.
첫번째 인덱스는 모든 컬렉션에 자동으로 생성되는 표준 _id 인덱스이고,
 두번째 인덱스는 num 에 대한 인덱스 ix_num 입니다.

인덱스가 생성되었기 때문에 다시 explain 을 수행해보겠습니다.

> db.numbers.find({ num: {"$gt": 19995}}).explain("executionStats")
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "test.numbers",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "num" : {
                                "$gt" : 19995
                        }
                },
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN", <-- IXSCAN: 인덱스 스캔
                                "keyPattern" : {
                                        "num" : 1
                                },
                                "indexName" : "ix_num", <-- ix_num 인덱스를 사용
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "num" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "num" : [
                                                "(19995.0, inf.0]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 4,
                "executionTimeMillis" : 1,
                "totalKeysExamined" : 4, <--- 오직 네개의 도큐먼트만 스캔
                "totalDocsExamined" : 4, <--- 오직 네개의 도큐먼트만 스캔
                "executionStages" : {
                        "stage" : "FETCH",
                        "nReturned" : 4,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 5,
                        "advanced" : 4,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 1,
                        "docsExamined" : 4,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                                "stage" : "IXSCAN", <-- IXSCAN: 인덱스 스캔
                                "nReturned" : 4,
                                "executionTimeMillisEstimate" : 0,
                                "works" : 5,
                                "advanced" : 4,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 0,
                                "restoreState" : 0,
                                "isEOF" : 1,
                                "keyPattern" : {
                                        "num" : 1
                                },
                                "indexName" : "ix_num",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "num" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "num" : [
                                                "(19995.0, inf.0]"
                                        ]
                                },
                                "keysExamined" : 4,
                                "seeks" : 1,
                                "dupsTested" : 0,
                                "dupsDropped" : 0
                        }
                }
        },
        "serverInfo" : {
                "host" : "wmp",
                "port" : 27017,
                "version" : "4.4.10",
                "gitVersion" : "58971da1ef93435a9f62bf4708a81713def6e88c"
        },
        "ok" : 1
}


num 키에 대해서 인덱스 ix_num 를 사용함에 따라서 네 개의 도큐먼트만 스캔하게 되었습니다.
(수행하는 시스템에 따라서 쿼리 실행 시간도 차이가 날수 있음)

inputStage.stage 가 IXSCAN 인 것을 확인할 수 있으며 각각의 의미는 아래와 같습니다.

COLLSCAN: 전체 스캔
IXSCAN: 인덱스 스캔


위에서 생성한 인덱스 외 다른 종류로 unique 인덱스를 생성할 수 있습니다.
유니크 인덱스는 생성시 unique: true 을 지정하고 생성을 진행합니다.

// 생성 //
> db.numbers.createIndex( { num: 1 }, { unique: true } )
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}

// 확인 //
> db.numbers.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "unique" : true, <-- unique 인덱스
                "key" : {
                        "num" : 1
                },
                "name" : "num_1"
        }
]

 

Note

WiredTiger의 LSM(Log-Structured Merge-trees) 에서는 B-Tree 인덱스가 지원되지 않습니다.


복합 인덱스 
2개 이상의 키(필드)가 포함되어 같이 생성되는 인덱스를 의미하며 아래와 같이 생성시 포함하고자 하는 키를 입력하여 생성합니다.

> db.collection.createIndex( { 키1:1 , 키2:1 }, {name:"인덱스_이름"} )



인덱스 삭제

인덱스의 삭제는 두가지 방법으로 할 수 있으며 해당 키를 입력하여 삭제하거나 인덱스명을 명시적으로 입력하여 삭제할 수 있습니다.


1) 인덱스에 해당하는 키 를 입력

> db.numbers.dropIndex({num:1})
{ "nIndexesWas" : 2, "ok" : 1 }

이때 키 이름과 인덱스 생성시 정렬 정보도 같이 기재하면 됩니다
1 또는 -1 이며, 해당정보는 getIndexes() 를 통해서 확인할 수 있습니다.

2) 인덱스 명을 통해 삭제

> db.numbers.dropIndex("ix_num")
{ "nIndexesWas" : 2, "ok" : 1 }



인덱스 재생성(reIndex())

인덱스에 대한 재생성은 reIndex() 를 통해 할 수 있으며, 해당 작업은 _id 를 비롯하여 모든 인덱스를 재생성 하게 됩니다.

이 작업은 많은 량의 데이터 또는 많은 수의 인덱스가 있을 경우 작업 비용(cost)가 많이 들 수 있는 작업입니다.
보통의 경우에는 해당 작업이 사실상 필요하지는 않습니다.

Replica Set의 경우 reIndex()는 primary에서 secondary 로 복제가 전파되지 않으며 단일 mongod 인스턴스에만 영향을 미치게 됩니다.
문서에도 replica set 환경 과 샤딩 클러스터의 컬렉션에서는 실행하지 말라는 내용이 기재되어 있습니다.

물론 인덱스는 어느정도 대가를 감수해야 합니다. 인덱스는 어느 정도의 공간이 필요하고 미세하게 insert의 성능을 떨어뜨리지만 쿼리 최적화를 위해 꼭 필요한 도구입니다.


인덱스에 대한 더 자세한 내용은 아래 포스팅을 참조하시면 됩니다.

                      

데이터베이스, 컬렉션 정보 확인

정보 확인하는 몇 가지에 대해서 확인해보도록 하겠습니다.

show dbs

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
test    0.001GB

show dbs 는 시스템상의 모든 데이터베이스 와 그 사용 용량을 보여 줍니다.


show collections

> show collections
numbers

데이터 베이스내에 존재하는 컬렉션 목록을 확인할 수 있습니다.
또는 show tables 를 통해서도 컬렉션 리스트를 확인할 수 있습니다


현재 데이터베이스의 정보

현재 선택한 데이터베이스에 정보는 아래와 같이 조회할 수 있습니다.

> db.stats()
{
        "db" : "test",
        "collections" : 1,
        "views" : 0,
        "objects" : 20000,
        "avgObjSize" : 35,
        "dataSize" : 700000,
        "storageSize" : 262144,
        "indexes" : 2,
        "indexSize" : 462848,
        "totalSize" : 724992,
        "scaleFactor" : 1,
        "fsUsedSize" : 52187213824,
        "fsTotalSize" : 93001322496,
        "ok" : 1
}



컬렉션 정보

컬렉션에 대해서도 stats()를 통해 확인할 수 있습니다.

> db.numbers.stats()
{
        "ns" : "test.numbers",
        "size" : 700000,
        "count" : 20000,
        "avgObjSize" : 35,
        "storageSize" : 262144,
        "freeStorageSize" : 0,
        "capped" : false,
        "wiredTiger" : {
                "metadata" : {
                        "formatVersion" : 1
                },
                "creationString" : "access_pattern_hint=none,
                allocation_size=4KB,app_metadata=(formatVersion=1),
                assert=(commit_timestamp=none,durable_timestamp=none,
                < 중략>
                "type" : "file",
                "uri" : "statistics:table:test/collection-0-609553672372511813",
                "LSM" : {
                        "bloom filter false positives" : 0,
                        "bloom filter hits" : 0,
                        "bloom filter misses" : 0,
                        "bloom filter pages evicted from cache" : 0,
                        "bloom filter pages read into cache" : 0,
                        "bloom filters in the LSM tree" : 0,
                        "chunks in the LSM tree" : 0,
                        "highest merge generation in the LSM tree" : 0,
                        "queries that could have benefited from a Bloom filter that did not exist" : 0,
                        "sleep for LSM checkpoint throttle" : 0,
                        "sleep for LSM merge throttle" : 0,
                        "total size of bloom filters" : 0
                },
<  ... 중략 ... > 


scale 인자는 위에서 조회된 stats 결과에 대해서 사이즈(용량)의 단위를 변경하게 됩니다
위와 같이 조회하면 사이즈(용량)가 bytes 에서 kilobytes 로 보이게 됩니다.

> db.numbers.stats( {scale : 1024} )
{
        "ns" : "test.numbers",
        "size" : 683, <--- 원본 사이즈 : 700000
        "count" : 20000, <--- count 는 사이즈가 아니라서 그대로 임
        "avgObjSize" : 35,
        "storageSize" : 256,
        "freeStorageSize" : 0,
        < 중략 >


indexDetails 는 컬렉션 내의 각 인덱스와 관련된 정보가 포함된 더 상세한 내용을 확인할 수 있습니다

> db.numbers.stats( { indexDetails : true } )

            

Reference

Reference Book
 • MongoDB in Action(몽고디비 인 액션) 2nd Edition


Reference link
 • mongodb.com/db.collection.reIndex
 • mongodb.com/query
 • mongodb.com/db.collection.stats


관련된 다른 글

       

 

 

      

0
글에 대한 당신의 생각을 기다립니다. 댓글 의견 주세요!x