- ElasticSearch란? : https://hyerin6.github.io/2021-09-17/es/
- ELK : https://hyerin6.github.io/2021-09-22/log-slack/
- 엘라스틱서치 검색기능 구현 방법 : https://github.com/hyerin6/TIL/blob/main/Mentoring/2021-09-23.md
High Level Rest Client 란?
ElasticSearch를 사용하는 자바 애플리케이션을 만들기 위한 client api이다.
(1) 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
Spring Data Elasticsearch를 사용하여 Spring 애플리케이션에서 Elasticsearch 인덱싱, 검색 및 쿼리를 사용할 수 있다.
또한, Spring Data Elasticsearch 에서 제공 하는 ElasticsearchRepository 인터페이스를 확장하는 저장소 인터페이스를 정의하면 해당 문서 클래스에 대한 CRUD 작업이 기본적으로 사용 가능하다.
(2) 환경 설정
- ElasticSearchConfig
@EnableElasticsearchRepositories("...")
@Configuration
public class ElasticSearchConfig {
@Value("${...}")
String elasticHost;
@Bean
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(RestClient.builder(HttpHost.create(elasticHost)));
}
}
- application.yml
spring:
elasticsearch:
rest:
uris: http://localhost:9200
ElasticSearch는 여러 노드를 등록할 수 있으며 HttpHost를 추가해주면 된다.
HttpHost의 파라미터로는 hostname, port 그리고 http 또는 https 사용의 스키마가 들어간다.
(3) 인덱스 생성
- PostIndex
@ToString
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Document(indexName = "post")
public class PostIndex {
@Id
private String id;
@Field(type = FieldType.Text)
private String content;
@Field(type = FieldType.Date)
private ZonedDateTime createdAt;
@Field(type = FieldType.Date)
private ZonedDateTime updatedAt;
}
@Document
어노테이션으로 인덱스 이름을 지정한다.- 각 필드는
@Field
어노테이션으로 설정한다. - createdAt, updatedAt을
ZonedDateTime
으로 선언한 것을 기억하자.
- PostIndexRepository
@Repository
public interface PostIndexRepository extends ElasticsearchRepository<PostIndex, Long> {
List<PostIndex> findByContent(String content);
}
ElasticsearchRepository를 상속받았다.
JpaRepository와 사용이 유사하다.
- PostService
@Slf4j
@RequiredArgsConstructor
@Service
public class PostService {
private final PostRepository postRepository;
private final ImageService imageService;
private final PostIndexRepository postIndexRepository;
@Transactional
public void create(User user, CreatePostRequest request) {
Post post = Post.builder()
.user(user)
.content(request.getContent())
.build();
Post savedPost = postRepository.save(post);
// imageService.create(post, request.getImages());
PostIndex postIndex = PostIndex.builder()
.id(String.valueOf(savedPost.getId()))
.content(request.getContent())
.createdAt(savedPost.getCreatedAt().atZone(ZoneId.of("Asia/Seoul")))
.updatedAt(savedPost.getUpdatedAt().atZone(ZoneId.of("Asia/Seoul")))
.build();
postIndexRepository.save(postIndex);
}
. . .
}
검색을 하기 위해 Post 객체를 저장할 때
PostIndex도 함께 저장해줘야 한다.
DB에 데이터를 저장하듯 IndexRepository를 이용하여 저장하면 엘라스틱서치에 데이터가 저장된다.
- 검색 테스트 코드
@Test
void getAllIndex() {
List<MultipartFile> images = new ArrayList<>();
List<CreatePostRequest> requests = List.of(
new CreatePostRequest("우주의 이야기", images),
new CreatePostRequest("우유의 이야기", images),
new CreatePostRequest("우기의 사랑", images),
new CreatePostRequest("우리 은행", images),
new CreatePostRequest("우리", images),
new CreatePostRequest("우리나라", images),
new CreatePostRequest("우주", images),
new CreatePostRequest("우주의 나라", images)
);
for (CreatePostRequest p : requests) {
postService.create(null, p);
}
List<PostIndex> index = postService.getAllIndex("우주의");
for (PostIndex i : index) {
System.out.println(i.toString());
}
}
결과
노란색 부분을 보면 @Document
어노테이션으로 설정한 Indexname으로 post가 생성되어 저장된 것을 확인할 수 있다.
ElasticSearch
엘라스틱 서치에 대해 처음 학습하면서 역색인 방식으로 텍스트를 저장하기 때문에
엘라스틱 서치의 검색이 빠르다는 것으로 게시글을 마쳤는데
엘라스틱 서치에 대해 조금 더 알아보자.
(1) Elasticsearch vs. Relational DB
Elasticsearch는 키워드가 어떤 Document에 있다고 저장하는 방식이다.
해쉬 테이블과 같이 big-o 표기법에 따르면 검색 시 O(1)의 효과를 낸다.
Relational DB는 텍스트 검색 시 문서 개수만큼 연산이 수행되기 때문에 O(N)이다.
Elasticsearch에 다음과 같이 저장된다.
(2) Mapping
매핑은 스키마라고 보면 된다.
엘라스틱 서치 매핑을 하지 않으면 모두 문자열로 저장되기 때문에 데이터를 시각화할 때 불편한 상황이 발생할 수 있다.
때문에 실무에서 매핑없이 데이터를 넣는 것은 위험하다.
Spring Data Elasticsearch를 사용했기 때문에 인덱스 매니저의 Mapping에 들어가보면
다음과 같이 각 필드가 잘 매핑되어 있는 것을 확인할 수 있다.
참고
Minsuk Heo 허민석
의 유튜브 강의
https://youtube.com/playlist?list=PLVNY1HnUlO24LCsgOxR_eK2Yi4sOgH9Pg