Database/MongoDB

[몽고DB 완벽 가이드] 5. 인덱싱

noahkim_ 2025. 4. 29. 08:01

크리스티나 초도로 , 섀넌 브래드쇼 , 오언 브라질 님의 "몽고DB 완벽 가이드" 책을 정리한 포스팅 입니다.

 

1. 인덱싱 소개

컬렉션 스캔

  • 인덱스를 사용하지 않는 쿼리
  • 컬렉션 풀스캔이 일어남

 

기본 인덱스

  • 자주 사용되는 쿼리와 빨리 수행되어야 하는 쿼리의 공통적인 키 셋에 적용

 

예제) 기본 인덱스 생성

더보기
db.users.createIndex({"name": 1}) # name 필드에 대해 인덱스 생성 (1: 오름차순, -1: 내림차순)

 

복합 인덱스

  • 복수개의 키로 구성된 인덱스
  • 모든 값을 정렬된 순서로 보관 (정렬 쿼리 성능 향상)
항목 설명 비고 / 핵심 포인트
선택성 (Selectivity) 인덱스가 얼마나 좁은 범위를 스캔하는지
선택성이 높을수록 좋음 (레코드 수 적을수록)
컬럼 구성 우선순위 인덱스 필드의 구성 순서 전략
① 동등 필터 (선택성 높임)
② 정렬 필드 (정렬 비용 제거)
③ 범위 필터 (스캔 대상 결정)
키 방향 선택 인덱스의 오름차순/내림차순 방향 설정
- 정렬에 영향을 줌
- 모든 키 방향은 동일하게 지정해야 효율적
- 역방향 인덱스끼리는 동등
커버드 쿼리 인덱스만 보고 결과를 생성하는 쿼리
- 문서 스캔 없음
- 반환 필드는 인덱스에만 있어야 함
- _id는 명시적으로 꺼야 함 (_id: 0)
암시적 인덱스 복합 인덱스의 접두사(prefix) 부분만으로 쿼리 수행
- 왼쪽부터 순서대로만 사용 가능
- 중간 필드만 필터로 쓰면 인덱스 못 씀

 

예제) 복합 인덱스

더보기
# (age, name) 순서로 둘다 오름차순 정렬되어 저장된 인덱스
db.users.createIndex({ "age": 1, "name": 1 })
db.users.find({age: 37}).sort({name: -1}) # age=37 전체 범위를 인덱스로 읽고, 결과를 뒤집어 반환함.
db.users.find({age: {$gte: 37, $lte: 39}}) # age 37~39까지만 인덱스에서 읽음. 별도 정렬 작업 없음.
db.users.find({age: {$gte: 37, $lte: 39}}).sort({name: 1}) # (2)와 결과, 처리 방식 둘 다 동일.

 

예제) 키 방향 선택

더보기
db.users.createIndex({class_id: 1, final_grade: 1, student_id: 1})
db.students.find({student_id: {$gt: 500000}, class_id: 54}).sort({final_grade: 1})

 

예제) 커버드 쿼리

더보기
# covered query
db.students.find({name: "Curry"}, {name: 1, age: 1, _id: 0}) # _id값 명시적으로 꺼주기

 

예제) 암시적 인덱스

더보기
# implicit index
db.users.createIndex({ name: 1, age: 1, score: 1 })
db.users.find({ name: "Noah" })             ✅ 사용 가능
db.users.find({ name: "Noah", age: 25 })    ✅ 사용 가능
db.users.find({ name: "Noah", score: 90 })  ✅ 사용 가능 (score도 projection or sort일 경우)
db.users.find({ age: 25 })                  ❌ 사용 불가
db.users.find({ score: 90 })                ❌ 사용 불가

 

인덱스 선택 기준

항목 설명
쿼리 모양 확인
조건 필드와 정렬 필드 분석
쿼리 플랜 생성
인덱스 조합으로 가능한 실행 계획을 모두 테스트 (직접 백그라운드에서 병렬로 실행해봄)
최종 선택 여러 레이스로 경쟁시켜, 가장 높은 성능에 도달하는 쿼리를 선택

 

비효율적인 연산자

기준 $ne $not $nin
의미 같지 않음 조건 부정 주어진 목록에 없음
인덱스 활용성 거의 없음 거의 없음
없음 (항상 COLLSCAN)
비효율 이유 제외할 항목 외 모든 인덱스 항목 확인 필요 범위나 정규표현식 부정을 역으로 처리해야 함
모든 문서를 스캔해야 함

 


예제

더보기
db.users.find({age: {$ne: 30}})
db.users.find({age: {$not: {$gt: 30}}})
db.users.find({age: {$nin: [20, 25, 30]}})

 

OR 쿼리

항목 $or $in
조건 대상 여러 필드 가능 단일 필드만 가능
인덱스 사용 각 조건마다 인덱스 가능
단일 필드 인덱스만 사용
실행 횟수 조건 수만큼 쿼리 실행 한 번만 실행
일반 성능 보통 느림 (복잡도 ↑) 보통 빠름 (단순 쿼리)

 

예제

더보기
db.users.find({$or: [{age: 20}, {name: "noah"}]})
db.users.find({age: {$in: [20, 25, 30]}})

 

내장 도큐먼트 인덱싱

  • 일반적인 키에 생성될 떄와 동일한 방식으로 내장 도큐먼트 키에 생성될 수 있음
  • 내장 도큐먼트 전체를 인덱싱하면, 내장 도큐먼트 전체를 쿼리할 때만 도움이 됨

 

예제

더보기
db.students.createIndex({"loc.city": 1})

 

배열 인덱싱

  • 배열 요소마다 인덱스 작업이 수행되므로 성능이 떨어질 수 있음
  • 배열의 한 필드만 인덱싱할 수 있음 (전체 인덱싱 불가)
  • 배열 인덱스는 다중키 인덱스로 인식됨

 

인덱스 카디널리티

  • 카디널리티는 컬렉션의 한 필드에 대해 고윳값이 얼마나 많은지 나타냄
  • 카디널리티가 높을수록 인덱싱이 더 도움이 됨 (검색 범위를 훨씬 작게 좁힐 수 있음)
  • 복합 인덱스일 경우, 카디널리티가 높은 필드를 앞에 두면 도움이 됨

 

2. explain 출력

카테고리 항목 의미 설명
queryPlanner winningPlan 선정된 쿼리 플랜 정보  
rejectedPlans 실패한 쿼리 플랜 정보들  
executionStats nReturned 반환된 문서 수  
executionTimeMillis 전체 실행 시간  
totalKeysExamined 인덱스가 읽은 도큐먼트 수 0개: 인덱스 사용 안 함
totalDocsExamined 실제로 읽은 도큐먼트 수  
executionStages.stage 실행 계획 단계 - COLLSCAN: 전체 컬렉션 스캔 수행

executionStages.isMultiKey 다중키 인덱스 사용 여부  
executionStages.indexBounds
인덱스 사용 범위  
executionStages.docsExamined 단계 내 읽은 문서 수 실행 계획을 통해 읽은 문서 수

 

예시) 실행계획 확인

더보기
db.users.find().explain("executionStats")

 

힌트

예시) 인덱스 사용 강제

더보기
db.users.find().hint({name: 1, age: 1})

 

3. 인덱스를 생성하지 않는 경우

  • 인덱스는 컬렉션에서 가져와야 할 부분이 많을수록 비효율적임
  • 인덱스를 한번 사용하는데 두번의 조회가 발생하기 때문 (인덱스 서칭 - 인덱스 포인터를 통한 도큐먼트 서칭)
  • 가져와야 할 부분이 많으면 컬렉션 스캔이 성능이 덜 들수 있음

 

4. 인덱스 종류

고유 인덱스

  • 각 값이 인덱스에 최대 한 번 나타나도록 보장하는 인덱스
  • null을 값으로 취급함
  • 중복값으로 추가하거나 수정할 경우, duplicate key error 발생

 

예제

더보기
db.students.createIndex({student_id: 1}, {unique: true})
db.students.createIndex({student_id: 1, name: 1}, {unique: true})

 

부분 인덱스

  • 특정 조건을 만족하는 도큐먼트에만 적용되는 인덱스
  • 인덱스 크기 및 성능을 높일 수 있는 전략적 기능
  • 원하는 조건에 맞게 쿼리를 작성해야 효과를 볼 수 있음

 

예제

더보기
db.students.createIndex({email: 1}, {unique: true, partialFilterExpression: {status: "active"}})

db.students.find({status: "active", email: "user@example.com"})

 

5. 인덱스 관리

  • 인덱스는 system.indexes 컬렉션에 저장됨 

 

인덱스 정보

  • getIndexes(): 메타 정보 확인 가능 (버전, 키 구성, 이름)

 

인덱스 식별

  • 인덱스 이름은 '키명_방향' 의 조합으로 이루어짐
  • ex) age_1_name_1

 

인덱스 삭제

  • dropIndex(이름)