본문 바로가기

언어/Java

ElasticSearch 스터디 정리 (3) - 엘라스틱서치 분석기

개요

오늘은 엘라스틱서치 분석기에 대해서 좀 얘기를 해보려고 한다. 검색 엔진에서는 제일 중요한 부분이라고 생각한다. 어떻게 텍스트를 분석하냐가 검색 엔진의 성능과 기능을 좌우한다고 생각한다. 처음 접하는 사용자 경우 결과가 검색되지 않거나, 원하지 않는 결과가 나온다면 잘못된 분석기가 설정되었을 확률이 매우 크다고 본다.

저번에 정리했던 내용에도 있는 예제인데, '아버지가 방에 들어가신다.' 라는 text가 들어온다면, 기본적으로는 아버지가, 방에, 들어가신다 요렇게 인덱싱이 될것이다. 물론 아버지로는 해당 document 로 조회 할 수가 없다. 그럼 아래 좀 더 자세히 확인해보자.

역색인 구조

책을 읽을때 특정 단어를 알고 있지만 해당 단어가 어떤 페이지에 있는지 확인하기 위해서 우리는 책의 마지막에 나열된 부분을 확인하다. 루씬의 역색인도 비슷한 개념이다. 아래와 같은 기능을 제공한다.

  • 모든 document에 포함된 고유 단어 리스트
  • 해당 고유 단어가 어떤 document에 포함되어 있는지에 대한 정보
  • 전체 문서에 각 단어가 몇 개 들어있는지에 대한 정보
  • 하나의 문서에 단어가 몇 번씩 출현했는지에 대한 정보

나는 읽으면서 자바의 Map 과 비슷한 구조를 가지고 있다고 생각이 들었다. document를 하나의 object 라고 생각하고 우리는 object 의 어떤 필드를 가져와서 key 로 만들고 Map 구조를 가진다.

class Document {
    T t;
    V v;
    K k;
}

위와 같은 구조의 Document 라는 클래스가 있다고 가정하자. 그리고 저기 있는 T 라는 타입을 가진 필드를 키로 잡는다면, 우리는 간단하게 아래와 같은 Map 을 정의 할수 있다.

Map<T, Document> map;

그렇게 된다면 우리는 쉽게 T 필드 값으로 빠르게 Document를 접근 할수도 있을것 이다. 물론 T 라는 필드가 유니크 하지 않는게 걱정되실수도 있다고 생각한다. 엘라스틱서치랑은 관계 없지만, HashMap 이 해쉬 충돌을 해결하는 방법과 비슷하게 수정 할 수 있다. (리스트는 LinkedList 나 ArrayList 중 편한것을 사용하면 되겠다.)

Map<T, List<Document>> map;

예제

아래와 같이 2개의 텍스트가 있다고 가정하자.

문서1: elastic is cool.
문서2: Elastic is great.

standard 분석기를 이용하면 아래와 같은 역색인 구조를 확인 할 수 있다.

토큰 문서 위치 빈도
elastic 문서1 1 1
Elastic 문서2 1 1
is 문서1, 문서2 2, 2 2
cool 문서1 3 1
greate 문서2 3 1

위와 같은 구조로 되어 있을때 만약에 elastic 으로 검색을 한다면 우리는 문서2가 조회되지 않고 문서1만 조회 될 것이다. 하지만 보통은 elastic 으로 검색했을때 문서1, 문서2 2개의 결과를 얻고 싶다고 생각된다.

그렇다면, 어떤 경우에는 구분을 하는게 더 좋을까? 고민했지만 답을 얻진 못했다.

분석기 구조

분석기는 기본적으로 아래와 같은 파이프라인으로 작동한다.

  • character filter: 특정 단어를 변경하거나 HTML 태그를 제거하는 역할을 한다.
  • tokenizer filter: 텍스트를 어떻게 나눌 것인지 정의한다.
  • token filter: tokenizer 로 토큰화된 토큰을 원하는 토큰으로 변환한다.

간단하게 인덱스 생성시 분석기를 정의해 보자.

## PUT /test_analyzer
## <B>Elasticsearch</B> is cool 를 아래 분석기로 분석해보겠다.
{
  "analyzer": {
    "custom_analyzer": {
      "type": "custom",
      "char_filter": [
        "html_strip"            // => Elasticsearch is cool
      ],
      "tokenizer": "standard",    // => Elasticsearch, is, cool
      "filter": [
        "lowercase"                // => elasticsearch, is, cool
      ]
    }
  }
}

기본 사용법

  • 분석 확인: _analyze API를 제공하기 떄문에 테스트 할 수 있다.
  • 필드 분석: 인덱스 생성시 직접 설정 가능하고, 컬럼에 지정할 수도 있다.
  • 검색 분석기 각각 설정: _search_analyzer 미 설정시 analyzer 사용
  • 참고
    • stopword(불용어): 보통 분석기는 불용어를 지정 가능하다. 지정 할 경우 filter 단계에서 불필요한(영어에서 i, my, mine 같은 분석에 별로 도움이 되지 않는) 단어를 제외하고 인덱싱 한다.

기본 분석기

  • Standard Analyzer: 공백 또는 특수 기호 기준 분리 및 소문자 변경.
  • Whitespace Analyzer: 단순 공백 기준 토큰 분리.
  • Keyword Analyzer: 텍스트를 하나의 keyword 처럼 처리.

전처리 필터 - Character Filter

텍스트 처리 파이프라인 에서 최초 실행하는 필터이다. 토크나이저에서 분리 작업을 진행하지만 전처리 같은 기능이 가능하기 때문에 많이 사용되지는 않는다.

주로 사용하는 필터는 Html strip char 필터. 다만 크롤링 프로그램이 아닌 이상, 전체 html 을 처리하는것보다, 일부 body에 포함된 컨텐츠를 텍스트를 넘기는게 좋을것 같다.

## PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "keyword",
          "char_filter": ["my_char_filter"]
        }
      },
      "char_filter": {
        "my_char_filter": {
          "type": "html_strip",
          "escaped_tags": ["b"]            // 여기에 포함된 태그 제외 제거한다.
        }
      }
    }
  }
}

토크나이저 필터

분석기에서 핵심 구성요소.

  • standard tokenizer
    • 대부분 기호 기준 분리
    • max_token_length 설정 가능 => 5인 경우, Hi NHNPAYCO => Hi, NHNPA, CO 이렇게 분리한다.
  • whitespace tokenizer
    • 공백 기준 분리
    • max_token_length: 상동
  • ngram tokenizer
    • 한 글자씩 분리
    • 파라미터(min_gram, max_gram, token_chars) => min_gram = 1, max_gram = 2 경우 Hello World => H, He, e, el, l, ll, ... 이렇게 분리됨.
  • edge ngram tokenizer
    • standard + ngram
    • 파라미터(min_gram, max_gram, token_chars) => min_gram = 2, max_gram = 4 경우 Hello World => He, Hel, Hell, Wo, Wor, Worl
  • keyword tokenizer
    • 분리하지 않음

토큰 필터

토크나이저에서 분리된 토큰을 변형하거나, 추가 / 삭제 같은 작업을 한다. 독립적으로 사용 할 수 없다.

  • ascii folding filter
    • ascii 에 해당되는 알파벳, 숫자, 기호를 제외한 문자를 ascii로 변환.
    • 루씬 ascii folding 사용 (하드 코딩... ㄷㄷ)
    • 한국어 안됨 => 한국어는 unicode 가능한 icu folding filter 사용
  • lowercase/uppercase filter
    • 이름에서 짐작 할수 있음.
  • stop filter
    • 인덱싱 되지 않게 하는 단어 등록
    • 인덱스 생성시 값을 줄수 있지만 운영환경에서 변경 어려움
    • 보통 stopword_path로 파일로 관리
  • stemmer filter
    • stemmer 알고리즘을 이용한 변환
      • 제일 쉽게 구현하는 방법은 lookup 사전 정의
      • ed, ing 같은 패턴 파악해서 정의
      • 어려운 알고리즘이 몇개 있는데, 이해 못함 ㅠ 나중에 기회가 되면 좀 자세히 볼 수 있도록.
    • 영단어 복수 => 단수형, 미래/과거형 -> 현재형 변환 등 작업 진행.
  • synonym filter
    • 동의어 처리 필터
    • stop filter 와 동일하게 생성시 지정 또는 파일 관리 가능
    • 동의어 추가(원 토큰 + 동의어) / 치환(원 토큰 추가 안하고 동의어만 추가) 가능
    • 동의어로 추가된 토큰 경우 get 할때 타입이 SYNONYM 으로 됨. (원래 텍스트는 이나 형식이 보통)
  • trim filter
    • java String trim 참고

Document API

Document 파라미터

  • 문서 ID
    • 필수 값
      • 지정하지 않으면 랜덤 UUID 생성
  • 버전 관리
    • 최초 값 1
    • 변경이 일어 날때마다 1씩 증가
  • op_type
    • mysql insert on duplicate key update 같은 방식으로 원래 작동한다, 다만 값이 있으면 api 실패 하기 위해서 op_type=create 지정시 create 하지 않으면 에러 반환.
  • timeout
    • 기본 1분
  • 매핑 자동 생성
    • 운영환경에서 false 추천

Index API

  • 인덱스에 문서 추가시 사용
  • 아래와 같은 응답을 받는다.
    • total은 총 복사본을 뜻함.
    • wait_for_active_shards 미 설정시 primative shard 에만 반영 => replica 업데이트 될 예정이기 때문에 업데이트 성공했다고 간주
      {
      "_shards" : {
      "total":2,
      "successful":1,
      "failed":0
      }
      }

아래 제공되는 API 는 특이사항 없음.

  • GET API
  • DELETE API
  • UPDATE API

Bulk API

  • 여러번 API 호출을 한번의 호출로 해결 할 수 있다.
  • 보통 HTTP 통신이기 때문에 배치에서 루프를 돌며 조회 하는 경우는 이런 방식도 고려하면 좋음

Reindex API

  • 주로 다른 인덱스로 복사시 사용
  • 검색시 정렬 작업은 리소스를 많이 사용하기 때문에 reindex로 미리 정렬한 데이터를 복사할 수도 있음
  • 기본적으로 1000건 단위 스크롤 수행