DataBase/MongoDB

MongoDB Compound Index 제대로 이해하고 성능까지 비교해보자.

누구세연 2025. 6. 21. 15:33

NoSQL은 유연한 스키마와 수평 확장이 강점인 데이터베이스입니다.
하지만 대용량 데이터를 효과적으로 처리하고, 원하는 쿼리 성능을 확보하기 위해서는 관계형 데이터베이스와 마찬가지로 인덱스 설계가 매우 중요하다고 생각합니다.

특히, 하나의 조건만으로는 부족할 때 여러 필드를 동시에 고려할 수 있는 `Compound Index`(복합 인덱스)는 매우 강력한 도구가 될 수 있습니다.

 


 

 

Compound Index(복합 인덱스) 란? 🤔

복합 인덱스는 말 그대로 두 개 이상의 필드를 조합해서 만든 인덱스입니다.

MongoDB에서는 다음과 같이 생성할 수 있습니다.

db.order.createIndex({ userId: 1, orderDate: -1 })

(이 인덱스는 userId를 오름차순, orderDate를 내림차순으로 정렬한 구조입니다. )

 

 

이 복합 인덱스는 다음과 같은 쿼리에 효율적으로 활용됩니다.

단일 인덱스는 하나의 필드에만 인덱스를 적용하는 반면, 복합 인덱스는 여러 필드를 동시에 고려하므로 더 많은 쿼리 조건을 커버할 수 있습니다.

 

 

인덱스 유무에 따른 쿼리 성능 비교 📖

위의 인덱스 개념과 적용하는 방법을 확인했다면 직접 성능을 비교해 보겠습니다!

 

먼저 userId가 동일한 데이터를 10000개 정도 생성해 보겠습니다.

 

그리고 다음과 같은 쿼리를 기준으로 성능을 비교해 봤습니다.

db.order.find({ userId: 12345 }).sort({ orderDate: -1 }).explain("executionStats")

 

많은 필드들이 노출될 텐데 성능을 비교하기 위해서는 아래의 필드를 위주로 확인해 주시면 좋습니다.

필드 의미 인덱스 관련 여부 판단
executionStats.totalDocsExamined 실제로 검사한 도큐먼트 수 많으면 인덱스 미사용 가능성 큼
executionStats.totalKeysExamined 인덱스를 통해 검사한 키 수 값이 크면 인덱스 사용됨
queryPlanner.winningPlan.stage 루트 연산 단계 IXSCAN이면 인덱스 사용, COLLSCAN이면 Collection 전체 스캔
queryPlanner.winningPlan.inputStage.stage 하위 단계에서 어떤 작업이 있었는지 SORT, COLLSCAN, IXSCAN 등
executionStats.executionTimeMillis 실제 쿼리 실행 시간 최적화 전후 비교에 유용

 

인덱스가 적용되기 전의 출력된 결과들을 확인해 보면 아래와 같습니다.

"stage": "SORT",
"inputStage": {
  "stage": "COLLSCAN",
  "filter": {
    "userId": {
      "$eq": 12345
    }
  },
  "docsExamined": 10001
}

 

  • COLLSCAN: 인덱스를 사용하지 않고 전체 컬렉션을 스캔 중이라는 뜻
  • SORT: 정렬도 인덱스로 하지 못해서 별도로 정렬을 수행하는 중
  • docsExamined: 10001: 1만 건을 직접 읽었다는 뜻으로, 인덱스 없다는 걸 방증
  • totalKeysExamined: 0: 인덱스를 전혀 사용하지 않았다는 걸 의미
  • executionTimeMillis: 33: 이걸 기준으로 인덱스 적용 전/후 성능 차이를 비교하면 좋음.
COLLSCAN과 SORT가 함께 등장하고 totalKeysExamined가 0이라면 → 인덱스 미사용!

 

이제 인덱스를 적용 후 다시 explain을 실행해 보겠습니다.

"stage": "FETCH",
"inputStage": {
  "stage": "IXSCAN",
  "indexName": "userId_1_orderDate_-1",
  "indexBounds": {
    "userId": ["[12345, 12345]"],
    "orderDate": ["[MaxKey, MinKey]"]
  },
  "keysExamined": 10001
},
"totalKeysExamined": 10001,
"totalDocsExamined": 10001,
"executionTimeMillis": 37

 

  • IXSCAN: 인덱스를 이용해 빠르게 원하는 데이터를 찾아냄
  • 정렬 생략 가능: 인덱스에 정렬 조건이 포함되어 있으면 SORT 단계가 제거됨
  • FETCH 단계는 실제 문서를 가져오는 작업 (IXSCAN으로 찾은 후)
정렬 조건까지 포함된 복합 인덱스가 잘 활용되었다는 뜻!

 

 

Prefix 규칙에 주의 ⚠️

Prefix 규칙(접두어 규칙)은 MySQL을 포함한 대부분의 관계형 데이터베이스에서도 동일하게 적용되는 규칙입니다.

MongoDB의 복합 인덱스 또한 왼쪽부터 시작하는 접두(prefix) 규칙을 따릅니다.

즉, 인덱스의 앞에서부터 연속되는 필드에 대해서만 인덱스를 사용할 수 있습니다.

 

예를 들어 아래와 같은 인덱스가 있다고 가정합시다!

db.products.createIndex({ category: 1, brand: 1, price: -1 })

이 인덱스는 다음 쿼리에서 사용할 수 있습니다:

  • find({ category: "shoes" })
  • find({ category: "shoes", brand: "Nike" })
  • find({ category: "shoes" }). sort({ brand: 1 })
  • find({ brand: "Nike" }) (category 없이 brand만 쓰면 인덱스를 못 씀)

 

 

복합 인덱스의 주의점⁉️

복합 인덱스는 원하는 쿼리 성능을 위해 사용하기에는 매우 좋은 기술이지만 주의하여 사용하는 것이 좋습니다.

 

❕ 너무 많은 복합 인덱스는 오히려 독이다

인덱스는 디스크에 저장되며, CRUD 작업마다 갱신되므로 너무 많으면 성능 저하 발생합니다.

 

사례: 여러 필드 조합마다 인덱스를 만들면 write latency가 증가하고, 메모리도 차지

권장사항: getIndexes()로 불필요한 인덱스를 주기적으로 정리!

db.orders.getIndexes()
db.orders.dropIndex("userId_1_orderDate_-1") // 필요 없으면 삭제

 

 

필드 순서에 따라 정렬 성능이 갈릴 수 있다

인덱스는 필드 순서대로 정렬되어 저장됩니다. orderDate 정렬이 필요하다면 필드 순서를 반영해야 합니다.

// 정렬 포함된 쿼리를 빠르게 처리하려면
db.orders.createIndex({ userId: 1, orderDate: -1 })
  • ✅ find({ userId: 123 }).sort({ orderDate: -1 }) → 정렬도 인덱스로 커버 가능
  • ❌ sort({ orderDate: -1 })만 존재 → COLLSCAN 후 정렬 발생

권장사항: 정렬 조건도 인덱스 설계에 포함하기!

 

 

  다중 필드 정렬이 필요한 경우 꼭 복합 인덱스로

MongoDB는 하나의 쿼리에 대해 하나의 인덱스만 사용할 수 있습니다.

find({ status: "SHIPPED" }).sort({ createdAt: -1 })

 

  • status와 createdAt 각각 단일 인덱스 있어도 둘 다 적용 불가능

권장사항: 자주 사용하는 조건 + 정렬 조합을 복합 인덱스로 설계하세요!

 

 

커버링 인덱스를 노린다면 필드 선택에 주의

쿼리에서 사용하는 모든 필드가 인덱스에 포함되어야 디스크 I/O 없이 결과를 리턴할 수 있습니다.

db.orders.createIndex({ userId: 1, orderDate: -1, status: 1 })
  • ✅ find({ userId: 123 }, { status: 1, orderDate: 1, _id: 0 }) → 커버링 인덱스 가능

 

권장사항: project 대상 필드도 인덱스에 포함시켜야 커버링 가능!

 


 

 

요약
1. 인덱스는 왼쪽부터 시작하는 필드 순서가 중요하다.
2. 정렬 조건까지 고려한 인덱스를 설계해야 한다.
3. explain()으로 인덱스 사용 여부를 확인하자.
4. 실제 성능 차이를 비교해 보고 도입하자.