텍스트 검색
정규 표현식으로 패턴 일치 검색을 해도 가능은 하지만, 인덱스 없이 대형 컬렉션에서 사용한다면 매우 느리다.
[단지 패턴 매칭만은 아니다.]
- 패턴 일치
- 스테밍(stemming) : stem, root 단어, 단어의 원형과 변형들을 다 참고한다 (script → scripts, scripted, scripting)
- 웹 페이지 검색
- 페이지의 대형 네트워크를 검색하고, 페이지 간 관련도에 따라 결과의 순위를 정하는 검색방법
- 제품 데이터베이스 등 DB 데이터 검색 문제를 해결하기 위한 것은 아님
- 웹 페이지 네트워크 검색에 초점: DB에서 생성한 웹 페이지에 액세스하고, DB 자체에는 액세스 X
- 예) Java 책을 검색한다고 할 때, 책만 검색되지 않고 정오표 등 다른 검색결과 (noise)도 포함됨
- 전용텍스트 검색엔진
- 매우 큰 DB도 인덱싱 가능함
- 웹 검색 엔진에서 가능한 맞춤법 교정, 검색 제안, 관련성 측정 등의 기능을 지원함
- 추가 기능
- facets: 거의 모든 필드를 카테고리화, 범위 기반 그룹도 가능
- 사용자 정의 기능: 동의어 라이브러리, 형태소 분석(stemming) 알고리즘, 불용어 사전
MongoDB에서 가능한 텍스트 검색 기능 (지원되는 언어 기준, 한국어 불가)
- 형태소 분석 → 실시간 자동 인덱싱
- 필드에 가중치 선택적 지정
- 불용어 삭제
- 정확한 단어/구문 일치
- 특정 단어/구문 제외
→ 모든 기능은 인덱스 정의를 통해 사용 가능
→ 전체 DB를 검색용 엔진에 복사하지 않고도 적당히 단어 검색이 가능함
→ 전용 검색 엔진 관리 및 운영 비용을 피할 수 있음
텍스트 검색에 필요한 인덱스를 정의한다
집계 프레임워크(aggregate), 기본 쿼리 등에서 텍스트 검색($text)을 사용한다
[텍스트 검색 인덱스 정의]
db.collection.createIndex(
{
field_name: 'text', //텍스트 인덱싱할 필드를 지정
...
//혹은 위를 생략하고 '$**'로 문자열을 포함하는 모든 인덱스를 선택할 수도 있음
'$**': 'text'
},
{
weights: //필드의 가중치를 지정
{
field1_name: 10, field2_name: 5,
... // 기본 가중치는 1, field1은 field2보다 2배 더 높은 가중치를 가지게 된다
}
},
[name: 'idx_name'] );
정규 인덱스와의 차이점
- 인덱싱 필드에 대해 1, -1이 아니라 ‘text’를 넣는다
- 인덱싱되는 모든 필드 값 텍스트에서, 고유 단어들에 대해 인덱스 항목이 만들어진다
- 컬렉션당 하나의 텍스트 검색 인덱스를 가질 수 있다
(그러나 한 인덱스에 원하는 만큼 필드를 추가할 수 있다) - 필드 값 텍스트에서 불용어는 무시된다
텍스트 인덱스 크기
- 텍스트 검색 인덱스는 컬렉션 자체보다 더 클 수 있다
- 불용어가 제거되더라도, 인덱스가 생성되는 대부분의 텍스트를 복제할 뿐 아니라
각 단어의 원본 도큐먼트에 대한 포인터를 추가해야 한다
- 불용어가 제거되더라도, 인덱스가 생성되는 대부분의 텍스트를 복제할 뿐 아니라
- 인덱스 이름의 길이가 너무 길 때:
- MongoDB에서 네임스페이스의 최대 길이는 120바이트 (V2.6~)
- 네임스페이스: db.collection.object의 이름
- name 속성으로 사용자 정의 이름을 주거나
- ‘$**’로 와일드카드 필드 이름을 지정한다
[기본 텍스트 검색]
db.books.find({$text: {$search: 'actions'}}, {title: 1})
//word to search examples
'word' ' word '
'word1 or word2' //or는 제외되어 검색됨
'"exact word or phrases" or other_word' // "exact word~ "는 반드시 포함, 뒤는 or 매칭
'included or -"not included"' // -를 붙이면 제외하고 검색
$text쿼리를 텍스트 검색으로 정의$search검색에 사용할 문자열을 정의- 결과는 임의의 순서로 반환됨
- 자동으로 Stemming하여 어간으로 모든 도큐먼트를 찾기 위해 텍스트 검색 인덱스를 사용한다
- “” 쌍 따옴표를 붙이면 정확한 일치 조건, -를 붙이면 제외 조건
//wordstofind를 포함하면서, other_field 필드에 valuetofind 값을 가지는 도큐먼트를 검색
db.collection.find({$text: {$search: 'wordstofind'}, other_field: 'valuetofind'});
텍스트 검색 기준 결합의 한계
- 다중 키 복합 인덱스, 지리공간 복합 키 인덱스는 허용X
- $text를 포함한 쿼리는 hint()를 사용할 수 없다
- 관련도에 따른 정렬 이외의 다른 정렬은 불가능함
[텍스트 검색 스코어]
/**
* 기본 find 쿼리
**/
db.collection.find({$text: {$search: 'words to find'}}
, {_id:0, field1: 1, score_val: {$meta: "textScore"}}).
limit(4);
// sort 가능함
db.collection.find({$text: {$search: 'words to find'}},
{_id:0, field1: 1, score_val: {$meta: "textScore"}}).
sort({score_val: {$meta: "textScore"}})
//find와 sort의 score_val은 동일하게 줘야 한다
- 결과에 텍스트 검색 스코어를 포함하여 검색
score_val은 사용자 정의 항목- find쿼리에서는 find에서 준 score 이름을 sort에도 동일하게 줘야 한다
/**
* 집계 프레임워크 사용(aggregate)
**/
db.collection.aggregate(
[
{ $match: { $text: { $search: 'words to find' } } }, //검색
{ $sort: { $score_val: { $meta: 'textScore' } } }, //정렬
{ $project: { field1: 1, score_val: { $meta: 'textScore' } } } //추출
] )
//sort, project 순서를 바꾸면 더 단순해진다
db.collection.aggregate(
[
{ $match: { $text: { $search: 'words to find' } } }, //검색
{ $project: { field1: 1, score_val: { $meta: 'textScore' } } } //추출
{ $sort: { $score_val: -1 }, //내림차순 정렬, $project의 score_name부분을 자동 참조함
]
)
aggregate의 텍스트 검색 시 제한 사항이 있다
- $text 사용할 때는 $match가 파이프라인의 첫 번째여야 하고,
$meta: ‘textScore’가 등장하기 전에 와야 한다 - $text는 파이프라인에서 한 번만 사용 가능
- $text에서는 $or, $not을 사용할 수 없다
- ”가 or연산, “”가 정확한 일치(반드시 포함), -“” ( -”)는 not 연산이다
/**
* 텍스트 승수 multiplier 추가하기
* 비슷한 텍스트를 가지고 있는 도큐먼트라도, 포함된 field에 차이가 있는 경우 문서 간 관련도는 낮게 책정된다
* =>보정하고 싶다면, 텍스트 승수로 관련도 score 값을 보정할 수 있다
**/
db.collection.aggregate(
[
{ $match: { $text: { $search: 'words to find' } } },
{ $project: { //1차 추출
field1_name: 1,
score_val: { $meta: 'textScore' },
multiplier: { $cond: [ '$field_to_adjust', 1.0, 3.0 ] } } },
// field_name, true, false
//이 필드가 있으면 multiplier의 값은 1.0, null이거나 없으면 3.0
{ $project: { //field 유무에 따른 문서간 관련도 점수 차이 보정
_id:0, field1_name:1, score_val:1,
adjScore: $multiply: ['$score_val', '$multiplier'] } },
{ $sort: { adjScore: -1 } } //조정된 점수를 기준으로 내림차순 정렬
]
)
[텍스트 검색 언어]
- 인덱스에서 : 특정 컬렉션에 대한 기본 언어 지정
- 도큐먼트 삽입 시 : 특정 도큐먼트 또는 필드가 인덱스 지정 기본값 외 다른 언어임을 알리기 위해 override
- find() 또는 aggregate() 함수에서 텍스트 검색 수행 시 : 검색 시 사용하는 언어 노티
/**
* 인덱스에서 언어 지정
**/
> db.books.find({$text: {$search: 'in '}}).count()
0
// 기존 텍스트 인덱스 삭제
db.books.dropIndex('books_text_index');
db.books.createIndex(
{'$**': 'text'},
{weights:
{title: 10,
categories: 5},
name : 'books_text_index',
default_language: 'french' //프랑스어로 하여 인덱스 추가
}
);
// 프랑스어에서 in 검색
> db.books.find({$text: {$search: 'in '}}).count()
334
// 인덱스 확인
> db.books.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "catalog.books"
},
{
"v" : 1,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"name" : "books_text_index",
"ns" : "catalog.books",
"weights" : {
"$**" : 1,
"categories" : 5,
"title" : 10
},
"default_language" : "french", // 프랑스어가 기본 텍스트 인덱스 언어로 지정
"language_override" : "language",
"textIndexVersion" : 2
}
]
/**
* 도큐먼트에서 언어 지정
**/
// 기존 텍스트 인덱스 삭제
db.books.dropIndex('books_text_index');
db.books.createIndex(
{'$**': 'text'},
{weights:
{title: 10,
categories: 5},
name : 'books_text_index',
default_language: 'english' // 영어 기본언어 지정
}
);
// 프랑스어로 언어를 지정하는 새로운 도큐먼트 삽입
db.books.insert({
_id: 999,
title: 'Le Petite Prince',
pageCount: 85,
publishedDate: ISODate('1943-01-01T01:00:00Z'),
shortDescription: "Le Petit Prince est une œuvre de langue française,
la plus connue d'Antoine de Saint-Exupéry. Publié en 1943 à New York
simultanément en anglais et en français. C'est un conte poétique et
philosophique sous l'apparence d'un conte pour enfants.",
status: 'PUBLISH',
authors: ['Antoine de Saint-Exupéry'],
language: 'french' // 언어를 프랑스어로 지정
})
/**
* 검색에서 언어 지정
**/
// 언어가 형태소 분석에 미치는 영향의 예
> db.books.find({$text: {$search:
'simultanment',$language:'french'}},{title:1})
{ "_id" : 999, "title" : "Le Petit Prince" }
> db.books.find({$text: {$search: 'simultanment'}},{title:1})
{ "_id" : 186, "title" : "Hadoop in Action" }
{ "_id" : 293, "title" : "Making Sense of Java" }
{ "_id" : 999, "title" : "Le Petite Prince" }
> db.books.find({$text: {$search: 'prince'}},{title:1})
{ "_id" : 145, "title" : "Azure in Action" }
{ "_id" : 999, "title" : "Le Petit Prince" }
언어가 none으로, 지정된 것이 없다면
- 오직 정확한 단어만이 형태소 분석 없이 인덱싱됨
- 불용어 제외되지 않음: 유사한 단어 검색 불가능