티스토리 뷰
728x90
반응형
5.1 인덱싱 소개
- 인덱스를 사용하지 않는 쿼리를 컬렉션 스캔이라 함
- explain 커서 메서드는 CRUD 작업의 실행 정보 제공
- executionStats 모드는 인덱스를 이용한 쿼리의 효과를 이해하는 데 도움
- totalDocsExamined : 쿼리를 실행하면서 살펴본 도큐먼트 개수
- totalKeysExamined : 결과 셋을 생성하기 위해 인덱스 내에서 몇 개의 키를 통과했는지
- nReturned : 반환받은 결과의 개수
- 쿼리 패턴 : 애플리케이션이 데이터베이스에 요구하는 다양한 유형의 질문
> db.users.find({"username" : "user101"}).explain("executionStats")
{
"queryPlanner" : {
...
},
"executionStats" : {
"nReturned" : 1, //반환받은 결과의 개수
"executionTimeMillis" : 419, //쿼리하는 데 걸린 시간
"totalDocsExamined" : 1000000, //쿼리를 실행하면서 살펴본 도큐먼트 개수
}
}
5.1.1 인덱스 생성
> db.users.createIndex({"username" : 1})
- 인덱싱된 필드를 변경하는 쓰기(삽입, 갱신, 삭제) 작업은 더 오래 걸린다
- 데이터가 변경될 때마다 도큐먼트뿐 아니라 모든 인덱스를 갱신해야 하기 때문
- 몽고DB 인덱스는 RDB 인덱스와 거의 동일하게 작동
- 자주 쓰는 쿼리와 빨리 수행해야 하는 쿼리를 조사해 공통적인 키 셋을 찾음
5.1.2 복합 인덱스 소개
- 인덱스는 모든 값을 정렬된 순서로 보관하므로 인덱스 키로 도큐먼트를 정렬하는 작업이 훨씬 빨리지게 함
- 인덱스가 앞부분에 놓일 때만 정렬에 도움이 됨
- 각 인덱스 항목은 나이와 사용자명을 포함하고 레코드 식별자를 가리킴
> db.users.createIndex({"age" : 1, "username" : 1})
[0, "user101"] -> 8363~
[0, "user102"] -> 8344~
...
[1, "user202"] -> 4331~
[1, "user420"] -> 2324~
단일 값을 찾는 동등 쿼리
- {"age" : 21} 과 일치하는 마지막 항목부터 순서대로 인덱스를 탐색
- 곧바로 정확한 키값으로 건너뛸 수 있으며 인덱스 탐색은 올바른 순서로 반환하므로 결과를 정렬할 필요 없음
- 몽고DB는 인덱스를 어느 방향으로도 쉽게 탐색하여 정렬 방향은 문제 없음
> db.users.find({"age" : 21}).sort({"username" : -1})
범위 쿼리
- 인덱스 순서에 따라 도큐먼트 결과를 반환
> db.users.find({"age" : {"$gte" : 21, "$lte" : 30}})
다중값 쿼리
- 인덱스는 사용자명을 정렬된 순서로 반환하지 않음 (age별 username 정렬)
- 쿼리는 사용자명 정렬된 결과를 요청 (find 결과에 username 정렬 처리)
- 결과를 반환하기 전에 메모리에서 정렬해야 함. 비효율적
- 결과가 32Mbyte 이상이면 정렬 거부하는 오류
- 복합 인덱스를 구성할 때는 정렬 키를 첫 번째에 놓으면 좋음
> db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username" : 1})
복합 인덱스 필드 순서 권고사항 (ESR Rule)
- 쿼리에서 가장 많이 사용되는 필드를 앞쪽에 배치
- 정렬이 필요한 경우, 정렬에 사용되는 필드를 먼저 배치
- 범위 쿼리에 사용되는 필드를 다른 필드보다 앞쪽에 배치
- 인덱스의 크기를 최소화하기 위해, cardinality가 높은 필드를 앞쪽에 배치
해당 필드에 중복된 값이 적은 경우에 인덱스의 크기가 작아지기 때문에 쿼리의 성능이 개선
복합 인덱스 필드 우선 순위
- 일치하는 필드: 쿼리에서 사용되는 필드와 동일한 필드를 가진 인덱스가 있으면 가장 높은 우선 순위를 가집니다. (동등 필터)
- 정렬 순서: 인덱스에 사용된 필드가 쿼리에서 정렬 순서를 지정하는데 사용되는 경우 해당 필드의 우선 순위가 높아집니다.
- 카디널리티(cardinality): 인덱스에 사용된 필드의 카디널리티가 높을수록 우선 순위가 높아집니다.
즉, 인덱스에 사용된 필드의 값이 서로 다른 경우 우선 순위가 높아집니다. - 인덱스 크기: 인덱스의 크기가 작을수록 우선 순위가 높아집니다. 인덱스가 작을수록 디스크 I/O를 줄일 수 있기 때문입니다.
- 사용 빈도: 인덱스에 대한 쿼리가 자주 실행될수록 우선 순위가 높아집니다.
- 다중값 필터 키는 마지막에 표시
5.1.3 몽고DB가 인덱스를 선택하는 방법
- 쿼리가 들어오면 몽고DB는 쿼리 모양을 확인
- 쿼리 모양은 검색할 필드와 정렬 여부 등 추가 정보와 관련
- 쿼리 후보로 인덱스를 식별 후 각 인덱스 후보에 쿼리 플랜을 만들고, 병렬 스레드에서 쿼리를 실행
- 플랜은 일정기간(시범기간) 동안 서로 경쟁
- 승리한 플랜은 차후 모양이 같은 쿼리에 사용하기 위해 캐시에 저장
- 인덱스를 다시 작성하거나, 추가, 삭제하면 플랜이 캐시에서 제거
5.1.4 복합 인덱스 사용
- 인덱스의 선택성 : 특정 쿼리 패턴에서 스캔할 레코드 개수를 인덱스가 얼마나 최소화하는지에 관심
- executionStats 내 winningPlan : 선정된 쿼리 플랜, rejectedPlan : 거부된 플랜
- explain 출력은 쿼리 플랜을 단계 트리로 표시
- 각 단계에는 하위 단계 개수에 따라 하나 이상의 입력 단계(input stage)가 있을 수 있다
- 입력 단계는 도큐먼트나 인덱스 키를 상위 단계에 제공
- "FETCH" 단계는 도큐먼트를 검색하고 클라이언트가 요청하면 일괄적으로 반환
- 쿼리 플랜에 "SORT" 단계가 표시된다면 결과 셋을 정렬할 때 인덱스를 사용할 수 없었으며 인메모리 정렬을 했다는 의미
특정 인덱스를 사용하도록 강제하는 방법
1. 커서 hint 메서드를 사용하여 사용할 인덱스를 지정
2.planCacheSetFilter 함수를 인덱스 필터와 함께 사용
> db.students.find({student_id : {$gt:500000}, class_id : 54})
.sort({student_id:1})
.hint({class_id:1})
.explain("executionsStats")
- 동등 필터를 사용할 필드가 다중값 필터를 사용할 필드보다 앞에 오도록 복합 인덱스 설계
- 인메모리 정렬을 피하려면 반환하는 도큐먼트 개수보다 더 많은 키를 검사해야 함 -> 도큐먼트를 정렬함으로써 시간 절약
- 인덱스를 사용해 정렬하려면 인덱스 키를 순서대로 살펴볼 수 있어야 함 -> 인덱스에 정렬 필드 포함
키 방향 선택하기
- 역방향 인덱스는 서로 동등함 (각 방향에 -1 곱하기)
- {age : 1, username : -1} == {age : -1, username : 1}
- 인덱스 방향은 다중 조건에 따라 정렬할 때만 문제가 됨
- 단일 키로 정렬하면 인덱스를 쉽게 역순으로 읽을 수 있음
커버드 쿼리 사용하기
- 쿼리가 단지 인덱스에 포함된 필드를 찾는다면 도큐먼트를 가져올 필요 없음
- 인덱스가 쿼리가 요구하는 값을 모두 포함하면 covered 된다고 함
- "_id" 필드를 반환받지 않도록 반환받을 키를 지정해야 함
암시적 인덱스
- {age : 1, username : 1} 로 인덱스를 가지면 {age : 1} 가질 때와 동일한 방법으로 정렬됨
- 인덱스가 N개의 키를 가진다면 키들의 앞부분은 공짜 인덱스
- 인덱스의 접두사를 이용하는 쿼리에만 적용 가능
- {a:1, b:1, c:1} -> {a:1}, {a:1, b:1}
5.1.5 $ 연산자의 인덱스 사용법
비효율적인 연산자
- 부정 조건은 비효율적
- "$ne" 쿼리는 인덱스를 사용하긴 하지만 잘 활용하지 못함
- "$ne"로 지정된 항목을 제외한 모든 전체 인덱스를 살펴봐야함
- "$not"은 기초적인 범위와 정규 표현식을 반대로 뒤집을 수 있음
- "$not"을 사용하는 쿼리는 테이블 스캔을 수행
- "$nin"은 항상 테이블 스캔 수행
범위
- 다중 필드로 인덱스를 설계할 때 완전 일치가 사용될 필드를 첫번째, 범위가 사용될 필드를 마지막에 설정
- {"age":1, "username":1} 인덱스를 사용한다면 곧장 "age":47로 건너뛰고 사용자명 범위 내에서 검색
> db.users.find({"age":47, "username":{"$gt":"user5", "$lt":"user8"}})
OR 쿼리
- 쿼리당 하나의 인덱스만 사용 가능
- 유일한 예외는 "$or". 두 개의 쿼리를 수행하고 결과를 합치므로 절마다 하나씩 인덱스 사용
- 두번 쿼리해서 결과를 병합하면 한 번 쿼리할 때보다 훨씬 비효율 (중복 제거해야함)
> db.foo.find({"$or" : [{"x":123}, {"y":456}]})
- 가능하면 "$or" 보다 "$in" 사용
- "$in" 쿼리는 정렬을 제외하면 반환되는 도큐먼트의 순서를 제어하는 방법 없음
- {"x": {"$in" : [1,2,3]}} 은 {"x" : {"$in" : [3,2,1]}} 와 동일한 순서로 도큐먼트 반환
5.1.6 객체 및 배열 인덱싱
- 다중키 인덱스(Multikey Index)는 배열이나 내장 문서 필드에 대한 인덱스를 생성하는 방법
- 다중키 인덱스를 사용하면 배열이나 내장 문서 필드의 개별 값에 대해 쿼리를 수행
내장 도큐먼트 인덱싱
- 서브 도큐먼트 전체를 인덱싱하면, 서브 도큐먼트 전체에 쿼리할 때만 도움
{
"username" : "sid",
"loc" : {
"ip" : "1.2.3.4",
"city" : "Springfield",
"state" : "NY"
}
}
> db.users.createIndex({"loc.city" : 1})
> db.users.find({"loc" :
{"ip" : "123.456",
"city" : Shelby,
"state" : NY
}}) //인덱스 적용
> db.users.find({"loc.city" : "Shelby"}) //인덱스 X
배열 인덱싱
- 배열을 인덱싱하면 배열의 각 요소에 인덱스 항목 생성
- 입력, 갱신, 제거 작업을 하려면 모든 배열 요소가 갱신돼야 하므로 배열 인덱스를 단일값 인덱스보다 더 부담
- 배열 전체를 단일 개체처럼 인덱싱 불가
- 배열 요소에 대한 인덱스에는 위치 개념이 없음 ("comments.4" 와 같이 특정 배열 요소를 찾는 인덱스 불가)
- 배열의 특정 항목에 인덱스 생성 가능
> db.blog.createIndex({"comments.10.votes" : 1}) //11번째 배열 요소를 쿼리할 때만 유용
- 인덱스 항목의 한 필드만 배열로부터 가져올 수 있음
- 가능한 요소 쌍이 모두 인덱싱되므로 도큐먼트마다 n*m 개의 인덱스 항목 생성
- {"x" : 1, "y" : 1} 인덱스가 있을 때
> db.multi.insert({"x" : [1,2,3], "y" : 1}) //x 배열, 정상
> db.multi.insert({"x" : 1, "y" : [4,5,6]}) //y 배열, 정상
> db.multi.insert({"x" : [1,2,3], "y" : [4,5,6]}) //x,y 배열 비정상
다중키 인덱스가 미치는 영향
- 다중키 인덱스가 사용됐다면 explain() 출력에서 "isMultiKey" : true
- 다중키로 표시되면 비다중키로 될 수 없고 인덱스를 삭제 후 재생성 해야함
- 다중키 인덱스는 비다중키 인덱스보다 느릴 수 있음
- 하나의 도큐먼트를 여러개의 인덱스 항목이 가리킬 수 있음
- 결과를 반환하기 전에 중복 제거해야함
5.1.7 인덱스 카디널리티
- cardinality는 컬렉션의 한 필드에 대해 고윳값이 얼마나 많은지 나타냄
- 필드의 카디널리티가 높을수록 인덱싱에 도움
- 인덱스가 검색 범위를 훨씬 작은 결과 셋으로 빠르게 좁힐 수 있기 때문에
- 복합 인덱스에서 높은 카디널리티 키를 낮은 카디널리티 키보다 앞에 둠
728x90
반응형
'DB > MongoDB' 카테고리의 다른 글
[MongoDB] 공간 정보 인덱스 (0) | 2023.04.26 |
---|---|
[MongoDB] 인덱싱2 (0) | 2023.04.18 |
[MongoDB] 쿼리 (0) | 2023.03.15 |
[MongoDB] 도큐먼트 생성, 갱신, 삭제 (0) | 2023.03.06 |
[MongoDB] 기본 (0) | 2023.03.06 |
반응형
300x250