database

MongoDB Indexes Guide

MongoDB에서 인덱스(Indexes)는 데이터 검색 성능을 비약적으로 향상시키는 핵심 도구입니다. 잘 설계된 인덱스는 대량의 데이터 속에서도 빠르고 효율적인 쿼리를 가능하게 하지만, 부적절하거나 과도한 인덱스 사용은 오히려 성능 저하를 유발할 수 있습니다. 이 글에서는 MongoDB 인덱스의 구조와 사용법, 최적화 전략을 알아봅시다.

MongoDB 저장 방식

MongoDB에서 데이터는 인덱스(index)와 컬렉션(collection)이라는 두 가지 위치에 저장 할 수 있습니다. 첫째, 인덱스는 B-Tree 자료구조를 기반으로 정렬되어 저장되기 때문에, 인덱스 검색, 삽입, 삭제 시 효율적인 탐색 및 정렬을 제공합니다. 데이터가 추가되거나 수정될 때 인덱스는 자동으로 정렬 상태를 유지하며, 쿼리가 실행되면 이 인덱스를 탐색하여 데이터를 빠르게 찾습니다. 인덱스는 _id와 같은 기본 키를 포함하여 특정 필드에 설정할 수 있으며, 필요 시 개발자기 특정 필드에도 인덱스를 설정할 수 있습니다.


우리가 보통 Mongodb에서 데이터를 조회하는 부분이 컬렉션입니다. Mongodb는 큰 범위인 데이터베이스(Database)에 여러개의 컬렉션(Collection)이 존재하고, 컬렉션 안에는 Application에서 저장된 실제 데이터가 문서(Document) 형태로 포함되어 있습니다. MongoDB는 데이터를 BSON(Binary JSON) 형식으로 저장됩니다. BSON은 JSON의 확장 형식으로 더 많은 데이터를 지원합니다. 보통 컬렉션에 대한 조회가 필요하다면 쿼리를 작성하게 됩니다. 쿼리 작성 시 인덱스가 없다면 컨렉션의 모든 문서를 스캔하는 풀 스캔(Collection Scan)이 발생합니다. 따라서 효율적인 인덱스 추가로 검색 성능을 향상 시키는 것이 필수적이라 할 수 있습니다.

MongoDB 인덱스 활용

MongoDB의 인덱스는 단순히 필드의 정렬을 위한 도구가 아니라, 쿼리 성능을 높이는 중요한 장치입니다. 예를 들어, 배열 내부 데이터를 탐색하는 복잡한 쿼리에서는 $elemMatch 연산자를 사용하여 특정 조건을 가진 데이터를 조회한다고 해봅시다.

mongodb
db.collection.find({
  nestedArray: {
    $elemMatch: { subArray: { $elemMatch: { key: value } } },
  },
})

이처럼 다층 구조를 가진 데이터를 처리하려면 올바른 필드에 인덱스를 설정해야 하며, 그렇지 않으면 배열 전체를 스캔하는 비효율적인 작업이 발생할 수 있습니다. 쿼리 성능 분석을 아래와 같이 진행 할 수 있습니다.

MongoDB 쿼리 성능 분석

실무에서는 모든 쿼리에 .explain() 메서드를 사용하여 성능을 분석합니다. find().explain('executionStats') 명령을 통해 쿼리가 인덱스를 타고 있는지, 아니면 collScan(컬렉션 스캔)으로 실행되고 있는지를 확인할 수 있습니다. 특히, 아래의 주요 항목을 주의 깊게 살펴보아야 합니다.


  • nReturned: 반환된 문서 수.
  • totalKeysExamined: 읽은 인덱스 키 수.
  • totalDocsExamined: 읽은 문서 수.
  • executionTimeMillis: 쿼리 실행 시간.

totalDocsExamined에 비해 nReturned 값이 지나치게 작다면, 쿼리가 비효율적으로 실행되고 있음을 의미합니다. 이 경우 적절한 인덱스를 생성하여 쿼리를 최적화를 고려해야합니다. 최적화를 고려하지 않아도 되는 경우가 nReturned와 totalKeysExamined, totalDocsExamined의 수가 같은경우에 최적화가 되었다고 할 수 있습니다.

MongoDB 인덱스 기능

MongoDB는 다양한 상황에 적합한 여러 종류의 인덱스 기능을 제공합니다. 아래의 내용은 디테일한 설정 부분이므로, 가볍게 읽어보도록 합니다. 디테일한 성능 개선은 적합한 인덱스 설정을 진행한 이후에 고려되어야 할 사항들 입니다.

1. Unique Index

유니크 인덱스는 중복 데이터를 방지하며, 데이터 무결성을 보장합니다. 다만, null 값도 중복 체크의 대상이 되므로 두 개 이상의 null 값이 필요한 경우 예외 처리가 필요합니다.

mongodb
db.collection.createIndex({ field: 1 }, { unique: true })

2. Partial Index

특정 조건을 만족하는 문서에만 인덱스를 생성할 수 있습니다. 예를 들어, 상태 값이 active인 문서만 인덱스에 포함하려면 다음과 같이 설정합니다. Partial Index를 사용하려면 설정한 조건을 조회 시에도 적용해야합니다.

mongodb
db.collection.createIndex(
  { field: 1 },
  { partialFilterExpression: { status: 'active' } },
)

3. Sparse Index

필드가 없는 문서를 인덱스에서 제외할 수 있습니다. 기본적으로 null 값도 제외되므로 공간을 절약할 수 있습니다.

mongodb
db.collection.createIndex({ field: 1 }, { sparse: true })

인덱스 종류와 활용

MongoDB는 효율적인 데이터 검색과 성능 최적화를 위해 다양한 종류의 인덱스를 제공합니다. 각각의 인덱스는 특정 데이터 구조나 쿼리 패턴에 맞게 설계되었으며, 올바르게 활용하면 데이터베이스의 성능을 크게 향상시킬 수 있습니다. 이 글에서는 주요 인덱스 종류와 특징, 활용 방법에 대해 살펴보겠습니다.

1. Single-Field Index (단일 필드 인덱스)

Single-Field Index는 하나의 필드에만 인덱스를 생성하는 방식으로, 가장 기본적이고 널리 사용되는 인덱스 유형입니다. 이 인덱스는 지정된 필드의 값을 기준으로 데이터를 정렬하여 검색 속도를 높입니다. 단일 필드에만 적용되며, 값이 오름차순(1) 또는 내림차순(-1)으로 정렬됩니다. 필드의 데이터 유형과 값에 따라 인덱스의 크기와 성능이 결정됩니다. 객체 타입 필드에 인덱스를 설정할 때 주의해야 합니다. 객체 필드의 키 순서가 인덱스 값에 영향을 미칩니다.


{ a: 1, b: 2 }
{ b: 2, a: 1 }

예를 들어, 인덱스 생성 시 위의 두 객체는 동일한 객체로 취급되지 않습니다.

mongodb
// 단일 필드 인덱스 생성
db.collection.createIndex({ fieldName: 1 }) // 오름차순
db.collection.createIndex({ fieldName: -1 }) // 내림차순

단일 조건 검색의 사용 예시는 특정 사용자 ID를 검색하거나, 특정 날짜 이후의 데이터를 가져오는 경우나 정렬된 데이터를 빠르게 조회가 필요한 경우에 주로 사용됩니다.

2. Multikey Index (멀티키 인덱스)

Multikey Index는 배열 데이터를 처리하기 위해 설계된 인덱스로, 배열의 각 요소를 별도로 인덱싱합니다. 이를 통해 배열 필드를 포함하는 문서에서 보다 효율적으로 검색할 수 있습니다. 배열의 각 요소를 개별 값으로 인덱싱하기 때문에, 배열 필드를 포함하는 쿼리에서 뛰어난 성능을 발휘합니다. Multikey Index는 복합 인덱스에서 두 개 이상의 배열 필드에 동시에 적용할 수 없습니다. 이는 각 배열 요소의 조합이 폭발적으로 증가하여 성능 저하를 초래할 수 있기 때문입니다. 또한, 중첩 배열(배열 안의 배열)에 대해 Multikey Index를 생성할 수 없습니다.

mongodb
// 단일 필드 인덱스 생성
db.collection.createIndex({ arrayField: 1 })

일반적으로 문서(Document) 내부에 있는 배열 필드를 통해 사용합니다. 예를 들어, 사용자의 관심사나 태그(tag) 목록을 검색하거나, 부분 매칭으로 배열의 특정 요소가 주어진 값과 일치하는 문서를 찾을 때 사용하게 됩니다. Array Query Operators($elemMatch, $size, $all)와 관련된 연사자를 사용 시 쿼리 성능이 향상됩니다.

mongodb
// 배열 내 특정 요소 검색
db.collection.find({ arrayField: 5 })
 
// $elemMatch로 복합 조건 검색
db.collection.find({ arrayField: { $elemMatch: { a: 1, b: 2 } } })
 
// 배열의 크기를 조건으로 검색
db.collection.find({ arrayField: { $size: 3 } })

Multikey Index는 배열의 각 요소를 인덱싱하므로, 배열 크기에 따라 인덱스 크기가 증가할 수 있습니다. 매우 큰 배열 데이터를 포함하는 문서에 Multikey Index를 설정할 경우, 메모리 사용량이 급증할 수 있습니다. Multikey Index를 복합 인덱스로 설계할 때는 성능을 면밀히 분석해야 합니다. 예를 들어, 아래와 같이 배열 필드와 단일 필드를 조합하여 인덱스를 생성할 수 있습니다.

mongodb
// 단일 필드 인덱스 생성
db.collection.createIndex({ arrayField: 1, anotherField: 1 })

MongoDB의 Single-Field Index와 Multikey Index는 각각 단일 값과 배열 데이터를 효율적으로 검색할 수 있도록 설계된 강력한 도구입니다. Single-Field Index는 단순한 쿼리와 정렬에 적합하며, Multikey Index는 배열 데이터를 다루는 복잡한 쿼리에서 성능을 극대화할 수 있습니다.

MongoDB 검색 최적화

MongoDB 인덱스는 성능 향상의 도구지만, 관리가 소홀하면 오히려 문제를 야기할 수 있습니다. 인덱스가 많을수록 쓰기 성능이 저하됩니다. 특히 데이터 삽입이나 업데이트 시 모든 관련 인덱스가 갱신되기 때문에, 컬렉션당 4~5개 이상의 인덱스는 설정하지 않도록 합시다. 사용하지 않는 인덱스는 주기적으로 확인하여 제거해야 하고, 새로운 쿼리를 도입할 때는 항상 .explain()으로 성능을 확인하고, collScan이 발생하지 않도록 최적화해야 합니다. 인덱스는 한 번만 생성해야 하며, 중복 생성은 피해야 합니다. 생성된 인덱스는 서버 메모리에서 관리되므로, 힙 메모리 부족으로 성능 저하가 발생할 수 있습니다.

정리

MongoDB의 인덱스는 단순한 성능 최적화 도구를 넘어, 시스템 설계와 데이터 모델링의 핵심입니다. 모든 쿼리에서 인덱스의 적합성을 검증하고, 불필요한 인덱스를 제거하여 MongoDB 인덱스를 관리한다면, 대규모 데이터 처리에서도 최적의 성능을 구현할 수 있습니다.