MongoDB Index

강동욱·2026년 5월 14일

Mongodb Index 개념

인덱스는 DB의 검색을 빠르게 하기 위하여 데이터의 순서를 미리 정리해 두는 과정이다.

index는 어떤 데이터가 도큐먼트에 추가되거나 수정될 때( write 작업) 그 컬렉션에 생성되어 있는 index도 새로운 도큐먼트를 포함시켜 수정된다. 이로 인하여 index 추가 시 wirte 작업은 느려질 수 있다. 따라서 index는 read 작업 위주의 애플리케션에서 유용하고 읽기보다 쓰기 작업이 많으면 index를 추가하는 것은 고려해야 한다.

MongoDB Index 타입

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

가장 기본적인 인덱스. 하나의 필드에 대해 B-Tree 구조로 인덱스를 생성한다. 1은 오름차순, -1은 내림차순이지만, 단일 필드 인덱스는 양방향 탐색이 가능해서 방향이 크게 의미 없다.

// age 필드에 오름차순 인덱스 생성
db.users.createIndex({ age: 1 })

// 특정 필드가 존재하는 도큐먼트만 인덱싱 (sparse 옵션)
db.users.createIndex(
  { phone: 1 },
  { sparse: true }
)

💡 _id 필드는 MongoDB가 컬렉션 생성 시 자동으로 단일 필드 인덱스를 만들어준다. 삭제 불가.


2. 복합 인덱스 (Compound)

두 개 이상의 필드를 묶어 하나의 인덱스로 생성. 필드 순서가 핵심이다 — 앞 필드 기준으로 먼저 정렬되고, 값이 같을 때 다음 필드로 넘어간다.

MongoDB 공식 문서에서 권장하는 ESR 규칙을 따르면 대부분의 경우 최적 성능을 낼 수 있다.

// ESR 규칙: Equality → Sort → Range 순서로 필드 배치
db.orders.createIndex({
  status: 1,      // Equality (등호 조건)
  createdAt: -1,  // Sort (정렬 기준)
  amount: 1       // Range (범위 조건)
})

💡 인덱스 prefix 활용: { status, createdAt, amount } 인덱스가 있으면 { status }, { status, createdAt } 쿼리도 이 인덱스가 커버한다.


3. 다중 키 인덱스 (Multikey)

필드 값이 배열일 때 MongoDB가 자동으로 적용하는 인덱스. 배열 내 각 요소마다 별도의 인덱스 엔트리가 생성된다. 별도 선언이 필요 없고, 배열 필드에 createIndex를 걸면 자동으로 다중 키 인덱스가 된다.

// tags 필드가 배열인 경우 → 자동으로 Multikey 인덱스
// { title: "MongoDB", tags: ["database", "nosql", "index"] }

db.posts.createIndex({ tags: 1 })

// 이제 아래 쿼리들이 인덱스를 탄다
db.posts.find({ tags: "nosql" })
db.posts.find({ tags: { $in: ["database", "index"] } })

⚠️ 복합 인덱스에서 배열 타입 필드는 1개만 허용. 두 배열 필드를 묶은 복합 인덱스는 생성 자체가 불가능하다.


4. 지리 공간 인덱스 (Geospatial)

좌표 데이터에 특화된 인덱스. 2dsphere(지구 구면 기준, 경위도)와 2d(평면 좌표) 두 종류가 있다. 실무에서는 대부분 GeoJSON과 함께 2dsphere를 쓴다.

// GeoJSON 형식으로 위치 저장
db.restaurants.insertOne({
  name: "맛있는 식당",
  location: {
    type: "Point",
    coordinates: [126.9784, 37.5665]  // [경도, 위도]
  }
})

db.restaurants.createIndex({ location: "2dsphere" })

// 현재 위치 기준 1km 내 식당 검색
db.restaurants.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [126.97, 37.56] },
      $maxDistance: 1000
    }
  }
})

💡 배달 앱, 지도 기반 서비스의 핵심 인덱스. $near, $geoWithin, $geoIntersects 연산자와 함께 사용.


5. 텍스트 인덱스 (Text)

문자열 필드에 대해 형태소 분석 기반의 풀텍스트 검색을 지원. stop word 처리와 stemming(어근 추출)이 내장돼 있다. 컬렉션 당 텍스트 인덱스는 1개만 생성 가능하지만, 여러 필드를 묶을 수 있다.

// 여러 필드를 하나의 텍스트 인덱스로
db.articles.createIndex({
  title: "text",
  body: "text",
  summary: "text"
})

// 검색
db.articles.find({
  $text: { $search: "MongoDB index performance" }
})

// 관련도 점수 기준 정렬
db.articles.find(
  { $text: { $search: "MongoDB" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })

⚠️ 한국어 형태소 분석은 기본 텍스트 인덱스로 커버되지 않는다. 한국어 검색이 필요하다면 Atlas Search(Lucene 기반) 사용을 고려할 것.


6. 해시 인덱스 (Hashed)

필드 값을 해시 함수에 통과시켜 저장하는 인덱스. 동등 비교(=)만 지원하고 범위 쿼리($gt, $lt 등)는 불가. 주 용도는 샤딩 환경에서 데이터를 균등 분산하는 것이다.

// 해시 인덱스 생성
db.users.createIndex({ userId: "hashed" })

// 샤딩 설정 시 활용
sh.shardCollection(
  "mydb.users",
  { userId: "hashed" }
)

⚠️ 범위 검색이 필요한 필드에는 단일 or 복합 인덱스를 써야 한다. 해시 인덱스는 샤딩 키 용도로만 사용하는 게 일반적.


몽고 디비가 인덱스를 선택하는 방법

하나의 도큐먼트에 N개의 인덱스가 있고, 도큐먼트에 쿼리를 요청하면 몽고DB는 쿼리 모양을 확인한다. 쿼리 모양에서는 검색할 필드와 정렬 여부 등 추가 정보가 있다. 해당 쿼리 모양을 통하여 쿼리에 충족하는 인덱스 후보 집합을 식별한다고 한다. 즉, 쿼리 레이스라고 이야기한다.

쿼리 레이스의 순서

  1. 몽고DB가 N개의 인덱스 중 4개의 인덱스 후보를 잡는다.

  2. 각 인덱스에 대한 쿼리 플랜을 만든다.

  3. 병렬 스레드를 사용하여 쿼리를 실행한다.

  4. 가장 빨리 결과를 반환하는 쿼리에 대해서 캐시화하여 쿼리플랜으로 만들어 관리한다.

이때 성공한 플랜은 몽고DB explain에서 winning plan, 그 외 플랜에 대해서는 reject plan으로 나온다.

쿼리플랜의 유지 시간
컬렉션과 인덱스(추가, 수정, 삭제)에 대해서 변경 및 몽고DB 프로세스가 재시작하면 삭제된다. 또한 명시적으로 제거할 수 있다.

인덱스 비교표

인덱스주요 용도범위 쿼리특이사항
단일 필드단일 조건 쿼리기본, _id 자동 생성
복합다중 조건/정렬ESR 규칙, prefix 활용
다중 키배열 필드 검색배열 시 자동 적용
지리 공간위치 기반 검색2dsphere / 2d 구분
텍스트풀텍스트 검색컬렉션당 1개 제한
해시샤딩 균등 분산동등 비교만 지원

profile
차근차근 개발자

0개의 댓글