Database/MongoDB

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

noahkim_ 2025. 4. 29. 08:01

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

 

1. 개요

컬렉션 스캔

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

 

기본 인덱스

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

 

예제) 기본 인덱스 생성

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

 

복합 인덱스

  • 복수개의 키로 구성된 인덱스
  • 모든 값을 정렬된 순서로 보관
  • ➡️ 정렬 쿼리 성능 향상
항목 설명 비고 / 핵심 포인트
선택성 (Selectivity) 인덱스 컬럼 값의 유니크 정도
선택성이 높을수록 성능 좋음
✅ 특정 값을 찾을 때 범위가 좁혀지므로 빠르게 조회됨
컬럼 구성 우선순위 인덱스 필드의 구성 순서 전략
1️⃣ 동등 필터 (선택성 높임)
2️⃣ 정렬 필드 (정렬 비용 제거)
3️⃣ 범위 필터 (스캔 대상 결정)
키 방향 선택 인덱스의 오름차순/내림차순 방향 설정
모든 키 방향은 동일하게 지정해야 효율적
역방향 인덱스끼리는 동등
커버드 쿼리 인덱스만 보고 결과를 생성하는 쿼리
문서 스캔 없음
 반환 필드는 인덱스 컬럼만 가능
_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})

 

예제) 조회

더보기
// 1) 동등 조건
db.students.find({ "loc.city": "Seoul" })

// 2) 범위/정렬
db.students.find({ "loc.city": { $gte: "M", $lt: "T" } }).sort({ "loc.city": 1 })

// 3) Covered query (필요 필드만 인덱스에 있을 때)
db.students.find(
  { "loc.city": "Seoul" },
  { _id: 0, "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(이름)