Database/MongoDB

[MongoDB] MongoDB 인덱스 1편 - 인덱스 원리와 주요 유형들

comgu 2025. 6. 1. 11:41
반응형

MongoDB를 사용하다 보면 데이터가 많아질수록 쿼리 성능이 현저히 떨어지는 경험을 하게 된다. 이때 가장 효과적인 해결책이 바로 인덱스(Index)이다. 인덱스는 마치 책의 색인처럼 특정 데이터를 빠르게 찾을 수 있도록 도와주는 핵심 기능이다.



인덱스의 기본 원리

MongoDB는 B-tree 자료구조를 사용해 인덱스를 구성한다. B-tree는 정렬된 상태로 데이터를 저장하여 빠른 검색, 삽입, 삭제를 가능하게 한다.

인덱스의 내부 구조 (B-Tree)

        [M]
       /   \
    [D,G]   [P,S]
   /  |  \   /  |  \
 [A] [E] [H] [N] [Q] [T]
  • 정렬된 상태로 저장
  • 이진 탐색으로 빠른 검색 (O(log n))
  • 범위 검색도 효율적

인덱스가 없을 때 vs 있을 때

인덱스가 없는 경우 (Collection Scan):

  • 컬렉션의 모든 문서를 하나씩 확인
  • 100만 개 문서가 있다면 최악의 경우 100만 번 확인

인덱스가 있는 경우 (Index Scan):

  • B-tree를 통해 필요한 데이터만 빠르게 찾음
  • 로그 시간 복잡도로 훨씬 빠른 검색

 

# PyMongo 기본 인덱스 생성 예제
collection.create_index("username")
collection.create_index([("age", 1)])  # 1: 오름차순, -1: 내림차순

 

주요 인덱스 유형

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

가장 기본적인 형태로, 하나의 필드에 대해서만 인덱스를 생성한다.

# 사용자명에 인덱스 생성
collection.create_index("username")

# 빠른 검색 가능
result = await collection.find({"username": "john_doe"})

특징:

  • 구현이 간단하고 직관적
  • 해당 필드로만 검색할 때 최적의 성능
  • 메모리 사용량이 적음

2. Compound Index (복합 인덱스)

여러 필드를 조합한 인덱스이다. 필드의 순서가 매우 중요하다.

# 사용자명과 나이를 함께 인덱싱
collection.create_index([("username", 1), ("age", -1)])

중요한 특징 - 인덱스 프리픽스 규칙:

  • username만으로도 인덱스 활용 가능 ✅
  • age만으로는 이 인덱스 사용 불가 ❌
  • username + age 조합으로 최적의 성능 ✅

3. Multikey Index (다중키 인덱스)

배열 필드에 자동으로 생성되는 인덱스이다.

# tags 필드가 ["python", "mongodb", "fastapi"] 배열이라면
collection.create_index("tags")

# 각 태그 값으로 검색 가능
result = await collection.find({"tags": "python"})

동작 원리:

  • 배열의 각 요소에 대해 개별 인덱스 엔트리 생성
  • 배열 내 어떤 값으로도 빠른 검색 가능

4. Text Index (텍스트 인덱스)

텍스트 검색을 위한 특별한 인덱스이다.

collection.create_index([("title", "text"), ("content", "text")])

# 단어 기반 텍스트 검색
result = await collection.find({"$text": {"$search": "python mongodb"}})

특징:

  • 단어별로 분해하여 저장
  • 대소문자 무관 검색
  • 언어별 불용어 처리
  • 컬렉션당 하나만 생성 가능

5. Geospatial Index (지리공간 인덱스)

지리적 위치 데이터를 위한 인덱스이다.

# 2dsphere 인덱스 생성
collection.create_index([("location", "2dsphere")])

# 근처 위치 검색
result = await collection.find({
    "location": {
        "$near": {
            "$geometry": {"type": "Point", "coordinates": [127.0276, 37.4979]},
            "$maxDistance": 1000
        }
    }
})

활용 사례:

  • 배달 앱의 근처 음식점 찾기
  • 위치 기반 서비스
  • 지도 애플리케이션

6. Sparse Index (희소 인덱스)

인덱싱된 필드가 존재하는 문서만 포함하는 인덱스이다.

collection.create_index("optional_field", sparse=True)

장점:

  • 선택적 필드에 유용
  • 인덱스 크기 절약
  • null 값이 많은 필드에 효과적

7. Partial Index (부분 인덱스)

특정 조건을 만족하는 문서만 인덱싱한다.

collection.create_index(
    "username",
    partialFilterExpression={"age": {"$gte": 18}}
)

활용 사례:

  • 성인 사용자만 인덱싱
  • 활성 사용자만 인덱싱
  • 특정 조건의 데이터만 빠른 검색

8. TTL Index (Time To Live)

문서의 자동 만료를 위한 인덱스다.

# 1시간 후 자동 삭제
collection.create_index("createdAt", expireAfterSeconds=3600)

활용 사례:

  • 세션 데이터 관리
  • 임시 파일 자동 정리
  • 로그 데이터 보관 기간 관리

 

인덱스 성능 모니터링

쿼리 실행 계획 확인

# 쿼리가 인덱스를 사용하는지 확인
explain_result = await collection.find({"username": "john"}).explain()
stage = explain_result["queryPlanner"]["winningPlan"]["stage"]

if stage == "IXSCAN":
    print("인덱스 사용됨 ✅")
elif stage == "COLLSCAN":
    print("전체 컬렉션 스캔 발생 ⚠️")

인덱스 사용량 통계

# 인덱스 사용 통계 확인
index_stats = await db.command("collStats", "users", indexDetails=True)
print(index_stats["indexSizes"])

 

인덱스 설계 시 고려사항

1. 쿼리 패턴 분석

  • 자주 사용되는 검색 조건 파악
  • 정렬 기준 확인
  • 필터링 조건 우선순위 결정

2. 성능 vs 비용 트레이드오프

  • 장점: 쿼리 성능 대폭 향상
  • 단점: 스토리지 공간 사용, 쓰기 성능 약간 저하

3. 인덱스 최적화 원칙

  • 필요한 인덱스만 생성
  • 중복 인덱스 제거
  • 정기적인 성능 모니터링

 

다음 편 예고

2편에서는 복합 인덱스의 심화 원리효과적인 설계 방법에 대해 자세히 알아본다. 특히 인덱스 프리픽스 규칙과 필드 순서가 성능에 미치는 영향을 실제 예제와 함께 살펴본다.

반응형