Print
카테고리: [ NoSQL ]
조회수: 20205

이번 글 에서는 multikey index에 대해 알아보겠습니다.
MongoDB는 Document 기반의 비정규화된 데이터를 저장하는 데이터베이스이기 때문에
하나의 document가 array형태의 데이터를 가지는 경우나, document안에 또 document가 있는 embeded document형태의 데이터를 갖는 경우가 많은데
이런 형태의 document를 위한 인덱스가 Multi-Key Index입니다.

multikey index

> db.user.insert({
... name:"kimdubi",
... addr:{ city : "seongnam", dong: "sampyeong" },
... db:["MongoDB","Mysql","Oracle"]})

db.user.createIndex({addr:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
db.user.createIndex({db:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 2,
    "numIndexesAfter" : 3,
    "ok" : 1
}

> db.user.createIndex({"addr.city":1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 3,
    "numIndexesAfter" : 4,
    "ok" : 1
}
> db.user.createIndex({"addr.city":1,"addr.dong":1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 4,
    "numIndexesAfter" : 5,
    "ok" : 1

=> array, embeded document 를 field 로 갖는 인덱스, embeded document의 특정 field만으로도 인덱싱 가능

embeded document / array field 조회하는 방법

db.user.find({addr:{city:"seongnam",dong:"sampyeong"}}).pretty()
{
    "_id" : ObjectId("5daaccd874411c322449442e"),
    "name" : "kimdubi",
    "addr" : {
        "city" : "seongnam",
        "dong" : "sampyeong"
    },
    "db" : [
        "MongoDB",
        "Mysql",
        "Oracle"
    ]
}
> db.user.find({addr:{dong:"sampyeong"}}).pretty()
>
> db.user.find({addr:{city:"seongnam"}}).pretty()
>
> db.user.find({"addr.city":"seongnam"})
{ "_id" : ObjectId("5daaccd874411c322449442e"), "name" : "kimdubi", "addr" : { "city" : "seongnam", "dong" : "sampyeong" }, "db" : [ "MongoDB", "Mysql", "Oracle" ] }
> db.user.find({"addr.dong":"sampyeong"})
{ "_id" : ObjectId("5daaccd874411c322449442e"), "name" : "kimdubi", "addr" : { "city" : "seongnam", "dong" : "sampyeong" }, "db" : [ "MongoDB", "Mysql", "Oracle" ] }
> db.user.find({db:"MongoDB"})
{ "_id" : ObjectId("5daaccd874411c322449442e"), "name" : "kimdubi", "addr" : { "city" : "seongnam", "dong" : "sampyeong" }, "db" : [ "MongoDB", "Mysql", "Oracle" ] }

Multi-Key index 제한 사항

=> 여러개의 인덱스 엔트리가 같은 document를 가리키기 때문에 shard key 로 사용할 수 없음
shard key 는 키 값의 범위에 따라 하나의 chunk로 매핑되어야 하기 때문

 db.installed_apps.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "test.installed_apps"
    },
    {
        "v" : 2,
        "key" : {
            "apps" : 1
        },
        "name" : "apps_1",
        "ns" : "test.installed_apps"
    }
]

db.installed_apps.explain().aggregate([ {'$unwind':'$apps'},{'$group':{_id:'$apps',sum:{'$sum':1}}},{'$match':{sum:{'$gte':2}}}])
{
    "stages" : [
        {
            "$cursor" : {
                "query" : {

                },
                "fields" : {
                    "apps" : 1,
                    "_id" : 0
                },
                "queryPlanner" : {
                    "plannerVersion" : 1,
                    "namespace" : "test.installed_apps",
                    "indexFilterSet" : false,
                    "parsedQuery" : {

                    },
                    "queryHash" : "8B3D4AB8",
                    "planCacheKey" : "8B3D4AB8",
                    "winningPlan" : {
                        "stage" : "COLLSCAN",
                        "direction" : "forward"
                    },
                    "rejectedPlans" : [ ]
                }
            }
        },
        {
            "$unwind" : {
                "path" : "$apps"
            }
        },
        {
            "$group" : {
                "_id" : "$apps",
                "sum" : {
                    "$sum" : {
                        "$const" : 1
                    }
                }
            }
        },
        {
            "$match" : {
                "sum" : {
                    "$gte" : 2
                }
            }
        }
    ],
    "ok" : 1
}

=> app field에 인덱스가 있지만 multi-key index는 커버링 인덱스 처리가 되지 않는다.
컬렉션을 모두 읽은 다음에 apps field 의 array 데이터를 그룹핑 해서 최종 결과를 반환하게됨

> db.test.insert({i:[2,9]})
WriteResult({ "nInserted" : 1 })
> db.test.insert({i:[4,3]})
WriteResult({ "nInserted" : 1 })
>
> db.test.find({i:{$gte:3, $lte:6}})
{ "_id" : ObjectId("5dab36f674411c3224494432"), "i" : [ 2, 9 ] }
{ "_id" : ObjectId("5dab36fb74411c3224494433"), "i" : [ 4, 3 ] }

=> i:{$gte:3, $lte:6} 조건이니 [2,9] 는 조회되지 않을 것 같지만 포함되어있음 i : {$gte:3} i : {$lte:6} 의 합집합으로 조회되기 때문임

> db.test.find({i:{$elemMatch:{$gte:3, $lte:6}}})
{ "_id" : ObjectId("5dab36fb74411c3224494433"), "i" : [ 4, 3 ] }

=> Mulki-key index 에 대해 between 검색을 하려면 array의 각 element들에 대해 $elemMatch 연산자를 사용해야함
i : {$gte:3} i : {$lte:6} 의 교집합으로 조회

> db.test.insert({i:[2,4]})
WriteResult({ "nInserted" : 1 })
> db.test.find({i:{$elemMatch:{$gte:3,$lte:6}}})
{ "_id" : ObjectId("5dab36fb74411c3224494433"), "i" : [ 4, 3 ] }
{ "_id" : ObjectId("5dab3a1874411c3224494434"), "i" : [ 2, 4 ] }

=> $elemMatch연산자의 결과는 배열의 모든 요소가 조건에 만족해야 하는 게 아니라
$elemMatch의 조건을 모두 만족하는 엘리먼트 하나라도 가지고 있는 document를 반환함