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"}
})
텍스트 인덱스 설계 체크리스트
✅ 모범 사례
- 복합 인덱스 우선: 일반 필드와 함께 사용하여 성능 향상
- 가중치 설정: 중요한 필드에 높은 가중치 부여
- 언어 설정: 적절한 언어 분석기 선택
- 성능 모니터링: 정기적인 검색 성능 및 관련성 평가
- 점진적 개선: 사용자 검색 패턴을 분석하여 인덱스 최적화
❌ 피해야 할 실수
- 과도한 필드 포함: 불필요하게 많은 텍스트 필드 인덱싱
- 가중치 무시: 모든 필드에 동일한 중요도 부여
- 언어 설정 부재: 기본 영어 분석기만 사용
- 성능 검증 없음: explain() 없이 운영 환경 배포
- 일반 인덱스와 혼동: 정확한 매칭이 필요한 곳에 텍스트 인덱스 사용
다음 편 예고
4편에서는 인덱스 가중치(Weight)의 심화 활용법과 검색 관련성 최적화 기법에 대해 알아본다. 텍스트 검색의 품질을 한층 더 향상시킬 수 있는 고급 기법들을 다룰 예정이다.
반응형