티스토리 뷰
인덱싱
인덱스를 사용하면 효율적으로 쿼리할 수 있다. 인덱스를 사용하지 않는 쿼리를 컬렉션 스캔(collection scan)이라 하며, 서버가 쿼리 결과를 찾으려면 '전체 내용을 살펴봐야 함'을 의미한다.
> db.col3.find({ a: 3 }).explain("executionStats")
{
explainVersion: '1',
queryPlanner: {
namespace: 'test.col3',
parsedQuery: {
a: {
'$eq': 3
}
},
// ...
executionStats: {
executionSuccess: true,
nReturned: 59, // 반환받은 결과의 개수
executionTimeMillis: 82, // 걸린 시간
totalKeysExamined: 0,
totalDocsExamined: 105166, // 도큐먼트를 읽은 개수
executionStages: {
isCached: false,
stage: 'COLLSCAN',
filter: {
a: {
'$eq': 3
}
},
// ...
}
컬렉션을 쿼리할 때 explain 함수를 이용해 쿼리가 실행될 몽고DB가 무엇을 하는지 확인할 수 있다. explain는 명령을 감싸는 커서 보조자 메서드(cursor helper method)와 사용하면 좋다. executionStats 모드는 인덱스를 이용한 쿼리의 효과를 이해하는 데 도움이 된다.
위처럼 "executionStats" 필드의 값인 중첩된 도큐먼트를 살펴보면 "totalDocsExamined"를 통해 몽고DB가 쿼리를 실행하면서 살펴본 도큐먼트 개수를 알 수 있다. 인덱스를 사용하지 않았기 때문에 컬렉션에 들어있는 모든 도큐먼트 개수와 같다.
따라서 몽고DB가 쿼리에 효율적으로 응답하려면 애플리케이션이 요구하는 모든 쿼리 패턴(query pattern)에 인덱스를 사용해야 한다.
> db.col3.createIndex({ a: 1 })
> db.col3.createIndex({ a: 1, b: 1 }) // 2개 이상의 필드로 구성된 복합 인덱스 가능
> db.col3.find({ a: 3 }).explain("executionStats")
{
executionStats: {
executionSuccess: true,
nReturned: 59,
executionTimeMillis: 0,
totalKeysExamined: 59,
totalDocsExamined: 59,
// ...
}
}
a 필드에 인덱스를 만들면 위처럼 totalDocsExamined, executionTImeMilles가 확연히 줄어든 것을 확인할 수 있다. 하지만 인덱싱된 필드를 변경하는 쓰기(삽입, 갱신, 삭제) 작업은 더 오래 걸리기 때문에 어떤 필드가 인덱싱하기에 적합한지 신중히 파악해야 한다.
몽고DB가 인덱스를 선택하는 방법

위 쿼리 플래너 로직처럼 쿼리 플래너는 주어진 쿼리에 대해 사용 가능한 인덱스를 고려하여 가장 효율적인 쿼리 계획을 선택하고 캐시한다. 쿼리 계획의 효율성을 평가하기 위해 쿼리 플래너는 쿼리를 충족하는데 사용할 인덱스 후보 집합을 식별하고 각 인덱스 후보에 쿼리 플랜(query plan)을 만들고, 각각 다른 인덱스를 사용하는 병렬 스레드에서 쿼리를 실행한다. 쿼리 스레드가 레이스에서 이기려면, 모든 쿼리 결과를 가장 먼저 반환하거나 결과에 대한 시범 횟수를 정렬 순서로 가장 먼저 반환해야 한다. 인메모리 정렬을 하면 비용이 많이 들기 때문에 정렬 순서는 중요한 부분이다. 즉, 평가 기간 동안 가장 적은 양의 작업을 수행하면서 가장 많은 결과를 생성하는 쿼리 계획을 채택한다.
서버는 쿼리 플랜의 캐시를 유지하는데, 승리한 플랜은 차후 모양이 같은 쿼리에 사용하기 위해 캐시에 저장한다.
복합 인덱스 사용
db.students.find({ student_id: { $gt: 500000 }, class_id: 54 })
.sort({ final_grade: 1 })
.explain("executionStats")
db.students.createdIndex({ class_id: 1, final_grade: 1, student_id: 1 })
2개 이상의 키로 구성된 복합 인덱스를 올바르기 설계하기 위해 인덱스의 선택성(selectivity)를 고려할 수 있다. 특정 쿼리 패턴에서 스캔할 레코드 개수를 인덱스가 얼마나 최소화하는지 중요하다. 즉, 복합 인덱스를 설계할 때 일반적으로 트레이프오프가 있다.
위 쿼리는 class_id가 54이면서 student_id가 500000보다 크고, 최종 성적을 기준으로 정렬하는 모든 레코드를 요청한다. 새로운 복합 인덱스의 키는 [class_id, final_grade, student_id]와 같이 정렬되어야 한다. 정렬 구성 요소는 동등 필터 바로 뒤, 다중값 필터 앞에 포함한다. 이 인덱스는 쿼리에서 고려하는 키 집합을 매우 선택적으로 좁힌다. 그런 다음 몽고DB는 인덱스의 동등 필터와 일치하는 키 3개를 통해 다중값 필터와 일치하는 레코드를 식별한다. 해당 레코드는 최종 성적에 따라 오름차순으로 정렬된다.
이 복합 인덱스는 몽고DB가 결과 셋에 포함될 도큐먼트보다 더 많은 도큐먼트의 키를 검사하지만 인덱스를 사용해 도큐먼트를 정렬함으로써 실행 시간을 절약할 수 있다.
복합 인덱스에 대한 설계를 설계할 때 고려할 점을 요약하면 아래와 같다.
- 동등 필터에 대한 키를 맨 앞에 표시해야 한다.
- 정렬에 사용된느 키는 다중값 필드 앞에 표시해야 한다.
- 다중값 필터에 대한 키는 마지막에 표시해야 한다.
객체 및 배열 인덱싱
{
"username": "sid",
"loc": {
"ip": "1.2.3.4",
"city": "Springfield",
"state": "NY"
}
}
db.users.createIndex({"loc.city": 1})
몽고DB는 도큐먼트 내부에 도달해서 내장 필드와 배열에 인덱스를 생성하도록 허용한다. 내장 객체와 배열 필드는 복합 인덱스에서 최상위 필드와 결합될 수 있으며, 다소 특수한 경우를 제외하면 대부분 '일반적인' 인덱스 필드와 같은 방식으로 동작한다. 위 도큐먼트처럼 "loc"의 서브필드(subfield)에 인덱스를 만들어 해당 필드를 이용하는 쿼리의 속도를 높일 수 있다.
카디널리티
카디널리티(cardinality)는 컬렉션의 한 필드에 대해 고윳값(distinct value)이 얼마나 많은지 나타낸다. 일반적으로 필드의 카디널리티가 높을수록 인덱싱이 더욱 도움이 된다. 인덱스가 검색 범위를 훨씬 작은 결과 셋으로 빠르게 좁힐 수 있기 때문이다. 일반적으로, 낮은 카디널리티 필드에서 인덱스는 높은 카디널리티 필드에서만큼 일치하는 항목을 많이 제거할 수 없다.
인덱스를 생성하지 않는 경우
인덱스가 항상 쿼리 성능을 높여주는 것은 아니다. 실제 효과는 데이터 크기, 인덱스 크기, 도큐먼트의 크기, 결과 집합의 평균 크기 등 여러 요소에 따라 달라진다. 일반적으로 매칭되는 도큐먼트의 비율이 낮을수록(=선택도가 높을수록) 인덱스가 유리하고, 반대로 컬렉션의 상당 부분을 반환하는 쿼리라면 단순 컬렉션 스캔이 더 효율적일 수 있다.인덱스 사용 여부는 explain()을 통해 실제 실행 계획을 확인하면서 판단하는 것이 좋다.
인덱스 종류
인덱스를 구축할 떄 인덱스 옵션을 지정해 동작 방식을 바꿀 수 있다.
고유 인덱스

고유 인덱스는 각 값이 인덱스에 최대 한 번 나타나도록 보장한다. 예를 들어, 여러 도큐먼트에 firstname 키에 동일한 값을 가질 수 없도록 하려면 firstname 필드가 있는 도큐먼트에 대해서만 partialFilterExpression으로 고유 인덱스를 만들면 된다. 위처럼 users 컬렉션에 도큐먼트를 삽입하면 중복 키 예외(duplicate key exception)를 발생시킨다.
_id의 인덱스는 컬렉션을 생성하면 항상 자동으로 생성되고, 다른 고유 인덱스와 달리 삭제할 수 없다는 점을 제외하면 일반적인 고유 인덱스이다.
부분 인덱스

고유 인덱스는 null을 값으로 취급하므로, 키가 없는 도큐먼트가 여러 개인 고유 인덱스를 만들 수 없다. 하지만 오직 키가 존재할 때만 고유 인덱스가 적용되도록 할 때가 많다. 부분 인덱스를 만들려면 "partialFilterExpression" 옵션을 포함시킨다. 부분 인덱스를 생성하려면 필터 표현식(filter expression)을 나타내는 도큐먼트와 함께 희소 인덱스가 제공하는 기능의 슈퍼셋(superset)을 나타낸다.
쿼리는 부분 인덱스 사용 여부에 따라 다른 결과를 반환할 수 있다. 위처럼 컬렉션에 대부분의 도큐먼트가 x 필드를 가지지만, 한 도큐먼트만 x를 가지지 않을때 x에 부분 인덱스를 생성하여 인덱스를 사용하면 { _id: 0 } 도큐먼트를 반환하지 않는다.
'DB > MongoDB' 카테고리의 다른 글
| MongoDB (0) | 2025.10.06 |
|---|---|
| MongoDB 트랜잭션 (0) | 2025.10.06 |
| 몽고DB CRUD (0) | 2025.09.17 |
- Total
- Today
- Yesterday
- 넥스트스탭
- nginx configuration
- annotation
- mdcfilter
- 람다
- 트랜잭션
- Java
- postgresql
- sql
- socket
- nginx
- Synchronized
- pessimistic lock
- mysql
- TDD
- 구름톤 챌린지
- redis session
- jvm 메모리 구조
- Kafka
- spring webflux
- transaction
- spring session
- 비관적 락
- Redisson
- NeXTSTEP
- 카프카
- 낙관적 락
- 분산 락
- 구름톤챌린지
- EKS
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
