MongoDB Atlas Search Index 완벽 가이드 - 고급 텍스트 검색 구현하기
소개
“Text Index로는 한국어 검색이 제대로 안 되고, 자동 완성 기능도 구현하기 어려워요.”
이런 문제를 겪어보신 적이 있나요? MongoDB의 기본 Text Index는 간단하지만, 고급 검색 기능이 필요하거나 한국어 검색이 중요한 프로젝트에서는 한계가 있습니다.
MongoDB Atlas Search Index는 바로 이런 문제를 해결합니다.
Atlas Search Index는 Apache Lucene 기반의 강력한 검색 엔진으로, 자동 완성, 유사어 매칭, 한국어 형태소 분석 등 고급 검색 기능을 제공합니다. 실제 테스트 결과, Text Index보다 평균 약 40% 더 빠른 성능을 보이며, 더 정확한 검색 결과를 제공합니다.
이 글을 읽으면:
- ✅ Atlas Search Index의 개념과 동작 원리를 명확히 이해할 수 있습니다
- ✅ 실무에서 바로 사용할 수 있는 Search Index 설정 방법을 학습할 수 있습니다
- ✅ 한국어 검색 최적화 전략을 마스터할 수 있습니다
- ✅ Text Index에서 마이그레이션하는 방법을 배울 수 있습니다
- ✅ 고급 검색 기능을 활용하는 방법을 익힐 수 있습니다
이 글에서 다루는 내용
이 글은 다음과 같이 구성되어 있습니다:
- Atlas Search Index란 무엇인가: 기본 개념과 Text Index와의 차이점
- Search Index 생성하기: Atlas UI에서 인덱스 생성 및 설정
- 검색 쿼리 작성하기: $search 연산자와 고급 쿼리
- 한국어 검색 최적화: lucene.korean 분석기 활용
- Text Index에서 마이그레이션: 점진적 전환 전략
- 실무 활용 전략: 성능 최적화와 베스트 프랙티스 각 섹션은 독립적으로 읽을 수 있지만, 순서대로 읽으면 더 깊이 이해할 수 있습니다.
Atlas Search Index란 무엇인가?
기본 개념
Atlas Search Index는 MongoDB Atlas에서 제공하는 Apache Lucene 기반의 고급 검색 엔진입니다. Text Index의 한계를 극복하고, 엔터프라이즈급 검색 기능을 제공합니다.
Text Index와의 차이점
Text Index의 한계:
-
❌ 컬렉션당 하나의 인덱스만 생성 가능
-
❌ 고급 검색 기능 부족 (자동 완성, 유사어 매칭 등)
-
❌ 한국어 형태소 분석 미지원
-
❌ 복잡한 쿼리 제한 Atlas Search Index의 장점:
-
✅ 컬렉션당 여러 개의 인덱스 생성 가능
-
✅ 자동 완성, 유사어 매칭, 근접 검색 등 고급 기능
-
✅ 한국어 형태소 분석 지원 (
lucene.korean) -
✅ 복잡한 쿼리와 필터링 지원
-
✅ Text Index보다 평균 약 40% 더 빠른 성능
Atlas Search Index의 특징
1. Apache Lucene 기반
Atlas Search Index는 검색 엔진의 표준인 Apache Lucene을 기반으로 합니다. 이는 Elasticsearch, Solr 등과 같은 기술을 사용하여 검색 품질과 성능을 보장합니다.
2. 여러 인덱스 생성 가능
하나의 컬렉션에 여러 개의 Search Index를 생성할 수 있어, 다양한 검색 시나리오에 대응할 수 있습니다.
// 예: 제품 검색용 인덱스 { "name": "product_search", "mappings": { ... } } // 예: 사용자 검색용 인덱스 { "name": "user_search", "mappings": { ... } }
3. 다양한 분석기 지원
언어별 분석기를 지원하여 각 언어에 최적화된 검색이 가능합니다.
lucene.standard: 영어 기본 분석기lucene.korean: 한국어 형태소 분석기lucene.japanese: 일본어 분석기lucene.chinese: 중국어 분석기
4. Atlas 전용 기능
MongoDB Atlas에서만 사용 가능하며, 별도의 인프라 구축 없이 바로 사용할 수 있습니다.
⚠️ 주의사항: Atlas Search Index는 MongoDB Atlas에서만 사용 가능합니다. 자체 관리형 MongoDB에서는 사용할 수 없습니다.
Search Index 생성하기
Atlas UI에서 생성하기
Step 1: Atlas 콘솔 접속
- https://cloud.mongodb.com 에 로그인
- 프로젝트 선택
- 클러스터 선택
Step 2: Search 탭으로 이동
- 왼쪽 메뉴에서 “Search” 클릭
- “Create Search Index” 버튼 클릭
Step 3: 인덱스 설정
방법 1: JSON Editor 사용 (권장)
- “JSON Editor” 선택
- 다음 JSON 설정을 붙여넣기:
{ "mappings": { "dynamic": false, "fields": { "title": { "type": "string", "analyzer": "lucene.standard" }, "content": { "type": "string", "analyzer": "lucene.standard" } } } }
-
Database: 사용하는 데이터베이스 이름
-
Collection: 검색할 컬렉션 이름
-
Index Name: 인덱스 이름 (예:
default,korean-text-index) 방법 2: Visual Editor 사용 -
“Visual Editor” 선택
-
Collection 선택
-
Fields 추가:
title(type: string, analyzer: lucene.standard)content(type: string, analyzer: lucene.standard)
- Index Name 지정
Step 4: 인덱스 생성
- “Next” 클릭
- 설정 확인
- “Create Search Index” 클릭
- 인덱스 생성 완료까지 몇 분 소요 (보통 1-3분)
한국어 검색을 위한 인덱스 설정
한국어 검색이 중요한 경우, lucene.korean 분석기를 사용하세요:
{ "mappings": { "dynamic": false, "fields": { "title": { "type": "string", "analyzer": "lucene.korean" }, "content": { "type": "string", "analyzer": "lucene.korean" } } } }
다중 분석기 사용
한국어와 영어를 모두 지원하려면 다중 분석기를 사용할 수 있습니다:
{ "mappings": { "dynamic": false, "fields": { "title": { "type": "string", "multi": { "korean": { "type": "string", "analyzer": "lucene.korean" }, "standard": { "type": "string", "analyzer": "lucene.standard" } } }, "content": { "type": "string", "multi": { "korean": { "type": "string", "analyzer": "lucene.korean" }, "standard": { "type": "string", "analyzer": "lucene.standard" } } } } } }
💡 실무 팁: 다중 분석기를 사용하면 한국어와 영어 검색을 모두 최적화할 수 있지만, 인덱스 크기가 증가합니다. 프로젝트 요구사항에 따라 선택하세요.
인덱스 생성 확인
방법 1: Atlas 콘솔에서 확인
- Search 탭으로 이동
- 생성한 인덱스가 “Active” 상태인지 확인
방법 2: 코드로 확인
// 인덱스가 정상 작동하는지 테스트 try { const results = await articleModel.aggregate([ { $search: { index: "korean-text-index", text: { query: "테스트", path: ["title", "content"], }, }, }, ]); console.log("Search Index is working!"); } catch (error) { console.warn("Search Index not available:", error.message); }
검색 쿼리 작성하기
기본 검색: $search 연산자
Atlas Search Index는 aggregate() 파이프라인에서 $search 스테이지를 사용합니다.
기본 텍스트 검색
async searchWithSearchIndex(query: string): Promise<ArticleSearch[]> { const results = await this.articleSearchModel.aggregate([ { $search: { index: 'korean-text-index', // Atlas에서 생성한 인덱스 이름 text: { query: query, path: ['title', 'content'], }, }, }, { $limit: 100, }, ]); return results; }
여러 필드 검색
// 여러 필드에서 검색 { $search: { index: 'korean-text-index', text: { query: query, path: ['title', 'content', 'tags'], // 여러 필드 지정 }, }, }
고급 검색 기능
1. 자동 완성 (Autocomplete)
{ $search: { index: 'korean-text-index', autocomplete: { query: query, path: 'title', fuzzy: { maxEdits: 1, // 오타 허용 범위 }, }, }, }
2. 유사어 매칭 (Fuzzy Search)
{ $search: { index: 'korean-text-index', text: { query: query, path: ['title', 'content'], fuzzy: { maxEdits: 2, // 최대 2글자까지 오타 허용 prefixLength: 3, // 앞 3글자는 정확히 일치해야 함 }, }, }, }
3. 근접 검색 (Phrase Search)
{ $search: { index: 'korean-text-index', phrase: { query: 'MongoDB 데이터베이스', path: ['title', 'content'], slop: 2, // 단어 사이 최대 2개 단어까지 허용 }, }, }
4. 복합 쿼리 (Compound Query)
{ $search: { index: 'korean-text-index', compound: { must: [ { text: { query: 'MongoDB', path: 'title', }, }, ], should: [ { text: { query: '데이터베이스', path: 'content', }, }, ], mustNot: [ { text: { query: '삭제됨', path: 'status', }, }, ], }, }, }
검색 결과 정렬 및 필터링
관련성 점수 활용
{ $search: { index: 'korean-text-index', text: { query: query, path: ['title', 'content'], }, }, }, { $addFields: { score: { $meta: 'searchScore' }, // 관련성 점수 추가 }, }, { $sort: { score: -1 }, // 관련성 순 정렬 }
다른 필터와 조합
{ $search: { index: 'korean-text-index', text: { query: query, path: ['title', 'content'], }, }, }, { $match: { category: '데이터베이스', // 추가 필터 publishedAt: { $gte: new Date('2024-01-01') }, }, }, { $sort: { publishedAt: -1 }, }, { $limit: 20, }
Fallback 로직 구현
Search Index가 없는 경우를 대비해 fallback 로직을 구현하는 것이 좋습니다:
async searchWithSearchIndex(query: string): Promise<ArticleSearch[]> { try { const results = await this.articleSearchModel.aggregate([ { $search: { index: 'korean-text-index', text: { query: query, path: ['title', 'content'], }, }, }, { $limit: 100, }, ]); return results; } catch (error) { // Search Index가 없는 경우 fallback console.warn('Search Index not available, falling back to regex'); return this.articleSearchModel .find({ $or: [ { title: { $regex: query, $options: 'i' } }, { content: { $regex: query, $options: 'i' } }, ], }) .limit(100) .exec(); } }
💡 실무 팁: 프로덕션 환경에서는 항상 fallback 로직을 구현하여 Search Index가 사용 불가능한 상황에서도 검색 기능이 동작하도록 해야 합니다.
한국어 검색 최적화
한국어 분석기 사용
lucene.korean 분석기의 장점
- ✅ 형태소 분석: 조사, 어미를 제거하여 더 정확한 검색
- ✅ 어간 추출: “데이터베이스를”, “데이터베이스의” 등도 “데이터베이스”로 검색 가능
- ✅ 띄어쓰기 처리: 띄어쓰기가 다른 경우도 매칭
인덱스 설정
{ "mappings": { "dynamic": false, "fields": { "title": { "type": "string", "analyzer": "lucene.korean" }, "content": { "type": "string", "analyzer": "lucene.korean" } } } }
실제 테스트 결과
1,013개의 문서를 대상으로 한국어 검색어로 테스트한 결과:
| 검색어 | Text Index 평균 | Search Index 평균 | 성능 향상 |
|---|---|---|---|
| “데이터베이스” | 115.7ms | 69.5ms | 39.93% 향상 |
| “검색” | 111.1ms | 65.7ms | 40.86% 향상 |
| 평균 | 113.4ms | 67.6ms | 40.4% 향상 |
주요 발견사항:
- ✅ Search Index가 Text Index보다 평균 약 40% 더 빠름
- ✅ 검색 결과 수: Text Index (152-154개) vs Search Index (253개)
- ✅ 실행 시간 안정성: Search Index가 더 일정함 (59-71ms 범위)
한국어 검색 예시
Text Index의 한계
// Text Index: 정확한 키워드만 매칭 db.articles.find({ $text: { $search: "데이터베이스" } }); // "데이터베이스를", "데이터베이스의" 등은 검색되지 않을 수 있음
Search Index의 장점
// Search Index: 형태소 분석으로 더 정확한 검색 { $search: { index: 'korean-text-index', text: { query: '데이터베이스', path: ['title', 'content'], }, }, } // "데이터베이스를", "데이터베이스의" 등도 검색됨
다국어 지원 전략
한국어와 영어를 모두 지원해야 하는 경우:
전략 1: 다중 분석기 사용
{ "mappings": { "dynamic": false, "fields": { "title": { "type": "string", "multi": { "korean": { "type": "string", "analyzer": "lucene.korean" }, "standard": { "type": "string", "analyzer": "lucene.standard" } } } } } }
전략 2: 별도 인덱스 생성
// 한국어 검색용 인덱스 { "name": "korean_search", "mappings": { "fields": { "title": { "analyzer": "lucene.korean" } } } } // 영어 검색용 인덱스 { "name": "english_search", "mappings": { "fields": { "title": { "analyzer": "lucene.standard" } } } }
Text Index에서 마이그레이션
마이그레이션 전략
1. 점진적 마이그레이션
기존 Text Index는 유지하면서 새로운 기능에만 Atlas Search를 사용합니다.
async search(query: string, useSearchIndex: boolean) { if (useSearchIndex) { // Atlas Search 사용 (새로운 기능) return this.searchWithSearchIndex(query); } else { // Text Index 사용 (기존 방식) return this.searchWithTextIndex(query); } }
2. A/B 테스트
두 방식을 병행하여 성능과 결과를 비교한 후 점진적으로 전환합니다.
// 성능 비교 API async comparePerformance(query: string, iterations: number = 10) { const textIndexTimes = []; const searchIndexTimes = []; for (let i = 0; i < iterations; i++) { // Text Index 성능 측정 const start1 = Date.now(); await this.searchWithTextIndex(query); textIndexTimes.push(Date.now() - start1); // Search Index 성능 측정 const start2 = Date.now(); await this.searchWithSearchIndex(query); searchIndexTimes.push(Date.now() - start2); } return { textIndex: { average: textIndexTimes.reduce((a, b) => a + b) / iterations, min: Math.min(...textIndexTimes), max: Math.max(...textIndexTimes), }, searchIndex: { average: searchIndexTimes.reduce((a, b) => a + b) / iterations, min: Math.min(...searchIndexTimes), max: Math.max(...searchIndexTimes), }, }; }
3. 완전 전환
모든 검색을 Atlas Search로 전환하고 Text Index를 제거합니다.
// 모든 검색을 Search Index로 전환 async search(query: string) { return this.searchWithSearchIndex(query); } // Text Index 제거 (필요시) // db.articles.dropIndex("text_index_name");
마이그레이션 체크리스트
- Atlas Search Index 생성 완료
- 인덱스 상태가 “Active”인지 확인
- Fallback 로직 구현
- A/B 테스트로 성능 비교
- 검색 결과 품질 검증
- 점진적으로 트래픽 전환
- 모니터링 및 최적화
- Text Index 제거 (선택사항)
실무 활용 전략
성능 최적화
1. 인덱스 크기 관리
Search Index는 상당한 저장 공간을 사용할 수 있습니다. 인덱스 크기를 주기적으로 확인하세요.
// Atlas 콘솔에서 인덱스 크기 확인 // 또는 MongoDB Compass에서 확인
2. 검색 성능 모니터링
실행 계획을 확인하여 쿼리 성능을 모니터링합니다.
// explain()을 사용하여 실행 계획 확인 const explainResult = await articleModel .aggregate([ { $search: { index: "korean-text-index", text: { query: query, path: ["title", "content"], }, }, }, ]) .explain("executionStats");
3. 결과 제한
불필요하게 많은 결과를 반환하지 않도록 제한합니다.
{ $search: { ... }, }, { $limit: 20, // 적절한 결과 수로 제한 }
베스트 프랙티스
1. 인덱스 이름 관리
명확하고 일관된 인덱스 이름을 사용하세요.
// ✅ 좋은 예 index: "korean-text-index"; index: "product-search-index"; index: "user-autocomplete-index"; // ❌ 나쁜 예 index: "index1"; index: "test";
2. 필드 선택
검색 대상이 되는 필드만 인덱스에 포함하세요.
{ "mappings": { "dynamic": false, // 동적 필드 비활성화 "fields": { "title": { ... }, // 검색 대상 "content": { ... }, // 검색 대상 "tags": { ... } // 검색 대상 // price, createdAt 등은 제외 } } }
3. 분석기 선택
언어와 검색 요구사항에 맞는 분석기를 선택하세요.
- 한국어:
lucene.korean - 영어:
lucene.standard - 다국어: 다중 분석기 사용
4. Fallback 전략
Search Index가 사용 불가능한 경우를 대비해 fallback 로직을 구현하세요.
일반적인 문제와 해결 방법
문제 1: 인덱스가 생성되지 않음
원인: 데이터베이스 이름이나 컬렉션 이름이 잘못됨
해결:
- Atlas 콘솔에서 데이터베이스와 컬렉션 이름 확인
- 인덱스 설정에서 정확한 이름 사용
문제 2: 검색 결과가 없음
원인: 인덱스가 아직 Active 상태가 아님 또는 분석기 설정 문제
해결:
- 인덱스 상태 확인 (Active여야 함)
- 분석기 설정 확인 (한국어는
lucene.korean) - 데이터가 인덱싱되었는지 확인
문제 3: 성능이 느림
원인: 너무 많은 결과를 반환하거나 인덱스 설정 문제
해결:
$limit으로 결과 수 제한- 불필요한 필드 제거
- 인덱스 설정 최적화
마무리
이 글에서는 MongoDB Atlas Search Index의 개념부터 실무 활용까지 상세히 다뤘습니다.
핵심 요약
지금까지 다룬 내용을 요약하면:
- Atlas Search Index는 Text Index보다 강력한 기능을 제공합니다
- 자동 완성, 유사어 매칭, 형태소 분석 등 고급 기능
- 한국어 검색에 최적화되어 있습니다
lucene.korean분석기로 형태소 분석 지원- Text Index보다 평균 40% 더 빠른 성능
- 여러 인덱스를 생성할 수 있습니다
- 다양한 검색 시나리오에 대응 가능
- 복잡한 쿼리를 지원합니다
- compound query, phrase search 등
- 실무에서 바로 활용 가능합니다
- NestJS/Mongoose 예시 코드 제공
실제 프로젝트 적용 예시
실제 NestJS 프로젝트에서 Atlas Search Index를 사용하는 예시를 확인해보세요:
프로젝트 구조
mongo-index/ ├── src/ │ ├── search/ # Search Index를 사용하는 모듈 │ │ ├── search.service.ts │ │ └── schemas/article-search.schema.ts │ └── performance/ # 성능 비교 모듈 │ └── performance.service.ts
API 엔드포인트
# Search Index 검색 GET /search/search-index?q=검색어 # 성능 비교 GET /performance/compare?q=검색어&iterations=10
실제 테스트 결과 확인
프로젝트의 KOREAN_SEARCH_TEST_RESULTS.md 파일에서 상세한 테스트 결과를 확인할 수 있습니다. 실제 1,013개의 문서를 대상으로 한 성능 테스트 결과:
- Text Index: 평균 113.4ms
- Search Index: 평균 67.6ms
- 성능 향상: 약 40% 개선
- 검색 결과: Text Index (152-154개) vs Search Index (253개)
다음 단계
이제 Atlas Search Index를 실제 프로젝트에 적용할 준비가 되었습니다. 다음 글에서는:
- 🚀 고급 검색 기능 심화 학습
- 🔧 성능 모니터링과 최적화
- 💾 대규모 데이터셋에서의 검색 전략
- 🎯 복잡한 검색 시나리오 해결하기 를 다루겠습니다.
💬 질문이나 피드백이 있으시면 댓글로 남겨주세요!
🔖 이 글이 도움이 되었다면 공유해주시면 감사하겠습니다.
댓글 (0)
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!