개발일기

ElasticSearch을 사용한 학교 정보 검색어 자동완성 본문

취준생 프로젝트

ElasticSearch을 사용한 학교 정보 검색어 자동완성

한둥둥 2024. 8. 28. 13:05

앞에서는 ElasticSearch를 사용하기 위한 docker-compose로 다운로드 받는 방법을 소개하였다. 
https://seounggyun.tistory.com/259

 

docker-compose ELK 설치

우선 ELK를 사용하는 이유는 ElasticSearch를 사용하여 자동완성 기능을 만들 예정이며, Logstash , kibana, elasticSearch를 통하여 logback을 통하여 log를 분석하여 그래프로 볼 예정이다.이에 따라서 해당 기

seounggyun.tistory.com

 

이제는 시간이 좀 많이 지났지만 ElasticSearch를 사용해보자. 

우선 ElasticSearch가 최근에 많이 바뀌어 구글에 있는 내용들은 잘 동작하지 않는 코드가 많다. 덕분에 ElasticSearch Docs와 Gpt와의 만담을 통해서 구현을 완료하였다. 

 

본격적으로 세팅하는 방법에 대해 알아보자 

 

🔥 ElasticSearchConfig

 

package darkoverload.itzip.global.config.elastic;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;


@Configuration
@EnableElasticsearchRepositories(basePackages = "org.springframework.data.elasticsearch.repository")
public class ElasticSearchConfig extends ElasticsearchConfiguration {

    @Value("${spring.elasticsearch.uris}")
    private String elasticsearchUrl;

    @Value("${spring.elasticsearch.username}")
    private String username;

    @Value("${spring.elasticsearch.password}")
    private String password;

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
                .connectedTo(elasticsearchUrl)
                .withBasicAuth(username, password)
                .build();
    }

}

 

해당 설정은 ElasticSearch에 해당하는 Config이다. 

여기서 핵심은 JPARepository를 Import받으면 Spring Boot는 기본적으로 JPA 레포지토리를 기준으로 레포를 찾아오기에 여기서 @EnableElasticSearchRepositories를 통해서 베이스 패키지를 등록하여 사용해주어야 한다. 

 

 

 

취준생 프로젝트에서는 다양한 DB를 사용하고 있으며 이에 따라서 DB를 SpringBoot가 Repository를 읽을 수 있게 처리를 하였는데 이에 대한 부분은 팀원이 작성한 글이 있어 해당 글을 참고하면 좋을 듯하다. 

 https://naturecancoding.tistory.com/120

 

[Database/Error] spring에서 db 2개 사용할 때 생기는 Bean 문제 해결

1. 해결 방법jpa가 repository를 탐색하지 않도록 하는 커스텀 어노테이션을 사용해서 Jpa의 @ComponentScan.Filter에 걸리도록해 리jpa리파지토리 등록을 자동으로 하지 않도록 했다.QuerydslConfig@EnableJpaReposi

naturecancoding.tistory.com

이 글은 진짜 맛있긴하더라 석원님 고마워요🙏

 

나의 레포지토리 구조이다. 

repository/
├── custom/
│   ├── CustomSchoolRepository.java
│   └── CustomSchoolRepositoryImpl.java
└── SchoolRepository.java

이런식으로 CustomSchoolRepository를 작성한 이유는 JPA, ElasticSearch를 추후에 있을 곳에 동시에 적용하기 위해 위에 패키지 구조를 택하였다. 하지만 다 끝나고 보니 괜히 이렇게 했나 엘라스틱 서치만 써서 학교정보만 단순히 조회해올텐데라는 생각이 들었다. 

아직 갈 길이 멀다. 

 

 

@RequiredArgsConstructor
public class CustomSchoolRepositoryImpl implements CustomSchoolRepository{

    private final ElasticsearchOperations elasticsearchOperations;

    @Override
    public List<String> searchSchool(String schoolName) {
        final int size = 10;
        Criteria criteria = new Criteria("school_name").contains(schoolName);
        PageRequest pageRequest = PageRequest.of(0, size);
        CriteriaQuery query = new CriteriaQuery(criteria, pageRequest);

        // 검색 수행
        return elasticsearchOperations.search(query, SchoolDocument.class)
                .stream()
                .map(hit->hit.getContent().getSchoolName())
                .collect(Collectors.toList());
    }
}



😇 ElasticsearchOperations


ElasticsearchOperations는 Elasticsearch와의 통신을 추상화하는 스프링 데이터의 인터페이스입니다. 이 인터페이스를 사용하여 Elasticsearch에서 데이터를 검색하고 조작할 수 있습니다.

 

 

🤔 Criteria

 

Criteria는 Elasticsearch에서 검색할 조건을 정의하는 클래스입니다. 여기서는 school_name 필드가 주어진 schoolName 문자열을 포함하는지 여부를 기준으로 검색을 수행합니다.

 

🫡  PageRequest

 

PageRequest는 페이징 처리를 위한 클래스입니다. 여기서는 첫 번째 페이지(0번째)에서 최대 10개의 결과를 반환하도록 설정합니다.

 

🥹 CriteriaQuery

 

CriteriaQuery는 Criteria와 PageRequest를 기반으로 Elasticsearch 쿼리를 구성하는 클래스입니다. 이 쿼리를 사용하여 Elasticsearch에서 검색을 수행합니다.

 

 

😎 search

elasticsearchOperations.search(query, SchoolDocument.class)는 정의된 쿼리를 사용하여 Elasticsearch에서 검색을 수행하고, 결과를 SchoolDocument 타입의 객체로 반환합니다.

 

 

🚍 내가 해당 방식을 적용하기까지의 과정

 

각각에 대한 부분을 작성을 하였고 사실 별거 없다.

내가 자동완성에 ElasticSearch를 사용한 이유는 자동완성 기능에 매우 적합하다. 그 이유는 데이터를 저장하면서 역색인구조를 사용해 각 단어와 문구의 위치를 저장하여 입력되는 검색에 대해 빠르게 결과를 할 수 있는 이유이다. 

 

-> 역색인은 따로 추가적으로 공부하여 게시글을 작성하겠습니다. 

 

엘라스틱 서치는 다양한 것들을 지원하며 다양한 예시를 아래에서 보여드리겠습니다.

 

✨ First. Edge N-gram 분석기를 설정해야하며 해당 분석기를 설정하는 방법이 있습니다.

 

해당 코드는 예시입니다.

PUT /schools
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "edge_ngram_tokenizer": {
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 20,
          "token_chars": [
            "letter"
          ]
        }
      },
      "analyzer": {
        "edge_ngram_analyzer": {
          "type": "custom",
          "tokenizer": "edge_ngram_tokenizer",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "school_name": {
        "type": "text",
        "analyzer": "edge_ngram_analyzer",
        "search_analyzer": "standard"
      }
    }
  }
}

 

만약에 진행한다면 위에 같은 방식으로 진행이 되었을 것이고 하지만 학교데이터는 어떤게 많이 사용될지 예측할 수 없으므로 사용자는 다양한 학교를 다닐 것을 예상하여 해당 방법으로 진행하지 않았습니다. 

 

 

📚 Completion suggester 방법입니다


Completeion suggester는 ElasticSearch에서 제공하는 또 다른 자동완성 기능이고 해당 방식으로 진행하려고 하니 많은 외국자료 및 국내자료가 @Deprecated되어 있어 해당 방식으로 진행하지 못하였습니다.  아마 적용이 된다면 구 버전 ElasticSearch일 것으로 판단됩니다. 

 

😇 인덱스 생성 

 

PUT /schools
{
  "mappings": {
    "properties": {
      "school_name": {
        "type": "completion"
      }
    }
  }
}

 

🚴🏻‍♂️ 데이터 인덱싱 

 

POST /schools/_doc/1
{
  "school_name": "Springfield High School"
}

POST /schools/_doc/2
{
  "school_name": "Spring Valley School"
}

POST /schools/_doc/3
{
  "school_name": "Springside Chestnut Hill Academy"
}

 

🛞 자동완성 쿼리 

 

POST /schools/_search
{
  "suggest": {
    "school-suggest": {
      "prefix": "Spr",
      "completion": {
        "field": "school_name",
        "size": 10
      }
    }
  }
}

 

해당 방식은 ElasticSearch에 들어가서 인덱싱을 적용시켜 확인 할 수 있습니다. 

하지만 저희는 Spring 서버에서 적용해야하기에  ElasticSearch Docs를 정처 없이 떠돌던 중 prefixQuery방식으로 하는 방법을 찾았습니다. 그렇게 해서 작성하게 된 코드는 위에 작성한 코드 입니다. 

 

 

사실 솔직하게 말하면 데이터가 겨우 1만2천 건 밖에 되어 있지 않아 RDBMS를 사용하더라도 크게 문제 없었을 것으로 판단하였지만 앞으로 많은 자동완성기능을 이력서 부분에 추가해야하기에 ElasticSearch를 적용하였습니다. 

 

해당 글에 부족한 부분이 많으니 참고하여 봐주셨으면 좋겠습니다.