엘라스틱서치 부분 검색 기능 개발


텍스트 분석

“엘라스틱서치는 루씬을 기반으로 구축된 텍스트 기반 검색엔진이다.”

일반적으로 특정 단어가 포함된 문서를 찾으려면 검색어로 찾을 단어를 입력하면 될 것이라 생각하겠지만

엘라스틱 서치의 기본 분석기는 내가 생각한대로 동작하지 않았다.

별도의 설정 없이 “엘라스틱서치”나 “텍스트”라고 입력해도 위 문장이 검색되지 않는다는 것이다.


엘라스틱서치는 문서를 색인하기 전에 해당 문서의 필드 타입이 무엇인지 확인하고

텍스트 타입이면 분석기 이용해 분석한다.

텍스트가 분석되면 텀(term)으로 나눠서 형태소 형태로 분석된다.

해당 형태소는 특정 원칙에 의해 필터링되어 단어가 삭제되거나

추가, 수정되고 최종적으로 역색인된다.



1) 역색인 구조

역색인 구조는 다음과 같이 요약할 수 있다.


색인 파일들에 들어간 토큰만 변경되어 저장되고 실제 문서의 내용은 변함없이 저장된다.

색인할 때 특정한 규칙과 흐름에 의해 텍스트를 변경하는 과정을 분석(Analyze)이라고 하고 해당 처리는 분석기(Analyzer)라는 모듈을 조합해서 이루어진다.



2) 분석기 구조

분석기는 기본적으로 다음과 같은 프로세스로 동작한다.


문장을 분석하기 전에 입력 텍스트에 대해 특정한 단어를 변경하거나 HTML과 같은 태그를 제거하는 역할을 하는 필터

해당 내용은 텍스트를 개별 토큰화하기 전의 전처리 과정이며,

ReplaceAll() 함수처럼 패턴으로 텍스트를 변경하거나 사용자가 정의한 필터를 적용할 수 있습니다.


TOKENIZER FILTER는 분석기를 구성할 때 하나만 사용할 수 있으며 텍스트를 어떻게 나눌 것인지를 정의한다.

한글을 분해할 때는 한글 형태소 분석기의 TOKENIZER를 사용하고,

영문을 분석할 때는 영문 형태소 분석기의 TOKENIZER를 사용하는 등 상황에 맞게 적절한 TOKENIZER를 사용하면 된다.


토큰화된 단어를 하나씩 필터링해서 사용자가 원하는 토큰으로 변환한다.

Token Filter는 여러 단계가 순차적으로 이뤄지며 순서를 어떻게 지정하느냐에 따라 검색의 질이 달라질 수 있습니다.


전체적인 프로세스는 다음과 같다.

Character Filter → Tokenizer Filter → Token Filter → Index 
                                           ↕️
                                          사전 




부분 검색 기능을 위한 설정

1) PostIndex

@Getter
@Setting(settingPath = "/tokenizer/setting.json")
@ToString
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Document(indexName = "post")
public class PostIndex {

	@Id
	private String id;

	@Field(type = FieldType.Text, analyzer = "word_analyzer")
	private String content;

	@Field(type = FieldType.Date)
	private ZonedDateTime createdAt;

	@Field(type = FieldType.Date)
	private ZonedDateTime updatedAt;

}

@Setting(settingPath = "/tokenizer/setting.json") 이 부분은 프로젝트 resource/** 경로의 파일을 찾는다.



2) /resources/tokenizer/setting.json

{
  "index": {
    "max_ngram_diff": "10",
    "analysis": {
      "analyzer": {
        "word_analyzer": {
          "tokenizer": "text_tokenizer",
          "filter": [
            "lowercase"
          ]
        }
      },
      "tokenizer": {
        "text_tokenizer": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 10,
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      }
    }
  }
}

해당 파일에 사용할 분석기, 필터 등을 설정하면 인덱스 생성시에 적용된다.



3) PostIndexRepository

@Repository
public interface PostIndexRepository extends ElasticsearchRepository<PostIndex, Long> {

	List<PostIndex> findByContent(String content);

	@Query("{\"match\": {\"content\": \"?0\"}}")
	List<PostIndex> searchByContent(String content, Pageable pageable);

}

위와 같이 jpql도 사용이 가능하다.



참고