app.get('/search', async (요청, 응답) => {
let result = await db.collection('post').find({
title: 요청.query.val
}).toArray()
응답.render('search.ejs', { 글목록: result })
})
문제점: 정확한 단어만 검색 가능, 성능 이슈
{
"title": "text", // 문자 필드
"price": 1, // 숫자 필드 (1: 오름차순, -1: 내림차순)
"date": -1 // 날짜 필드
}
// Text Index 사용
db.collection('post').find({
$text: { $search: '검색어' }
}).toArray()
// 숫자 Index는 기본 문법 그대로 사용
db.collection('post').find({
price: { $gte: 1000 }
}).toArray()
// Index 없이 검색
let result1 = await db.collection('post')
.find({ title: '안녕' })
.explain('executionStats')
// Index 활용 검색
let result2 = await db.collection('post')
.find({ $text: { $search: '안녕' } })
.explain('executionStats')
// 느린 검색 (COLLSCAN)
{
"totalDocsExamined": 13, // 13개 문서 모두 확인
"executionStage": "COLLSCAN" // 전체 컬렉션 스캔
}
// 빠른 검색 (Index 활용)
{
"totalDocsExamined": 1, // 1개 문서만 확인
"executionStage": "IXSCAN" // Index 스캔
}
핵심: COLLSCAN이 안 나오면 빠른 검색
| 구분 | 기본 Index | Search Index |
|---|---|---|
| 검색 방식 | 정확한 단어 | 부분 검색 가능 |
| 한국어 지원 | 제한적 | 우수 |
| 설정 복잡도 | 간단 | 복잡 |
| 용도 | 단순 검색 | 검색엔진 수준 |
텍스트 전처리
"안녕하세요 여러분"
→ ["안녕하세요", "여러분"] (조사 제거)
단어 정렬 및 인덱싱
단어: "안녕하세요" → Document ID: [1, 3, 5]
단어: "여러분" → Document ID: [1, 2, 4]
검색 실행
검색: "안녕" → "안녕하세요" 포함 문서 반환
점수 계산: 관련도에 따른 순위 자동 결정
{
"analyzer": "lucene.korean",
"mappings": {
"dynamic": false,
"fields": {
"title": {
"type": "string",
"analyzer": "lucene.korean"
},
"content": {
"type": "string",
"analyzer": "lucene.korean"
}
}
}
}
app.get('/search', async (요청, 응답) => {
let 검색조건 = [
{
$search: {
index: 'search_index_name',
text: {
query: 요청.query.val,
path: 'title'
}
}
}
]
let result = await db.collection('post')
.aggregate(검색조건)
.toArray()
응답.render('search.ejs', { 글목록: result })
})
let 검색조건 = [
{
$search: {
index: 'search_index_name',
text: {
query: '검색어',
path: ['title', 'content'] // 여러 필드 검색
}
}
},
{ $sort: { score: { $meta: 'textScore' } } }, // 관련도순 정렬
{ $limit: 10 }, // 상위 10개만
{ $skip: 0 }, // 페이지네이션
{
$project: {
title: 1,
content: 1,
score: { $meta: 'textScore' } // 점수 포함
}
}
]
let 검색조건 = [
// 1. 검색 실행
{
$search: {
index: 'search_index',
text: { query: '검색어', path: 'title' }
}
},
// 2. 정렬
{ $sort: { _id: -1 } }, // 최신순
{ $sort: { score: { $meta: 'textScore' } } }, // 관련도순
// 3. 결과 제한
{ $limit: 20 }, // 상위 20개
{ $skip: 10 }, // 10개 건너뛰기
// 4. 필드 선택
{
$project: {
title: 1, // 포함
content: 0, // 제외
_id: 1,
score: { $meta: 'textScore' } // 검색 점수
}
},
// 5. 조건 필터링
{
$match: {
author: '특정작성자',
createdAt: { $gte: new Date('2024-01-01') }
}
}
]
// 검색 + 필터링 + 페이지네이션
app.get('/search', async (요청, 응답) => {
let page = parseInt(요청.query.page) || 1
let limit = 10
let 검색조건 = [
{
$search: {
index: 'search_index',
text: { query: 요청.query.val, path: 'title' }
}
},
{ $match: { published: true } }, // 공개글만
{ $sort: { score: { $meta: 'textScore' } } }, // 관련도순
{ $skip: (page - 1) * limit }, // 페이지네이션
{ $limit: limit },
{
$project: {
title: 1,
summary: 1,
author: 1,
score: { $meta: 'textScore' }
}
}
]
let result = await db.collection('post')
.aggregate(검색조건)
.toArray()
응답.render('search.ejs', {
글목록: result,
currentPage: page
})
})
// 검색어 정제
let searchQuery = 요청.query.val
.trim() // 공백 제거
.replace(/[^\w\s가-힣]/g, '') // 특수문자 제거
.slice(0, 100) // 길이 제한
app.get('/search', async (요청, 응답) => {
if (!요청.query.val || 요청.query.val.trim() === '') {
return 응답.render('search.ejs', { 글목록: [] })
}
// 검색 로직...
})
// 인기 검색어나 최근 검색 결과 캐싱
const searchCache = new Map()
app.get('/search', async (요청, 응답) => {
let cacheKey = 요청.query.val
if (searchCache.has(cacheKey)) {
return 응답.render('search.ejs', {
글목록: searchCache.get(cacheKey)
})
}
// 실제 검색 후 캐시 저장
let result = await db.collection('post').aggregate(검색조건).toArray()
searchCache.set(cacheKey, result)
응답.render('search.ejs', { 글목록: result })
})