본문으로 바로가기
반응형


사내에서 제안 검색어를 사용하는 방법에 대해 작성해 보겠습니다.

 

우선 플러그인으로는 analysis-icu와 QueryDsl의 Suggest를 사용합니다.

 

analysis-icu 분석 플러그인은 ICU(Interational Components for Unicode) 라이브러리를 사용하여 아시아 언어에 대한 향상된 분석, 유니코드 정규화, 유니코드 인식 대소문자 접기, 대조 지원 및 음역을 포함하여 확장된 유니코드 지원을 추가합니다.

 

 

analysis-icu 분석 플러그인은 문자 필터, 토크나이저, 토큰 필터를 제공하는데 그중 문자 필터인 icu_normalizer를 사용하겠습니다.

 

플러그인 설정

 

name

  • 정규화의 종류를 지정.
  • nfc(normalization Form C)는 문자열을 정규화할 때 모든 조합 문자를 조합 문자로 변환하여 표준 형식을 만듦.

mode

  • Unicode 정규화 모드 지정.
  • compose: 하나의 문자로 합치는 방식
  • decompose: 문자열을 구성하는 하위 문자로 분해하는 방식

type

  • 문자 필터의 종류를 지정.
  • icu_normalizer는 Unicode 문자열을 정규화하는 데 사용.

Suggest 쿼리는 제공된 텍스트를 기반으로 유사해 보이는 용어를 제한하는 Query Dsl 입니다.

POST _search
{
  "suggest": {
    "text" : "tring out Elasticsearch",
    "my-suggest-1" : {
      "term" : {
        "field" : "message"
      }
    },
    "my-suggest-2" : {
       "term" : {
        "field" : "user"
       }
    }
  }
}

 

text: 제안 텍스트.

 

suggest_mode

  • missing: 가장 유사한 검색 제안을 제공. 입력된 검색어와 가장 일치점을 가지는 검색어를 추천함. (default)
  • popular: 가장 인기 있는 검색어나 검색어 구성요소를 추천함.
  • always: 항상 검색어를 제안함. (사용자가 입력한 키워드도 포함됨)

 

sort: 제안 텍스트 용어별로 제안을 정렬하는 방법을 정의.

  • score: 먼저 점수를 기준으로 정렬한 다음 문서 빈도, 용어 자체를 기준으로 정렬.
  • frequency: 문서 빈도를 기준으로 정렬한 다음 유사성 점수, 용어 자체를 기준으로 정렬.

 

string_distance: 제안된 용어가 얼마나 유사한지 비교하기 위해 사용된 문자열 거리 구현 알고리즘.

  • jaro_winkler: Jaro-Winkler 알고리즘을 기반으로 한 문자열 거리 알고리즘.

실습

 

Elasticsearch 엔진에 analysis-icu 플러그인을 설치 방법

sudo bin/elasticsearch-plugin install analysis-icu

 

엘라스틱서치 클러스터 docker-compose.yml

 

GitHub - lgm3555/todo-docker: Docker STUDY

Docker STUDY. Contribute to lgm3555/todo-docker development by creating an account on GitHub.

github.com

 

플러그인 설치 확인

GET _cat/plugins

es1 analysis-icu  7.8.1
es1 analysis-nori 7.8.1
es2 analysis-icu  7.8.1
es2 analysis-nori 7.8.1

 

제안 검색어 인덱스 생성

위에서 설명했던 icu_normalizer 문자 필터를 가지고 icu_analyzer를 만들어 keyword.icu 필드에 분석기로 넣었습니다.

PUT propose-keyword
{
 "mappings": {
    "properties": {
      "keyword": {
        "type": "text",
        "fields": {
          "icu": {
            "type": "text",
            "analyzer": "icu_analyzer"
          }
        }
      }
    }
  },
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "icu_analyzer": {
            "filter": [
              "lowercase"
            ],
            "char_filter": [
              "icu_normalizer"
            ],
            "tokenizer": "standard"
          }
        },
        "char_filter": {
          "icu_normalizer": {
            "mode": "decompose",
            "name": "nfc",
            "type": "icu_normalizer"
          }
        }
      }
    }
  }
}

 

Suggest 쿼리 사용

my-suggestion이라는 제안을 만들고 노트븍 키워드를 jaro_winkler 알고리즘 방식으로 유사한 키워드를 찾겠습니다.

POST propose-keyword/_search
{
  "suggest" : {
    "my-suggestion" : {
      "text" : "노트븍",
      "term" : {
        "field" : "keyword.icu",
        "string_distance" : "jaro_winkler"
      }
    }
  }
}



{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "my-suggestion" : [
      {
        "text" : "노트븍",
        "offset" : 0,
        "length" : 3,
        "options" : [
          {
            "text" : "노트북",
            "score" : 0.952381,
            "freq" : 1
          }
        ]
      }
    ]
  }
}

 

 

Suggest 호출 클라이언트와 결과 값입니다.

public class SuggestExample {
    public static void main(String[] args) throws IOException {
        // Elasticsearch 클라이언트 초기화
        RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost("localhost", 9200, "http"))
                .setHttpClientConfigCallback(httpClientBuilder -> {
                    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                    credentialsProvider.setCredentials(AuthScope.ANY,
                            new UsernamePasswordCredentials("elastic", "changeme"));
                    return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                });

        try (RestHighLevelClient client = new RestHighLevelClient(restClientBuilder)) {
            // SearchRequest 초기화
            SearchRequest searchRequest = new SearchRequest("propose-keyword");

            // Suggest 쿼리 설정
            SuggestBuilder suggestBuilder = new SuggestBuilder();

            // SuggestionBuilder를 사용하여 Suggest 쿼리 작성
            SuggestionBuilder<?> completionSuggestionBuilder = SuggestBuilders.termSuggestion("keyword.icu")
                    .text("노투븍")
                    .size(5); // 추천 결과 개수 설정

            // SuggestBuilder에 SuggestionBuilder 추가
            suggestBuilder.addSuggestion("my-suggestion", completionSuggestionBuilder);

            // SearchRequest에 SuggestBuilder 설정
            searchRequest.source(new SearchSourceBuilder().suggest(suggestBuilder));

            // Elasticsearch에 요청 보내기
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            // 결과 출력
            Suggest suggest = searchResponse.getSuggest();
            List<? extends Option> options = suggest.getSuggestion("my-suggestion").getEntries().get(0).getOptions();

            for (Option option : options) {
                String text = option.getText().string();
                System.out.println("Suggested Text: " + text);
            }
        }
    }
}


// 결과값
Suggested Text: 노트북

 

 

참고

  • https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/writing-analyzers.htm
  • https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html
  • https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu-normalization-charfilter.html

 

반응형