몽고 디비 인 액션 9장

텍스트 검색

정규 표현식으로 패턴 일치 검색을 해도 가능은 하지만, 인덱스 없이 대형 컬렉션에서 사용한다면 매우 느리다.

[단지 패턴 매칭만은 아니다.]

  • 패턴 일치
    • 스테밍(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으로, 지정된 것이 없다면

  • 오직 정확한 단어만이 형태소 분석 없이 인덱싱됨
  • 불용어 제외되지 않음: 유사한 단어 검색 불가능

Leave a Comment