Database/MongoDB

[MongoDB] MongoDB 인덱스 3편 - 텍스트 인덱스의 원리와 효과적인 사용법

comgu 2025. 6. 1. 12:14
반응형

텍스트 인덱스는 MongoDB에서 전문 검색(Full-text Search)을 위한 특별한 인덱스이다. 일반 인덱스와는 완전히 다른 동작 원리를 가지며, 검색 엔진과 같은 기능을 제공한다.



일반 인덱스 vs 텍스트 인덱스

일반 인덱스의 한계

# 일반 인덱스로는 이런 검색만 가능
collection.create_index("title")

# ✅ 정확한 매칭만 가능
await collection.find({"title": "MongoDB 완전정복"})

# ❌ 대소문자가 다르면 검색 안됨  
await collection.find({"title": "mongodb 완전정복"})

# ❌ 부분 단어 검색 불가
await collection.find({"title": "완전정복"})

# 정규식 사용하면 가능하지만 매우 느림
await collection.find({"title": {"$regex": "MongoDB", "$options": "i"}})

텍스트 인덱스의 강력함

# 텍스트 인덱스 생성
collection.create_index([("title", "text"), ("content", "text")])

# ✅ 단어 기반 검색
await collection.find({"$text": {"$search": "MongoDB"}})

# ✅ 대소문자 무관
await collection.find({"$text": {"$search": "mongodb"}})

# ✅ 여러 단어 검색 (OR 조건)
await collection.find({"$text": {"$search": "MongoDB tutorial"}})

# ✅ 구문 검색
await collection.find({"$text": {"$search": "\"MongoDB 완전정복\""}})

# ✅ 제외 검색
await collection.find({"$text": {"$search": "MongoDB -Python"}})


텍스트 인덱스의 내부 동작 원리

단어 분해와 정규화 과정

일반 인덱스는 문자열을 그대로 저장하지만, 텍스트 인덱스는 단어별로 분해하여 저장한다.

# 원본 문서
{
  "title": "MongoDB와 Python을 활용한 FastAPI 개발",
  "content": "이 튜토리얼에서는 MongoDB 데이터베이스와 Python FastAPI를 연동하는 방법을 설명합니다."
}

# 텍스트 인덱스 내부 저장 형태
{
  "mongodb": [문서1],
  "python": [문서1],
  "활용한": [문서1], 
  "fastapi": [문서1],
  "개발": [문서1],
  "튜토리얼에서는": [문서1],
  "데이터베이스와": [문서1],
  "연동하는": [문서1],
  "방법을": [문서1],
  "설명합니다": [문서1]
}

언어별 분석기 처리

# 영어 텍스트 인덱스 (기본)
collection.create_index([("title", "text")], default_language="english")

# 한국어 텍스트 인덱스
collection.create_index([("title", "text")], default_language="korean")

# 여러 언어 지원
collection.create_index([("title", "text")], language_override="language")

언어별 처리 특징:

  • 영어: 불용어(the, a, an) 자동 제거, 어간 추출(running → run)
  • 한국어: 불용어(은, 는, 이, 가) 처리, 형태소 분석
  • 공통: 대소문자 정규화, 구두점 제거

 

텍스트 인덱스의 고급 검색 기능

1. 점수 기반 정렬 (Relevance Scoring)

# 검색 점수와 함께 결과 반환
result = await collection.find(
    {"$text": {"$search": "MongoDB tutorial"}},
    {"score": {"$meta": "textScore"}}
).sort([("score", {"$meta": "textScore"})]).to_list(None)

for doc in result:
    print(f"제목: {doc['title']}, 점수: {doc['score']:.2f}")

점수 계산 요소:

  • 검색 단어의 문서 내 빈도
  • 단어의 희귀성 (TF-IDF 유사)
  • 텍스트 인덱스 가중치

2. 구문 검색과 부울 연산

# 정확한 구문 검색
await collection.find({"$text": {"$search": "\"MongoDB tutorial\""}})

# AND 연산 (모든 단어 포함)
await collection.find({"$text": {"$search": "+MongoDB +Python +tutorial"}})

# OR 연산 (기본 동작)
await collection.find({"$text": {"$search": "MongoDB Python tutorial"}})

# NOT 연산 (특정 단어 제외)
await collection.find({"$text": {"$search": "MongoDB -beginner"}})

3. 언어별 검색

# 문서별로 다른 언어 설정
await collection.insert_many([
    {"title": "MongoDB Tutorial", "content": "Learn MongoDB...", "language": "english"},
    {"title": "MongoDB 튜토리얼", "content": "MongoDB를 배워보세요...", "language": "korean"},
    {"title": "Tutorial de MongoDB", "content": "Aprende MongoDB...", "language": "spanish"}
])

# 언어별 인덱스
collection.create_index([("title", "text"), ("content", "text")], language_override="language")

 

복합 텍스트 인덱스 설계 전략

단일 텍스트 인덱스

# 전체 컬렉션에서 텍스트 검색
collection.create_index([("title", "text"), ("content", "text"), ("tags", "text")])

# 모든 텍스트 필드에서 검색
result = await collection.find({"$text": {"$search": "MongoDB FastAPI"}})

적합한 경우:

  • 필터링 없이 순수 텍스트 검색만 하는 경우
  • 전체 컬렉션을 대상으로 하는 통합 검색
  • 간단한 블로그나 문서 검색

복합 텍스트 인덱스 (권장)

# 카테고리별 텍스트 검색이 빈번한 경우
collection.create_index([
    ("category", 1),           # 일반 필드
    ("status", 1),             # 일반 필드
    ("title", "text"),         # 텍스트 필드
    ("content", "text")        # 텍스트 필드
])

# 특정 카테고리 내에서만 텍스트 검색
result = await collection.find({
    "category": "technology",
    "status": "published",
    "$text": {"$search": "MongoDB tutorial"}
})

성능 이점:

  • 먼저 일반 필드로 범위 축소 (예: 1만개 → 100개)
  • 축소된 범위에서만 텍스트 검색 수행
  • 훨씬 빠른 검색 성능

실제 사용 사례별 설계

1. 이커머스 상품 검색

# 상품 검색 최적화
collection.create_index([
    ("category", 1),
    ("brand", 1), 
    ("is_available", 1),
    ("name", "text"),
    ("description", "text")
])

# 브랜드별 상품 검색
result = await collection.find({
    "category": "electronics",
    "brand": "Samsung", 
    "is_available": True,
    "$text": {"$search": "스마트폰 갤럭시"}
})

2. 블로그/뉴스 사이트

# 날짜별 기사 검색 최적화
collection.create_index([
    ("published_date", -1),
    ("category", 1),
    ("status", 1),
    ("title", "text"),
    ("content", "text")
])

# 최근 기술 기사 검색
result = await collection.find({
    "published_date": {"$gte": datetime(2024, 1, 1)},
    "category": "technology",
    "status": "published",
    "$text": {"$search": "인공지능 ChatGPT"}
})

3. 문서 관리 시스템

# 사용자별 문서 검색
collection.create_index([
    ("owner_id", 1),
    ("department", 1),
    ("doc_type", 1),
    ("title", "text"),
    ("content", "text")
])

# 특정 부서의 보고서 검색
result = await collection.find({
    "department": "engineering",
    "doc_type": "report",
    "$text": {"$search": "성능 최적화 방안"}
})

 

텍스트 인덱스 가중치 활용

필드별 중요도 설정

# 제목이 내용보다 3배 중요한 경우
collection.create_index([
    ("category", 1),
    ("title", "text"),
    ("content", "text")
], weights={
    "title": 3,
    "content": 1
})

# 제목에서 매칭된 결과가 더 높은 점수를 받음
result = await collection.find(
    {"$text": {"$search": "MongoDB"}},
    {"score": {"$meta": "textScore"}}
).sort([("score", {"$meta": "textScore"})])

검색 품질 향상

# 태그 필드에 가장 높은 가중치
collection.create_index([
    ("title", "text"),
    ("content", "text"), 
    ("tags", "text")
], weights={
    "tags": 10,      # 태그 매칭 시 높은 점수
    "title": 5,      # 제목 매칭 시 중간 점수
    "content": 1     # 내용 매칭 시 기본 점수
})

 

성능 최적화 및 모니터링

1. 텍스트 검색 성능 측정

# 실행 계획 확인
explain = await collection.find({
    "category": "technology",
    "$text": {"$search": "MongoDB tutorial"}
}).explain()

# 텍스트 인덱스 사용 확인
stage = explain["queryPlanner"]["winningPlan"]["stage"]
if "TEXT" in stage:
    print("✅ 텍스트 인덱스 사용됨")
    
    # 성능 지표 확인
    stats = explain["executionStats"]
    print(f"검사한 문서: {stats['totalDocsExamined']}")
    print(f"반환한 문서: {stats['totalDocsReturned']}")
    print(f"실행 시간: {stats['executionTimeMillis']}ms")

2. 인덱스 크기 모니터링

# 텍스트 인덱스는 일반 인덱스보다 훨씬 큰 공간 사용
stats = await db.command("collStats", "articles")
print(f"전체 인덱스 크기: {stats['totalIndexSize']} bytes")

# 개별 인덱스 크기 확인
for index_name, size in stats["indexSizes"].items():
    print(f"{index_name}: {size} bytes")

3. 검색 품질 개선

# 검색 결과의 관련성 평가
async def evaluate_search_quality(search_term):
    results = await collection.find(
        {"$text": {"$search": search_term}},
        {"title": 1, "score": {"$meta": "textScore"}}
    ).sort([("score", {"$meta": "textScore"})]).limit(10).to_list(None)
    
    print(f"검색어: '{search_term}'")
    for i, doc in enumerate(results, 1):
        print(f"{i}. {doc['title']} (점수: {doc['score']:.2f})")

await evaluate_search_quality("MongoDB 튜토리얼")

 

텍스트 인덱스 제약사항과 해결책

1. 컬렉션당 하나만 생성 가능

# ❌ 이렇게 할 수 없음
collection.create_index([("title", "text")])
collection.create_index([("description", "text")])  # 에러!

# ✅ 하나의 복합 텍스트 인덱스로 해결
collection.create_index([
    ("category", 1),
    ("title", "text"),
    ("description", "text"),
    ("tags", "text")
])

2. 정확한 문자열 매칭 어려움

# 텍스트 인덱스로는 정확한 매칭이 어려움
# 제품 코드, 이메일 등은 일반 인덱스 별도 생성
collection.create_index("product_code")  # 정확한 매칭용
collection.create_index([("name", "text"), ("description", "text")])  # 텍스트 검색용

# 두 인덱스를 조합하여 사용
result = await collection.find({
    "product_code": "PRD-001",  # 정확한 매칭
    "$text": {"$search": "노트북"}  # 텍스트 검색
})

3. 메모리 사용량이 큼

# 큰 텍스트 인덱스의 메모리 사용량 최적화
# 자주 검색되지 않는 필드는 제외
collection.create_index([
    ("category", 1),
    ("title", "text"),
    # ("full_content", "text")  # 큰 필드는 제외 고려
], weights={
    "title": 3
})

# 필요시 별도 검색으로 보완
if detailed_search_needed:
    result = await collection.find({
        "_id": {"$in": initial_result_ids},
        "full_content": {"$regex": search_term, "$options": "i"}
    })

 

텍스트 인덱스 설계 체크리스트

✅ 모범 사례

  1. 복합 인덱스 우선: 일반 필드와 함께 사용하여 성능 향상
  2. 가중치 설정: 중요한 필드에 높은 가중치 부여
  3. 언어 설정: 적절한 언어 분석기 선택
  4. 성능 모니터링: 정기적인 검색 성능 및 관련성 평가
  5. 점진적 개선: 사용자 검색 패턴을 분석하여 인덱스 최적화

❌ 피해야 할 실수

  1. 과도한 필드 포함: 불필요하게 많은 텍스트 필드 인덱싱
  2. 가중치 무시: 모든 필드에 동일한 중요도 부여
  3. 언어 설정 부재: 기본 영어 분석기만 사용
  4. 성능 검증 없음: explain() 없이 운영 환경 배포
  5. 일반 인덱스와 혼동: 정확한 매칭이 필요한 곳에 텍스트 인덱스 사용

 

다음 편 예고

4편에서는 인덱스 가중치(Weight)의 심화 활용법검색 관련성 최적화 기법에 대해 알아본다. 텍스트 검색의 품질을 한층 더 향상시킬 수 있는 고급 기법들을 다룰 예정이다.

반응형