티스토리 뷰

DB/MongoDB

[MongoDB] 인덱싱

snail voyager 2023. 3. 22. 00:25
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가 높은 필드를 앞쪽에 배치
    해당 필드에 중복된 값이 적은 경우에 인덱스의 크기가 작아지기 때문에 쿼리의 성능이 개선

복합 인덱스 필드 우선 순위

  1. 일치하는 필드: 쿼리에서 사용되는 필드와 동일한 필드를 가진 인덱스가 있으면 가장 높은 우선 순위를 가집니다. (동등 필터)
  2. 정렬 순서: 인덱스에 사용된 필드가 쿼리에서 정렬 순서를 지정하는데 사용되는 경우 해당 필드의 우선 순위가 높아집니다.
  3. 카디널리티(cardinality): 인덱스에 사용된 필드의 카디널리티가 높을수록 우선 순위가 높아집니다.
    즉, 인덱스에 사용된 필드의 값이 서로 다른 경우 우선 순위가 높아집니다.
  4. 인덱스 크기: 인덱스의 크기가 작을수록 우선 순위가 높아집니다. 인덱스가 작을수록 디스크 I/O를 줄일 수 있기 때문입니다.
  5. 사용 빈도: 인덱스에 대한 쿼리가 자주 실행될수록 우선 순위가 높아집니다.
  6. 다중값 필터 키는 마지막에 표시

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