Bon Voyage

'MongoDB in Action'으로 정리해보는 텍스트 검색 인덱스 본문

개념정리/데이터베이스

'MongoDB in Action'으로 정리해보는 텍스트 검색 인덱스

nangkyeong 2019. 9. 30. 23:22

MongoDB in Actionhttps://book.naver.com/bookdb/book_detail.nhn?bid=6876243

 

MongoDB in Action 몽고디비 인 액션

MONGODB나 NOSQL에 경험 없는 개발자를 위한 쉽고 실전적인 입문서『MONGODB IN ACTION 몽고디비 인 액션』. 이 책은 MONGODB와 도큐먼트 지향 데이터베이스 모델을 소개한다. 적당한 속도로 진행되는 이 책은 개발자로서 필요한 큰 그림과 시스템 엔지니어를 만족시키기에 충분한 하위 수준의 상세한 내용을 동시에 제공한다. 수많은 예제들은 데이터 모델링의 중요한 분야에서 확신을 갖는 데 도움을 제공할 것이다. 또한 복제, 자동 샤딩, 배포

book.naver.com

9장 텍스트 검색 파트를 요약 정리했습니다.

 


 

개요

텍스트 검색 엔진 기능의 예시

  • 대소문자 구분하지 않는 검색 Case-insensitive Search
  • Java와 JavaScript를 구분한 검색 결과

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

 

개념 정리

1. 스테밍Stemming

  • stem, root 단어, 단어의 원형과 변형들을 다 참고한다 (script → scripts, scripted, scripting)

2. 웹페이지 검색

  • 페이지의 대형 네트워크를 검색하고, 페이지 간 관련도에 따라 결과의 순위를 정하는 검색방법
  • 제품 데이터베이스 등 DB 데이터 검색 문제를 해결하기 위한 것은 아님
    • 웹 페이지 네트워크 검색에 초점: DB에서 생성한 웹 페이지에 액세스하고, DB 자체에는 액세스 X
    • 예를 들어 Java 책을 검색한다고 할 때, 책만 검색되지 않고 정오표 등 다른 검색결과 (noise)도 포함됨

3. Page Rank 검색 알고리즘

구글의 Larry Page가 개발한, 구글 검색 알고리즘 중 하나

5. 전용 텍스트 검색 엔진

  • 매우 큰 DB도 인덱싱 가능함
  • 웹 검색 엔진에서 가능한 맞춤법 교정, 검색 제안, 관련성 측정 등의 기능을 지원함
  • 추가 기능
    — facets: 거의 모든 필드를 카테고리화, 범위 기반 그룹도 가능
    — 사용자 정의 기능: 동의어 라이브러리, 형태소 분석(stemming) 알고리즘, 불용어 사전

 

MongoDB에서 가능한 텍스트 검색 기능 (지원되는 언어 기준, 한국어 불가)

  • 형태소 분석 → 실시간 자동 인덱싱
  • 필드에 가중치 선택적 지정
  • 불용어 삭제
  • 정확한 단어/구문 일치
  • 특정 단어/구문 제외

→ 모든 기능은 인덱스 정의를 통해 사용 가능
→ 전체 DB를 검색용 엔진에 복사하지 않고도 적당히 단어 검색이 가능함
→ 전용 검색 엔진 관리 및 운영 비용을 피할 수 있음

HOW?

  1. 텍스트 검색에 필요한 인덱스를 정의한다
  2. 집계 프레임워크(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, -1이 아니라 'text'를 넣는다
  • 인덱싱되는 모든 필드 값 텍스트에서, 고유 단어들에 대해 인덱스 항목이 만들어진다
  • 컬렉션당 하나의 텍스트 검색 인덱스를 가질 수 있다
    (그러나 한 인덱스에 원하는 만큼 필드를 추가할 수 있다)
  • 필드 값 텍스트에서 불용어는 무시된다

2. 텍스트 인덱스 크기

  • 텍스트 검색 인덱스는 컬렉션 자체보다 더 클 수 있다
    • 불용어가 제거되더라도, 인덱스가 생성되는 대부분의 텍스트를 복제할 뿐 아니라
      각 단어의 원본 도큐먼트에 대한 포인터를 추가해야 한다
  • 인덱스 이름의 길이가 너무 길 때:
    • MongoDB에서 네임스페이스의 최대 길이는 120바이트 (V2.6~)
    • 네임스페이스: db.collection.object의 이름
    • name 속성으로 사용자 정의 이름을 주거나
    • '$**'로 와일드카드 필드 이름을 지정한다

 

 

텍스트 검색 인덱스 사용 쿼리

1. 기본 검색

db.collection.find({$text: {$search: 'word_to_search'}}, {field_to_select: 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하여 어간으로 모든 도큐먼트를 찾기 위해 텍스트 검색 인덱스를 사용한다
  • "" 쌍 따옴표를 붙이면 정확한 일치 조건, -를 붙이면 제외 조건
db.collection.find({$text: {$search: 'wordstofind'}, other_field: 'valuetofind'});
  • wordstofind를 포함하면서, other_field 필드에 valuetofind 값을 가지는 도큐먼트를 검색한다

텍스트 검색 기준 결합의 한계

  • 다중 키 복합 인덱스, 지리공간 복합 키 인덱스는 허용X
  • $text를 포함한 쿼리는 hint()를 사용할 수 없다
  • 관련도에 따른 정렬 이외의 다른 정렬은 불가능함

 

2. 텍스트 검색 스코어 활용

textScore → 도큐먼트에 해당 단어가 표시된 횟수를 기반으로 도큐먼트간 관련성을 나타내는 숫자

1. 기본 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에도 동일하게 줘야 한다

2. 집계 프레임워크 사용 (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 연산이다

3. 텍스트 승수 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 } } //조정된 점수를 기준으로 내림차순 정렬
    ]
)

 

 

텍스트 검색 언어 설정

언어별로 형태소 분석 내용이 달라진다, 하지만 불용어 사전은 사용자 정의 기능이 없다

하지만 '단순 언어 특수적 접미사 형태소 분석'을 사용할 뿐임: 더 많은 기능은 전용 텍스트 검색 엔진 사용하자

언어 설정 방법

  1. 인덱스 생성 시 지정

     db.collection.createIndex(
                                     {'$**': 'text'},
                                     {weights: {...}},
                                     name: ...,
                                     default_language: 'language_to_set');
  2. 도큐먼트 삽입 시 지정

     db.collection.insert({..., language: 'lang_to_set'});
  3. 텍스트 검색 시 지정

     db.collection.find(
                                     {$text: {$search: 'words', $language: 'lang_to_find'}});

언어가 none으로, 지정된 것이 없다면

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