본문 바로가기
ELK

Elasticsearch를 이용한 문서 유사도 검색과 Springboot를 통한 구현 (설정편)(2)

by AndoneKwon 2020. 12. 2.

지난번 게시글에 이어서 (약..반년만에 글을 쓰는것 같은..) 스프링부트에서 어떻게 Elasticsearch를 이용해 유사도를 활용한 검색을 하는지 작성하겠다.

 

기본적으로 지난 글 내용을 참조하여서 특정 필드에 대해서 유사도를 계산하는 인덱스를 만들어줘야 한다. 공식 DOC을 찾아보면 쉽게 찾아서 적용시킬 수 있으며 검색할때 사용하는 analyzer도 반드시 지정해줘야 한다.(tokenizer를 이용해서 나오는 결과는 추후에 첨부하도록 하겠다.)

 

아무튼.. 인덱스 설정 및 생성을 완료하였다면 이젠 엘라스틱서치로 요청을 할 차례이다.

 

기본적으로 ES는 json을 이용해 HTTP Method인 Get, Post, Delete, Put을 이용해 모든 요청 받고 리턴해주게 된다. 

 

그런데..사실 검색을 할 때에 보내는 json의 형식은 고정되어 있다. 따라서 한번만 정의해주면 되는데 json의 깊이가 꽤 깊어 직접 그 모양을 만들어 주는 것은 JAVA에서는 굉장히 힘들며 수정도 쉽지 않다.

 

따라서, 검색을 위한 Object를 따로 생성해줘야 한다.

 

우선, 서비스의 요청구사항을 설명하도록 하겠다.

 

본인이 개발했던 서비스의 경우 검색을 통해서 가게의  "이름", "소개", "메뉴", "주소" 필드에 일치하는 정보가 있는 가게의 정보를 주는 것이었다. 따라서 Multi match가 필요했고 게시물이 지워졌을수도 있기 때문에 지워진 게시물들은 제외해야 한다.

 

마지막으로 불필요한 정보는 포함하여 보내면 안된다. 따라서 아래와 같은 json이 필요하다.

{
   "from":0,
   "size":20,
   "_source":[
      "_id",
      "menu",
      "shopname",
      "address",
      "introduce",
      "_score",
      "category",
      "main_photo",
      "likenum",
      "grade_avg",
      "photo1",
      "photo2",
      "photo3",
      "photo4",
      "photo5",
      "photo6",
      "photo7",
      "photo8",
      "photo9",
      "photo10"
   ],
   "query":{
      "bool":{
         "should":{
            "multi_match":{
               "query":"강남",
               "fields":[
                  "menu^2",
                  "shopname",
                  "introduce",
                  "address^1.5"
               ]
            }
         },
         "filter":{
            "term":{
               "is_deleted":false
            }
         }
      }
   }
}

보다시피.. 꽤 복잡하다. 저러한 json 오브젝트가 나오게 된 것은 엘라스틱서치 공식문서를 따로 찾는 편이 빠를것 같다.

(본 블로그에선 주인이 개을러서 저거까지는 못할것 같다..)

 

이러한 요청을 보내기 위해 만든 스프링부트의 파일 구조는 다음과 같다.

 

프로젝트 구조

그리고 이번에는 스프링부트에서 제공하는 starter-elasticsearchdata를 이용하지 않고 엘라스틱서치 자체의 의존성을 이용할 것이기 때문에 반드시 추가해줘야 하는 의존성은 다음과 같다.

Gson은 후에 Object를 Json화 하기 위해서 생성한 라이브러리이다.

    implementation 'org.elasticsearch.client:elasticsearch-rest-client'
    implementation 'com.google.code.gson:gson:2.8.5'

 

이제 ElasticsearchConf 파일로 가보자. 이 부분은 엘라스틱서치를 연결하기 위한 설정 부분이며 @Configuration 어노테이션을 이용하여 빈을 등록해주어 싱글톤으로 작동하게 한다. 코드는 다음과 같다.

package com.followme.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;


@Configuration
public class ElasticsearchConf implements InitializingBean {
    @Value("${elasticsearch.host}")
    String hostIP;
    @Value("${elasticsearch.port}")
    String hostPort;
    private RestClient restClient;

    public RestClient getRestClient(){
        return restClient;
    }



    @Override
    public void afterPropertiesSet() throws Exception {
        this.restClient=RestClient.builder(new HttpHost(hostIP, Integer.parseInt(hostPort),"http")).build();
    }
}

 

이것을 설정하기 위해서 굉~장히 당황하고 고생했던 부분이 바로 application.properties에서 Elasticsearch가 설치된 서버의 IP주소와 Port를 받아 오는 것이었는데 이것은 스프링의 빈 관리 주기와 관련이 있기 때문에 이 부분에 대해서는 따로 Spring boot 카테고리에서 설명하도록 하겠다.

 

간단하게 설명하자면 스프링부트는 빈을 먼저 등록하고 Properties를 셋팅하기 때문에 발생한 문제였다.

 

이것은 InitializingBean을 상속받아 afterPropertiesSet이라는 메서드를 오버라이드 하고 Config에서 할 동작을 적어주면 된다.

 

즉, HttpHost를 이용해 http로 연결을 만들어 주고 그것을 이용해 Elasticsearch Client Object를 생성하여(빌더패턴을 통해) ResetClient에 집어넣어 준게 된다. 따라서 이 과정을 통해서 Elasticsearch와의 연결 만들고 이것을 통해서 Elasticsearch를 사용할 준비가 된 것이다.

 

그리고 이 Client는 get 함수를 이용해 얻어지도록 한다.

 

여기까지가 Elasticsearch를 사용하기 위해 연결하는 과정이다.