2011. 9. 22. 09:35

[mongodb] Indexing

index 사용으로 query의 속도를 향상시킨다.
(index에 있는 entry로 찾을때는, 그 결과로 바로 jump할 수 있기 때문)
참고로, "username"으로 query한다면, "user_num" field를 indexing할 필요 없다.
왜냐하면, "user_num"으로 query할 일은 없으니깐.

* indexing 소개

아래와 같이 single key로 검색한다고 가정한다.
db.a.find({"field":"value"})

해당 key가 index되면 좀더 속도가 향상된다.
index를 생성하기 위해서, 아래를 실행한다.
db.a.ensureIndex({"field":1})

위 실행은 아래의 속도를 향상시키지는 않는다.
db.a.find({"b":"value"}).sort({"b":1,"a":1})

위 경우 "look through the whole book" 즉, 전체를 검사한다.
이것을 table scan이라고 한다. (즉, index없이 검사함)

다음과 같이 선언가능하다.

db.a.ensureIndex({"a":1, "b":1})

ensureIndex로 전달된 document는 sort로 전달된것과 동일한 형태이여야 한다.
ensureIndex로 전달된 1, -1은 index가 진행될 방향을 의미한다. (sort 정렬을 의미하는 듯함)

single key로 지정된 경우, 방향은 상관이 없다.

single key는 보통 일반책의 index(즉, 알파벳 순)의 구조와 동일하다.
A-Z, Z-A이든 상관없이 M 부터 찾기 시작한다.

하나의 key 이상이라면, index 방향을 고려해야 한다.
다음과 같은 값이라 고려하자.

------------------
| a  |  b  |  c  |
------------------
| f  |  9  |  0  |
| f  |  6  |  1  |
| c  |  7  |  2  |
| c  |  4  |  3  |
| b  |  7  |  4  |
| c  |  2  |  5  |
| e  |  1  |  6  |
| b  |  5  |  7  |
| a  |  3  |  8  |
| d  | 10  |  9  |
| e  | 11  | 10  |
------------------

만일, {"a":1, "b":-1}이라면, 다음과 같이 구조화한다.

------------------
| a  |  b  |  c  |
------------------
| a  |  3  |  8  |
| b  |  7  |  4  |
| b  |  5  |  7  |
| c  |  7  |  2  |
| c  |  4  |  3  |
| c  |  2  |  5  |
| d  | 10  |  9  |
| e  | 11  | 10  |
| e  |  1  |  6  |
| f  |  9  |  0  |
| f  |  6  |  1  |
------------------

이련 경우 {"a":1, "b":-1} sorting에 적합하지만,
{"a":1, "b":1}은 그다지 효과적이지 못하다. 그때는,
{"a":1, "b":1} index를 그냥 만들면 된다.

일반적으로,
N key를 가지는 index는 어떤 조합으로 이뤄지는 query든 결과를 빠르게 한다.
예를 들어, {"a":1, "b":1, "c":1, ..., "z":1}의 index는
{"a":1}, {"a":1, "b":1}, {"a":1, "b":1, "c":1},...의 query를 빠르게 한다.
{"b":1}, {"a":1, "c":1}과 같은 경우는 최적화되진 않는다.

index 생성의 단점은, 모든 insert, update, remove에서 약간의 overhead 추가이다.
collection당 64개의 index를 추가할 수 있다.

가끔은 index를 쓰지 않아야 효과적인 경우가 있다.
일반적으로 collection의 절반 혹은 그 이상이 return되는 경우,
table scan이 더 효과적일 수 있다.
또, key가 존재하는지, 혹은 boolean 값이 true인지 false인지를 query하는 경우는
index를 쓰지 않는 것이 좋다.

; 규모의 index (scaling indexes)

최근 date 별로 user를 query해야 하는 경우를 살펴보자.

db.status.ensureIndex({user:1, date:-1}) - (1)
인 경우와
db.status.ensureIndex({date:-1, user:1}) - (2)
의 차이점을 잘 이해해야 한다.

(1)의 경우는,
AAA | 2011/12/31
AAA | 2011/06/06
AAA | 2011/01/01
AAB | 2011/05/05
AAB | 2011/03/03
AAC | 2011/04/04
AAC | 2011/02/02
...
ZZZ | 2011/08/08
형태가 되고,
만일 user의 수가 굉장히 많은 규모를 가지는 경우,
부하가 걸린다.
즉, AAA ~ ZZZ까지 모두 뒤져 봐야 하기 때문이다.

(2)의 경우는,
2011/12/31 | AAA
2011/06/06 | AAA
2011/05/05 | AAB
2011/04/04 | AAC
2011/03/03 | AAB
..
와 같은 형태가 되고,
user의 수가 많더라도, 전체가 아닌 부분만 query하는 것이 가능하다.

index를 만들때 다음을 고려해 봐라.

1) 무었을 query하는가? 해당 key는 index가 될 필요가 있다.
2) 각각의 key의 바른 방향은 무었인가?
3) 규모가 클 때는 어떻게 될 것인가? 다른 방향을 한번 생각해 봐라.

; embeded document를 indexing 하기

db.a.ensureIndex({"a.b":1})
와 같이 선언 가능

; sort를 위한 index

collection이 커지면서, sort를 위한 index를 생성하게된다.
만일, index되지 않은 키로 sort한다면, 모든 data를 memory에 올려 sort할 것이다.
즉 테라바이트크기는 sort하지 못한다. index를 만들면 해결된다.

; index의 unique 확인하기

collection안의 각각의 index는 index의 unique가 확인되는 string이 있고,
server에 의해 삭제 혹은 관리된다. index name은, 기본값으로,
keyname1_dir1_keyname2_dir2_...keynameN_dirN와 같이 구성된다.

이러한 index name은 길이의 제한(127 byte)이 있기 때문에,
복잡한 index는 생성 실패될 수 있다. getLastError로 확인할 수 있다.

* unique index

unique index는 주어진 key에 대해 해당 collection의 모든 document가 unique값을
가지도록 보장한다.

db.a.ensureIndex({"a":1},{"unique":true})
db.a.insert({"a":1})
db.a.insert({"a":1})
E11000 duplicate key error index: qqqwww.a.$a_1  dup key: { : 1.0 }
와 같이 동일 값에 대해서 insert가 실패된다.

; 중복 제거하기

이미 존재한 document들에 대해, unique index를 생성하면,
중복값이 포함되어 있을 수 있다.
해당 case를 위해 drop 할 수 있다. (처음만 남기고 이후는 삭제)

{"a":1, "b":1}
{"a":1, "b":2}
가 있을 때,
db.a.ensureIndex({"a":1, {"unique":true})
E11000 duplicate key error index: asdsad.a.$a_1  dup key: { : 1.0 }
db.a.find()
{"a":1, "b":1}
{"a":1, "b":2}
db.a.ensureIndex({"a":1}, {"unique":true, "dropDups":true})
E11000 duplicate key error index: asdsad.a.$a_1  dup key: { : 1.0 }
db.a.find()
{"a":1, "b":1}

; unique index 결합하기

결합된 unique index도 가능하다.

* explain, hint 사용하기

explain은 query에 대한 주요 정보를 알려준다. cursor를 통해 실행한다.
document를 리턴하는데, cursor 그 자체는 아니다.

db.a.find().explain()
{
 "cursor" : "BasicCursor",
 "nscanned" : 5,
 "nscannedObjects" : 5,
 "n" : 5,
 "millis" : 17,
 "nYields" : 0,
 "nChunkSkips" : 0,
 "isMultiKey" : false,
 "indexOnly" : false,
 "indexBounds" : {
  
 }
}

db.a.find({"a":1}).sort({"a":-1}).explain()
{
 "cursor" : "BasicCursor",
 "nscanned" : 5,
 "nscannedObjects" : 5,
 "n" : 2,
 "scanAndOrder" : true,
 "millis" : 30,
 "nYields" : 0,
 "nChunkSkips" : 0,
 "isMultiKey" : false,
 "indexOnly" : false,
 "indexBounds" : {
  
 }
}
db.a.ensureIndex({"a":1})
db.a.find({"a":1}).sort({"a":-1}).explain()
{
 "cursor" : "BtreeCursor a_1 reverse",
 "nscanned" : 2,
 "nscannedObjects" : 2,
 "n" : 2,
 "millis" : 16,
 "nYields" : 0,
 "nChunkSkips" : 0,
 "isMultiKey" : false,
 "indexOnly" : false,
 "indexBounds" : {
  "a" : [
   [
    1,
    1
   ]
  ]
 }
}
db.a.find({"a":1}).sort({"a":1}).explain()
{
 "cursor" : "BtreeCursor a_1",
 "nscanned" : 2,
 "nscannedObjects" : 2,
 "n" : 2,
 "millis" : 0,
 "nYields" : 0,
 "nChunkSkips" : 0,
 "isMultiKey" : false,
 "indexOnly" : false,
 "indexBounds" : {
  "a" : [
   [
    1,
    1
   ]
  ]
 }
}

주요 정보든 다음과 같다.

"cursor" : "BasicCursor"
 ; index를 사용하지 않았다는 뜻.

"nscanned" : 2
 ; document를 뒤져본 개수. 이것과 query 개수를 비교해 볼 만 하다.

"n" : 2
 ; query 개수

"millis" : 0
 ; query 시간

"cursor" : "BtreeCursor a_1"
 ; B-tree 구조의 index를 사용함. a_1 index name이 적용됨.
   이는 db.system.indexes.find({"ns":"db이름.collection이름", "name":"a_1"})
   으로 조회 가능하다.

hint()
; query에 사용될 index를 강제로 지정할 수 있다.

db.a.find({"a":3, "b":4}).hint(("b":1, "a":1})

* index 관리하기


index의 meta 정보는 system.indexes collection에 저장된다.

예약된 collection이므로 임의로 insert/remove 못한다.
ensureIndex 혹은 dropIndex 명령을 통해서 접근된다.

; index 변경하기

db.a.ensureIndex({"a":1}, {"background":true})
를 통해 새로운 index를 db를 운영하는 중간에 생성할 수 있다.

index 생성은 시간을 잡는 작업이다. 위와 같이 하면, background로 작업되며,
요청되는 request들도 처리된다. 만일 해당 option이 없다면, db는 block된다.
물론 block되면 작업은 빨리 끝난다.

dropIndexes로 index를 삭제할 수 있다.
가끔 system.indexes를 통해 index name으로 확인해 봐야 한다.

db.a.dropIndexes()
 ; 모든 index 삭제

db.a.dropIndex({"a":1, "b":-1})
 ; index 삭제

db.runCommand({"dropIndexes":"foo", "index":{"a":1}})
 ; foo collection에서 {"a":1} index 삭제
db.runCommand({"dropIndexes":"foo", "index":"*"})
 ; foo collection에서 모든 index 삭제