MongoDB Text Index로 빠른 텍스트 검색 구현하기
MongoDB Text Index로 빠른 텍스트 검색 구현하기
소개
“사용자가 입력한 키워드로 제품 설명을 검색하고 싶은데, $regex를 사용하니 데이터가 많아질수록 느려지고 있어요.”
이런 문제를 겪어보신 적이 있나요? MongoDB에서 텍스트 검색을 구현할 때 가장 흔히 사용하는 방법이 $regex입니다. 하지만 데이터가 수천, 수만 건을 넘어가면 성능 저하가 심각해집니다.
MongoDB Text Index는 바로 이런 문제를 해결합니다.
Text Index는 전체 텍스트 검색(Full-Text Search)을 지원하는 특수한 인덱스로, 대량의 문서에서도 빠르고 정확한 텍스트 검색을 가능하게 합니다. 단순히 키워드를 찾는 것을 넘어서, 검색 결과의 관련성을 점수화하고, 필드별 중요도를 설정할 수 있습니다.
이 글을 읽으면:
- ✅ Text Index의 개념과 동작 원리를 명확히 이해할 수 있습니다
- ✅ 실무에서 바로 사용할 수 있는 Text Index 생성 방법을 학습할 수 있습니다
- ✅ 가중치 설정과 검색 최적화 전략을 마스터할 수 있습니다
- ✅ 성능 문제를 해결하고 최적화하는 방법을 배울 수 있습니다
- ✅ Atlas Search Index와의 차이점을 이해하고 적절한 선택을 할 수 있습니다
이 글에서 다루는 내용
이 글은 다음과 같이 구성되어 있습니다:
- Text Index란 무엇인가: 기본 개념과 $regex와의 차이점
- Text Index 생성하기: 단일 필드, 복합 필드, 가중치 설정
- 텍스트 검색 쿼리: $text 연산자와 검색 옵션
- 실무 활용 전략: 성능 최적화와 베스트 프랙티스
- Text Index vs Atlas Search Index: 간단한 비교와 선택 가이드 각 섹션은 독립적으로 읽을 수 있지만, 순서대로 읽으면 더 깊이 이해할 수 있습니다.
Text Index란 무엇인가?
기본 개념
Text Index는 MongoDB에서 제공하는 특수한 인덱스 타입으로, 문자열 필드에 대한 전체 텍스트 검색(Full-Text Search)을 지원합니다.
$regex와의 차이점
$regex를 사용한 검색:
// 느리고 비효율적 db.products.find({ description: { $regex: "스마트폰", $options: "i" }, });
- ❌ 전체 컬렉션 스캔: 인덱스를 사용하지 못해 모든 문서를 확인
- ❌ 대소문자 구분 처리:
$options: "i"를 사용해도 성능 저하 - ❌ 복잡한 패턴 매칭: 정규식 처리로 인한 오버헤드
- ❌ 결과 정렬 어려움: 관련성 순으로 정렬하기 어려움 Text Index를 사용한 검색:
// 빠르고 효율적 db.products.find({ $text: { $search: "스마트폰" } });
- ✅ 인덱스 기반 검색: 인덱스를 활용해 빠른 검색
- ✅ 자동 대소문자 무시: 기본적으로 대소문자를 구분하지 않음
- ✅ 불용어 제거: “the”, “a”, “an” 같은 불용어 자동 제외
- ✅ 관련성 점수: 검색 결과를 관련성 순으로 정렬 가능
💡 실무 팁: 데이터가 1,000건 이상이거나, 검색 기능이 핵심 기능이라면 Text Index를 사용하는 것을 강력히 권장합니다.
Text Index의 특징
1. 컬렉션당 하나의 Text Index
중요한 제약사항입니다. 하나의 컬렉션에는 하나의 Text Index만 생성할 수 있습니다. 여러 필드를 검색하고 싶다면, 하나의 Text Index에 여러 필드를 포함해야 합니다.
// ✅ 올바른 방법: 하나의 Text Index에 여러 필드 포함 db.products.createIndex({ title: "text", description: "text", tags: "text", }); // ❌ 잘못된 방법: 여러 개의 Text Index 생성 시도 db.products.createIndex({ title: "text" }); db.products.createIndex({ description: "text" }); // 에러 발생!
2. 문자열 및 문자열 배열만 지원
Text Index는 문자열(string) 또는 문자열 배열(string array) 필드에만 적용됩니다.
// ✅ 지원되는 필드 타입 { title: "스마트폰", // 문자열 tags: ["전자제품", "모바일"], // 문자열 배열 keywords: ["스마트", "폰"] // 문자열 배열 } // ❌ 지원되지 않는 필드 타입 { price: 100000, // 숫자 createdAt: ISODate(), // 날짜 metadata: { category: "..." } // 객체 }
3. 언어별 불용어 제거
Text Index는 기본적으로 영어 불용어(stop words)를 제거합니다. “the”, “a”, “an” 같은 단어는 인덱싱되지 않아 검색에서 제외됩니다.
// "the best phone" 검색 시 // "the"는 불용어로 제외되고 "best", "phone"만 검색됨 db.products.find({ $text: { $search: "the best phone" } });
💡 실무 팁: 한국어는 기본적으로 불용어 처리가 제한적입니다. 한국어 텍스트 검색이 중요하다면 Atlas Search나 Vector Search를 고려해보세요.
Text Index 생성하기
기본 생성 방법
단일 필드 Text Index
가장 간단한 형태입니다. 하나의 필드에 Text Index를 생성합니다.
// products 컬렉션의 title 필드에 Text Index 생성 db.products.createIndex({ title: "text" }); // 인덱스 확인 db.products.getIndexes();
복합 필드 Text Index
여러 필드를 하나의 Text Index에 포함할 수 있습니다. 이렇게 하면 여러 필드에서 동시에 검색이 가능합니다.
// title, description, tags 필드에 Text Index 생성 db.products.createIndex({ title: "text", description: "text", tags: "text", });
💡 실무 팁: 검색 대상이 되는 모든 필드를 미리 Text Index에 포함하는 것이 좋습니다. 나중에 필드를 추가하려면 인덱스를 삭제하고 다시 생성해야 합니다.
가중치(Weight) 설정
필드별로 중요도를 다르게 설정할 수 있습니다. 가중치가 높은 필드에서 매칭된 결과가 더 높은 점수를 받습니다.
가중치 설정 방법
db.products.createIndex( { title: "text", description: "text", tags: "text", }, { weights: { title: 10, // 가장 중요 (기본값 1) description: 5, // 중간 중요도 tags: 1, // 기본 중요도 }, name: "text_index", // 인덱스 이름 지정 (선택사항) } );
가중치의 영향
가중치가 높은 필드에서 매칭된 문서는 검색 점수가 높아집니다.
// 검색 결과 예시 db.products .find({ $text: { $search: "스마트폰" } }, { score: { $meta: "textScore" } }) .sort({ score: { $meta: "textScore" } }); // 결과: // { // _id: 1, // title: "최신 스마트폰", // title에서 매칭 (가중치 10) // score: 10.5 // } // { // _id: 2, // description: "스마트폰 추천", // description에서 매칭 (가중치 5) // score: 5.2 // }
💡 실무 팁: 제품명이나 제목 같은 핵심 필드에 높은 가중치를 주면, 사용자가 원하는 결과를 더 쉽게 찾을 수 있습니다.
인덱스 이름 지정
인덱스에 이름을 지정하면 나중에 관리하기 쉬워집니다.
db.products.createIndex( { title: "text", description: "text" }, { name: "product_text_search_index" } ); // 이름으로 인덱스 삭제 db.products.dropIndex("product_text_search_index");
텍스트 검색 쿼리
기본 검색: $text 연산자
$text 연산자를 사용하여 Text Index가 적용된 필드에서 검색할 수 있습니다.
// 기본 검색 db.products.find({ $text: { $search: "스마트폰" } }); // 여러 단어 검색 (OR 조건) db.products.find({ $text: { $search: "스마트폰 갤럭시" } }); // "스마트폰" 또는 "갤럭시"가 포함된 문서 검색 // 구문 검색 (정확한 일치) db.products.find({ $text: { $search: '"스마트폰 추천"' } }); // "스마트폰 추천"이라는 구문이 정확히 포함된 문서 검색 // 제외 검색 db.products.find({ $text: { $search: "스마트폰 -아이폰" } }); // "스마트폰"은 포함하지만 "아이폰"은 제외
검색 점수(Score) 활용
Text Index는 각 검색 결과에 관련성 점수를 부여합니다. 이 점수를 활용하여 결과를 정렬할 수 있습니다.
// 검색 점수 포함하여 조회 db.products.find( { $text: { $search: "스마트폰" } }, { score: { $meta: "textScore" } } ).sort({ score: { $meta: "textScore" } }) // 결과 예시 { _id: ObjectId("..."), title: "최신 스마트폰", description: "스마트폰 추천 제품", score: 2.5 // 관련성 점수 }
💡 실무 팁: 검색 결과를 관련성 순으로 정렬하면 사용자 경험이 크게 향상됩니다. 항상 sort()와 함께 사용하는 것을 권장합니다.
다른 쿼리와 조합하기
$text 쿼리는 다른 쿼리 조건과 함께 사용할 수 있습니다.
// 가격 필터와 함께 사용 db.products.find({ $text: { $search: "스마트폰" }, price: { $gte: 500000, $lte: 1000000 }, }); // 카테고리 필터와 함께 사용 db.products.find({ $text: { $search: "스마트폰" }, category: "전자제품", }); // 정렬과 함께 사용 db.products .find({ $text: { $search: "스마트폰" } }, { score: { $meta: "textScore" } }) .sort({ score: { $meta: "textScore" }, price: 1 });
⚠️ 주의사항: $text 쿼리는 하나의 쿼리에서 한 번만 사용할 수 있습니다. 여러 $text 조건을 AND/OR로 조합할 수 없습니다.
실무 활용 전략
성능 최적화
1. 인덱스 생성 시 주의사항
대량의 데이터가 있는 컬렉션에 Text Index를 생성할 때는 시간이 오래 걸릴 수 있습니다.
// ✅ 백그라운드에서 인덱스 생성 (프로덕션 권장) db.products.createIndex( { title: "text", description: "text" }, { background: true } ); // ❌ 포그라운드 생성 (작은 데이터셋에만 사용) db.products.createIndex({ title: "text" });
💡 실무 팁: 프로덕션 환경에서는 항상 background: true 옵션을 사용하세요. 인덱스 생성 중에도 컬렉션에 대한 읽기/쓰기가 가능합니다.
2. 인덱스 크기 관리
Text Index는 상당한 저장 공간을 사용할 수 있습니다. 인덱스 크기를 주기적으로 확인하세요.
// 인덱스 크기 확인 db.products.stats(); // Text Index만의 크기 확인 db.products.aggregate([{ $indexStats: {} }]);
3. 검색 성능 모니터링
explain()을 사용하여 쿼리 실행 계획을 확인할 수 있습니다.
// 실행 계획 확인 db.products.find({ $text: { $search: "스마트폰" } }).explain("executionStats"); // 결과에서 확인할 항목: // - executionTimeMillis: 실행 시간 // - totalDocsExamined: 검사한 문서 수 // - indexesUsed: 사용된 인덱스
베스트 프랙티스
1. 검색 대상 필드 선택
모든 필드에 Text Index를 만들지 말고, 실제로 검색이 필요한 필드만 선택하세요.
// ✅ 좋은 예: 검색에 필요한 필드만 포함 db.products.createIndex({ title: "text", description: "text", tags: "text", }); // ❌ 나쁜 예: 불필요한 필드까지 포함 db.products.createIndex({ title: "text", description: "text", price: "text", // 숫자 필드는 Text Index 불가 createdAt: "text", // 날짜 필드는 검색 대상이 아님 });
2. 가중치 전략
사용자가 가장 중요하게 생각하는 필드에 높은 가중치를 부여하세요.
// 제품 검색의 경우 db.products.createIndex( { title: "text", // 제품명이 가장 중요 description: "text", // 설명은 보조적 tags: "text", // 태그는 참고용 }, { weights: { title: 10, // 제품명 매칭 시 높은 점수 description: 3, // 설명 매칭 시 중간 점수 tags: 1, // 태그 매칭 시 낮은 점수 }, } );
3. 검색 쿼리 최적화
불필요한 정렬이나 프로젝션을 피하세요.
// ✅ 효율적인 쿼리 db.products .find( { $text: { $search: "스마트폰" } }, { title: 1, price: 1, score: { $meta: "textScore" } } ) .sort({ score: { $meta: "textScore" } }) .limit(20); // ❌ 비효율적인 쿼리 db.products .find({ $text: { $search: "스마트폰" } }) .sort({ score: { $meta: "textScore" }, price: 1, createdAt: -1 }) .limit(100); // 너무 많은 결과 반환
일반적인 문제와 해결 방법
문제 1: 인덱스가 생성되지 않음
원인: 컬렉션에 이미 다른 Text Index가 존재하는 경우
// 해결: 기존 인덱스 확인 후 삭제 db.products.getIndexes(); db.products.dropIndex("기존_인덱스_이름"); // 새 인덱스 생성 db.products.createIndex({ title: "text", description: "text" });
문제 2: 검색 결과가 없음
원인: Text Index가 생성되지 않았거나, 검색어가 불용어인 경우
// 해결 1: 인덱스 확인 db.products.getIndexes(); // 해결 2: 검색어 확인 (불용어 제외) // "the", "a", "an" 같은 단어는 검색되지 않음 db.products.find({ $text: { $search: "best phone" } }); // "the" 제외됨
문제 3: 성능이 느림
원인: 인덱스가 없거나, 너무 많은 문서를 반환하는 경우
// 해결 1: 인덱스 생성 확인 db.products.getIndexes(); // 해결 2: 결과 제한 db.products.find({ $text: { $search: "스마트폰" } }).limit(20); // 해결 3: 다른 필터와 조합하여 결과 수 줄이기 db.products.find({ $text: { $search: "스마트폰" }, category: "전자제품", price: { $lte: 1000000 }, });
Text Index vs Atlas Search Index
MongoDB에는 텍스트 검색을 위한 두 가지 주요 방법이 있습니다: Text Index와 Atlas Search Index.
간단한 비교
| 특징 | Text Index | Atlas Search Index |
|---|---|---|
| 사용 환경 | 자체 관리형 + Atlas | Atlas 전용 |
| 설정 난이도 | 간단 (한 줄) | 복잡 (JSON 설정) |
| 기본 검색 | ✅ 지원 | ✅ 지원 |
| 고급 기능 | ❌ 제한적 | ✅ 자동 완성, 유사어 매칭 등 |
| 한국어 지원 | ⚠️ 제한적 | ✅ 형태소 분석 지원 |
| 성능 | 소규모~중규모 적합 | 대규모에서 우수 |
언제 무엇을 사용할까?
Text Index를 사용해야 하는 경우
- ✅ 자체 관리형 MongoDB 사용
- ✅ 간단한 키워드 검색만 필요
- ✅ 빠른 프로토타이핑
- ✅ 소규모 프로젝트
Atlas Search Index를 사용해야 하는 경우
- ✅ 고급 검색 기능 필요 (자동 완성, 유사어 매칭)
- ✅ 대규모 데이터셋
- ✅ 한국어 검색이 중요
- ✅ 검색 품질이 중요
📚 다음 글에서 Atlas Search Index를 자세히 다룹니다: Atlas Search Index의 설정 방법, 한국어 검색 최적화, 마이그레이션 전략 등을 상세히 설명하는 별도 글이 준비되어 있습니다.
마무리
이 글에서는 MongoDB Text Index의 개념부터 실무 활용까지 상세히 다뤘습니다.
핵심 요약
지금까지 다룬 내용을 요약하면:
- Text Index는 $regex보다 훨씬 빠르고 효율적입니다
- 인덱스 기반 검색으로 대량의 데이터에서도 빠른 성능
- 하나의 컬렉션에는 하나의 Text Index만 생성 가능합니다
- 여러 필드를 하나의 인덱스에 포함해야 함
- 가중치를 활용하면 검색 품질을 향상시킬 수 있습니다
- 중요한 필드에 높은 가중치 부여
- 검색 점수를 활용하여 관련성 순 정렬이 가능합니다
score: { $meta: "textScore" }사용
- Text Index는 간단한 검색에 적합합니다
- 고급 기능이 필요하면 Atlas Search Index를 고려하세요
실제 프로젝트 적용 예시
실제 NestJS 프로젝트에서 Text Index를 사용하는 예시를 확인해보세요:
프로젝트 구조
mongo-index/ ├── src/ │ ├── articles/ # Text Index를 사용하는 모듈 │ │ ├── articles.service.ts │ │ └── schemas/article.schema.ts │ └── performance/ # 성능 비교 모듈 │ └── performance.service.ts
API 엔드포인트
# Text Index 검색 GET /articles/search/text-index?q=검색어
다음 단계
이제 Text Index를 실제 프로젝트에 적용할 준비가 되었습니다. 다음 글에서는:
- 🚀 MongoDB Atlas Search Index 완벽 가이드
- Atlas Search Index 설정 방법
- 한국어 검색 최적화 전략
- Text Index에서 마이그레이션하기
- 고급 검색 기능 활용하기 를 다루겠습니다.
💬 질문이나 피드백이 있으시면 댓글로 남겨주세요!
🔖 이 글이 도움이 되었다면 공유해주시면 감사하겠습니다.