인덱싱(Indexing)
몽고DB의 인덱스는 책의 인덱스(=색인)과 유사하다. 인덱스를 사용하면 효율적으로 쿼리할 수 있다.
- 컬렉션 스캔(collection scan) : 인덱스를 사용하지 않는 쿼리, 전체를 읽는 방식
- 전형적인 관계형 데이터베이스의 인덱스와 거의 동일하게 작동한다.
- 인덱스는 모든 값을 정렬된 순서로 보관한다. 인덱스의 순서 또한 이부분에 영향을 가짐으로 정렬 최적화시 순서를 고려해야한다.
// 많은 양을 갖는 도큐먼트 삽입
for(i=0;i<100000;i++){
db.users.insertOne({
"i":i,
"username":"user"+i,
"created":new Date()
});
}
// 1. 인덱스 없이 검색
// explain("executionStats") 모드로 현재 인덱스 없이 전체 조회를 하는 것을 확인 할 수 있다.
> db.users.find({"username":"user101"}).explain("executionsStats")
// 인덱스 생성, username으로 인덱스 생성
> db.users.createIndex({"username":1})
// 2. username 인덱스가 존재하는 상태로 검색
//executionsStat의 실행 시간이 많이 줄어든 것을 확인 할 수 있다.
> db.users.find({"username":"user101"}).explain("executionsStats")
- 몽고DB는 실행하는 쿼리 종류에 따라 인덱스를 사용하는 방법이 다르다.
- 단일 값을 찾는 동등 쿼리(equality query)
# 일치하는 마지막 항목부터 순서대로 탐색
> db.${collection_name}.find(${filter}).sort({${column_name} : -1})
- 여러 값이 일치하는 도큐먼트를 찾아내는 범위 쿼리
# 범위에 따른 값
> db.${collection_name}.find({${column_name}:{${비교 연산자}:${비교 값}}})
- 정렬을 포함하는 다중값 쿼리(multivalue query)
# 정렬을 포한한 다중값 처리
# age를 기준으로 정렬된 순서로 포함하여 결과를 내는 것이 아닌, 이미 몽고 DB가 정렬된 인덱스를 메모리에서 정렬하게된다.
# 정렬된 값 -> 데이터 도출 -> sort로 정렬 == 비효율!
> db.${collection_name}.find({${column_name}:{${비교연산자}:${비교값}, ${비교연산자}:${비교값}}).sort({${column_name}:1})
- 몽고DB는 직접 인덱스를 선택하는 방법을 가지고 있다.
1. 쿼리의 모양(qeury shape)을 확인한다. 쿼리 모양은 검색할 필드와 정렬 여부 등 추가 정보와 관련있다.
2. 쿼리 모양에 따라 선택된 인덱스 후보들로 쿼리 플랜(query plan)을 만들고 각각 병렬 스레드에서 쿼리를 실행한다.
3. 빠른 실행 혹은 빠른 정렬을 보인 인덱스를 선택한다.
4. 3번의 경쟁에서 승리한 쿼리 플랜은 차후 동일한 쿼리 모양에서 사용되기 위해 캐시에 저장된다.
- 인덱스를 잘 설계하면 인메모리 정렬을 피할 수 있다. 이를 통해서 데이터셋 크기와 시슽메 부하와 관련해 보다 쉽게 확장할 수 있다.
- 복합 인덱스를 설계 주의 사항
- 동등 필터에 대한 키를 맨 앞에 표시
- 정렬에 사용되는 키는 다중값 필드 앞에 표시
- 다중값 필터에 대한 키는 마지막에 표시
- 단일 키일 경우에는 인덱스 방향은 상관 없으나 다중 키로 정렬 할 경우 인덱스의 방향을 고려해야한다.
- 인덱스가 쿼리가 요구하는 값을 모두 포함하면 쿼리가 커버드 된다고 한다. 커버드 쿼리(covered query)를 사용하면 작업 셋(working set)을 작게 만들 수 있다.
- 복합 인덱스는 복합적으로 사용될 수 있고, 쿼리마다 다른 각각의 인덱스처럼 동작 할 수 있다. 하지만, 모든 서브 셋에 적용되지는 않는다. 순서나 구조가 다르면 적용되지 않는다.
$ 연산자의 인덱스 사용법
- 일반적으로 부정 조건은 비효율적이다.
- 몽고 DB는 쿼리당 하나의 인덱스만 사용할 수 있다. 유일한 예외는 $or이다. $or은 두개의 쿼리를 수행하고 결과를 합친다.
객체 및 배열의 인덱싱
- 인덱스는 일반적인 키에 생성될 때와 동일한 방식으로 내장 도큐먼트 키에 생성될 수 있다. 서브도큐먼트 전체를 인덱싱하면, 서브도큐먼트 전체에 대해 쿼리할때만 도움이 된다. (내부 값 정렬 최적화X)
- 배열에도 인덱스를 생성할 수 있다. 배열에서 인덱스를 사용하면 배열의 특정 요소를 효율적으로 찾을 수 있다. 도큐먼트 인덱싱과 다르게 단일 개체처럼 인덱싱 할 수 없다.
- 배열 필드 인덱싱은 배열 자체가 아니라 배열의 각 요소를 인덱싱한다.
- 배열의 특정 항목에 인덱스를 생성할 수 있다.
- 배열 필드를 인덱스 키로 가지면 인덱스는 다중키 인덱스로 표시된다. 필드 내의 배열을 포함하는 도큐먼트가 제거되더라도 비다중키로 변환할 수 없다. 비다중키가 되려면, 인덱스를 삭제하고 다시 생성해야한다
- 다중키 인덱스는 비다중키 인덱스보다 느릴 수 있다. 느린 속도를 보완하기 위해서 중복을 제거해야한다.
인덱스 카디널리티(index cadinality)
- 카디널리티는 컬렉션의 한 필드에 대해 교유값이 얼마나 많은지 나타낸다.
- 카디널리티가 높을 수록 인덱싱에 도움이 된다.
explain
explain은 쿼리에 대한 많은 정보를 제공하며, 느린 쿼리를 위한 중요한 진단 도구이다.
executionTimeMillis | 요청 받고 응답을 보낸 시점까지 실행 시간(모든 플랜 소요 시간) |
isMultiKey | 다중 인덱스 사용 여부 |
nReturned | 쿼리에 의해 반환된 도큐먼트 개수 |
totalDocsExamined | 디스크내 실제 도큐먼트를 가리키는 인덱스 포인터를 따라간 횟수 |
totalKeysExamine | 인덱스가 사용되었다면 인덱스 항목 개수, 테이블 스캔을 했다면 조사한 도큐먼트 개수 |
stage | 인덱스를 사용하여 쿼리할 수 있는지 여부(colscan : 인데스 쿼리할 수 없음, ixscan:인덱스 쿼리 가능) |
needYields | 쓰기 요청을 처리하도록 쿼리가 양보(일시중지)한 횟수 |
indexBounds | 인덱스가 어떻게 사용되었는지에 대한 내용과 탐색한 인덱스 범위 제공 |
- hint를 사용하여 특정 인덱스를 사용하도록 강제할 수 있다.
# hint를 사용하여 {"username": 1, "age":1} 인덱스 사용을 강제한다.
> db.users.find({"age":14,"username": /.*/}).hint({"username": 1, "age":1})
인덱스를 생성하지 않는 경우
- 컬렉션에서 가져와야 하는 부분이 많을 수록 인덱스는 비효율적이다.
- 작은 컬렉션, 도큐먼트의 경우, 컬렉션 스캔이 적합하다
인덱스 종류
- 인덱스를 구축할 때 인덱스 옵션을 지정하여 동작 방식을 바꿀 수 있다.
- 고유 인덱스(unique index)는 각 값이 인덱스에 최대 한번 나타나도록 보장한다.
- 여러 도큐먼트에서 특정 키에 동일한 값을 가질 수 없도록 하려면, 특정 키 필드가 있는 도큐먼트에 대해서만 partialFilterExpression으로 고유 인덱스를 만들 수 있다.
> db.${collection_name}.createIndex({${key_name}: 1},
{"unique" : true, "paritialFilterExpression":{ ${key_name}:{$exists:true}}})
- 도큐먼트에 키가 존재하지 않으면 인덱스는 그 도큐먼트에 대한 값을 null로 저장한다.
- 인덱스 버킷(index bucket)은 크기 제한이 있으며 이로 인해 어떠한 경우에는 값이 인덱싱 되지 않는다.
- 기존 컬렉션에 고유 인덱스를 구축할때, 중복된 값이 있으면 실패한다. 중복을 꼭 제거하자!
- 복합 고유 인덱스(compound unique index)는 개별 키를 같은 값으로 가질 수 있지만 인덱스 항목의 모든 키에 걸친 값의 조합은 딘덱스에서 최대 한번만 나타난다.
- 오직 키가 존재할 때만 고유 인덱스가 적용 하도록 부분 인덱스를 만들 수 있다. partialFilterExpression 옵션을 포함하여 부분인덱스를 만들 수 있다.
- 부분 인덱스는 반드시 고유할 필요는 없다. unique 옵션을 제외시켜 고유성을 뺄 수 있다.
- 쿼리는 부분 인덱스의 사용 여부에 따라 다른 결과를 반환 할 수 있다. 해당 내용을 숙지하고 인덱스를 생성하고 사용해야한다.
인덱스 관리
- createIndex를 통해 인덱스를 생성할 수 있다.
- 데이터베이스의 모든 인덱스 정보는 system.indexes 컬렉션에 저장된다.(예약된 컬렉션)
- 예약된 컬렉션은 도큐먼트를 직접 조작할 수 없다.
- createIndex, createIndexes, dropIndexes, getIndexex 명령어로 인덱스 관리 내용을 조작할 수 있다.
# 특정 컬렉션의 모든 인덱스 정보 조회
> db.${collection_name}.getIndexes()
- getIndexex 메소드를 이용하여 확인하는 필드 값 중 "v"는 내부적으로 인덱스 버저닝(index versioning)에 사용된다.
- 컬렉션 내 각 인덱스는 고유하게 식별하는 이름이 있다. (=인덱스 명) 기본적으로 키명1_방향1_키명2_방향2_..._키명N_방향N 이다.
- 인덱스 명이 너무 길어질 것 같다면, createIndex 옵션으로 원하는 이름으로 설정할 수 있다.
- dropIndexes명령을 사용하여 불필요한 인덱스를 제거할 수 있다.
- 몽고DB 4.2 이후 버전에서는 인덱스를 빠르게 구축하기 위하여, 인덱스 구축이 완료될때 까지 읽기와 쓰기를 중단시킨다.
- 인덱스 구축동안 읽기/쓰기를 어느정도 응답하게 하려면 인덱스 구축 시, background 옵션을 사용한다. 이 옵션은 인덱스 구축이 다른 작업에 양보할 수 있도록 강제한다.
- 백그라운드 인덱싱은 포그라운드 인덱싱보다 훨씬 느리다.
- 몽고DB 4.2는 하이브리드 인덱스 구축이라는 새로운 접근 방식을 도입하였다. 인덱스 구축 프로세스의 시작과 끝만 락을 가지고 나머지 부분에서는 읽기/쓰기 작업을 인터리빙 한다.
참고
도서 - 몽고DB 완벽 가이드 3판
'컴퓨터 > database' 카테고리의 다른 글
[MongoDB] 몽고DB 후다닥 시작하기 - 7 (복제) (0) | 2022.07.14 |
---|---|
[MongoDB] 몽고DB 후다닥 시작하기 - 6 (집계 프레임워크) (0) | 2022.07.10 |
[MongoDB] 몽고DB 후다닥 시작하기 - 3 (0) | 2022.06.26 |
[MongoDB] 몽고DB 후다닥 시작하기 - 2 (0) | 2022.06.26 |
[MongoDB] 몽고DB 후다닥 시작하기 - 1 (0) | 2022.06.24 |