
사내에서 제안 검색어를 사용하는 방법에 대해 작성해 보겠습니다.
우선 플러그인으로는 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
 
'ELK' 카테고리의 다른 글
| Elasticsearch 커스텀 분석기 만들기 (이슈) (2) | 2023.09.20 | 
|---|---|
| Elasticsearch 커스텀 분석기 만들기 (0) | 2023.09.19 | 
| es_rejected_execution_exception 처리하기 (2) | 2023.09.12 | 
| Elasticsearch 롤링 배포 (2) | 2023.06.23 | 
| Elasticsearch Reindex 성능 개선 (1) | 2023.06.12 |