본문 바로가기

AWS/ElasticSearch를 이용한 검색

1) ElasticSearch 설치하고 Springboot로 테스트하기

반응형

진행하고 있는 업무에서 검색 기능이 필요하여 찾아 보던중 ElasticSearch를 이용한 검색에 대해 알아보고 적용해 보기로 했다. ElasticSearch의 기본 개념에 대해서는 넘어가자. 적용 방법고 그 과정에서 알아야 할것에 대해 알아 본다.

 

먼저 엘라스틱서치를 설치해 보자.

https://www.elastic.co/kr/elasticsearch/

 

Elasticsearch: 공식 분산형 검색 및 분석 엔진 | Elastic

Elasticsearch는 속도, 수평적 확장성, 안정성 및 간편한 관리를 위해 설계된 선도적인 분산형 RESTful 무료 오픈 소스 검색 및 분석 엔진입니다. 무료로 시작하세요.

www.elastic.co

위 사이트에서 엘라스틱서치를 다운받아 실행해 보자. 최신버전이 8.0 이지만 springboot의 버전 호환성 문제로 7.17을 설치 한다.

엘라스틱 서치를 다운받고 실행해 보자.

위와 같이 엘라스틱 서치를 다운받고 기본으로 실행시켜서 9200 포트로 호출해 보면 위와 같은 설정 정보가 보인다.

위 내용은 cluster_name  , name은 config 파일에서 변경했다.

 

이제 설치된 엘라스틱 서치의 상태정보와 설정을 편하게 위해 

https://github.com/lmenezes/cerebro/releases 에 들어가서 세레브로를 설치해보자.

세로브로를 설치하고 실행하고 나서 아래 그림과 같이 호출해 보자.

bin/cerebro -Dhttp.port=1234

node address에 앞에서 실행했던 locahost:9200을 입력하고 Connect한다.

접속하게 되면 설치된 엘라스틱서치의 상태 정보가 표시 된다. 

현재 es-node-1에는 테스트로 입력한 content , contents index가 두개 생성 되어 있는것이 보인다.

여기까지 하고 이제 Springboot에서 어떻게 연동을 하는지를 알아보자.

plugins {
    id 'org.springframework.boot' version '2.6.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.devracoon'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

build.gradle파일이다. 엘라스틱서치와의 연동을 위해 spring starter data 를 제공한다.

package com.devracoon.elasticsearchtest.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;

@Configuration
public class ElasticSearchConfig {

    @Value("${es.url}")
    private String hostname; // localhost

    @Value("${es.port}")
    private Integer port; // 9200

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(RestClient.builder(new HttpHost(hostname, port, "http")));
    }

    @Bean
    public ElasticsearchOperations elasticsearchTemplate() {
        return new ElasticsearchRestTemplate(restHighLevelClient());
    }

}

엘라스틱서치와 연결을 위한 Client 설정을 추가한다.

엘라스틱서치에 데이터를 넣고 조회 하고 수정하고 삭제 하기 위해 client를 이요하는 방법 elasticsearchOperations을 이용하는 방법 

그리고 Repository를 이용하는 방법을 하나씩 알아 본다.

먼저 검색할 데이터를 엘라스틱서치에 올려야 하는데 이때 데이터가 저장될  DB에서 테이블과 비슷한 역활을하는것이 index 이다.

이 index를 생성하거나 삭제 할때 엘라스틱서치 api를 직접 호출할수도 있고 spring api를 이용할수도 있다.

요기서는 먼저 spring api를 이용해서 만들어 보자. 

(나중에 엘라스틱서치의 index에 분석기를 등록하는 부분에서 엘라스틱 api를 이용해서 만드는것을 알아보자.)

@PostMapping(value="/create_index")
public CreateIndexResponse createIndex() {
    CreateIndexRequest request = new CreateIndexRequest("contents_test_2");

    request.settings(Settings.builder()
            .put("index.number_of_shards", 1)
            .put("index.number_of_replicas", 0)
    );

    CreateIndexResponse createIndexResponse = null;
    try {
        createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
    }catch(ElasticsearchStatusException es_exception){
        log.error("es_exception " , es_exception);
    }catch (Exception e){
        e.printStackTrace();
    }

    log.info("createIndexResponse : {}" , createIndexResponse);

    return createIndexResponse;
}

위와 같이 엘라스틱서치 index 생성이 완료되었고  , 응답값으로 보내준 response를 정상적으로 받아졌다.

앞에서 설치한 세레브로에서 인덱스가 만들어졌는지 보자.

contents_test_2 라는 인덱스가 만들어졌다.

이제 저장할 테이블에 해당하는 인덱스를 만들었으니 인덱스에 검색을 위한 데이터를 넣어 보자.

두개의 테스트 데이터를 넣어 보자.

엘라스틱서치에 데이터를 넣기 위해 엘라스틱서치 Document 형태의 Content DTO를 엘라스틱서치에 담을 필드값을 설정한다.

package com.devracoon.elasticsearchtest.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "contents_test_2" )
@Builder
public class Content {
    @Id
    private String contentId;
    @Field(type = FieldType.Text)
    private String contentText;
    @Field(type = FieldType.Text)
    private String title;
}
@PostMapping(value="/create_content_index")
public Content create_content_index() {
    Content content = Content.builder()
            .contentId("content1")
            .title("테스트하고 있는 컨텐츠 입니다. aaa bbb ccc ddd ")
            .contentText("이것은 테스트하고 있는 컨텐츠. 서치조건1 시치조건2 우편함 , 우리나라  1111 22222 33333")
            .build();
    content = contentSearchRepository.save(content);

    Content content1 = Content.builder()
            .contentId("content2")
            .title("우리나라 대한민국 대한민국만세 태극기만세 ")
            .contentText("동해물과 백두산이 마르고 닳도록 하느님이보우하사 우리나라만세")
            .build();
    content1 = contentSearchRepository.save(content1);
    return content1;
}

데이터를 입력하고 나서 세레브로에서 content_test_2 를 보면 docs 가 0 -> 2 로 변경되어 있는것이 보인다.

이제 잘 검색이 되는지 확인해 보자.

QueryDsl 형태의 paging request를 이용해서 쿼리해 보자. sql에서의 like 검색 효과를 얻고 싶어 wildcard query를 이용했다.

영어 일때는 여기까지 입력해도 형태소 분석과 wildcard 를 이용한 검색으로 무리가 없지만 한글이되면서 조금 문제가 생기는 경우가 있는것 같다. ( 이부분은 나중에 한글 분석기를 설정하고 사용하는부분에서 따로 공부해보자.)

@GetMapping(value="/find_conetnt_index")
public SearchHits<Content> find_conetnt_index(String title , String contentText) {

    PageRequest pageRequest = PageRequest.of(0, 100);
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    if(title != null && !"".equals(title)){
        boolQueryBuilder.should(QueryBuilders.wildcardQuery("title" , "*"+title+"*"));
    }

    if(contentText != null && !"".equals(contentText)){
        boolQueryBuilder.should(QueryBuilders.wildcardQuery("contentText" , "*"+contentText+"*"));
    }


    NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).withPageable(pageRequest).build();
    SearchHits<Content> contents = elasticsearchOperations.search(nativeSearchQuery , Content.class);

    return contents;

}

title만 검색했을때 정상적으로 like 검색된다. 

이제 contentText도 같이 조회를 해보자 조회 쿼리를 만들때 should( or 와 같음) 사용했기 때문에  title 또는 contextText에 둘중 하나만 검색 되어도 결과가 나오게 했다.

 

title이 테스트가 들어가는 content1 과 contentText에 우리가 포함 되는 content2까지 두개가 검색되는것을 확인했다.

다음에는 index를 만들때 분석기를 설정하고 해당 분석기를 Document에 설정하는부분까지 공부해 보자.

오늘은 여기까지