일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 완전탐색
- Generics
- SpringBoot
- 프로그래머스
- 알고리즘
- 계산기 만들기
- 구현
- 이분 탐색
- 누적합
- programmers
- binary search
- web
- 이분탐색
- 백준
- 프로젝트
- til
- Java
- 브루트포스
- Baekjoon
- 내일배움캠프
- 객체지향
- 코딩테스트
- OOP
- ES
- CSS
- BFS
- parametric search
- Spring
- Algorithm
- Elasticsearch
- Today
- Total
개발하는 햄팡이
[ES] 공지사항 게시판 검색 기능 구현하기(1) : Elasticsearch 인덱스 구조 설계 본문
[ES] 공지사항 게시판 검색 기능 구현하기(1) : Elasticsearch 인덱스 구조 설계
hampangee 2024. 11. 12. 09:15현재 진행 중인 프로젝트에서 디자인측에서 공지사항 검색과 일정 검색 기능을 넣어달라는 요청이 들어왔다.
검색창이 있는게 안어색하기도 하고 UI관점에서 편할 것 같아서 넣기로 결정.
일정 검색은 시간때문에 잠시 보류지만 공지사항 검색은 기획 초반에 무조건 있어야된다라고 얘기하기도 했었고,
그닥 어렵지 않으니 텍스트 검색을 위해 Elasticsearch로 구현하기로 했다.
그래서 이렇게저렇게 aws 서버를 하나 더 만들어서 그 서버는 EKL용으로 사용하기로 하고
(Elasticsearch가 메모리 사용량이 보기보다 좀 나가서 혹시 다른 서버 터질까봐
일단 서버를 하나 더 만들어서 그 곳에 Elasticsearch와 Kibana를 띄워서 사용하기로 했다.)
한글 검색을 위해 Nori Plugin도 설치하고 SpringBoot연동도 완료!
Elasticsearch 설치 및 SpringBoot 연동은 이전 글 참고
일단 공지사항의 형태가 대충 이렇게 생겼는데
@Entity
@Table(name = "notice")
@Getter
@Setter
public class Notice extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long noticeId;
private String mmMessageId;
private String title;
@Lob
private String content;
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
private String noticeTypeCd;
@Enumerated(EnumType.STRING)
@Column(name = "noticeTypeCd", updatable = false, insertable = false)
private NoticeType noticeType;
private String isEssentialYn = "N";
@Convert(converter = BooleanToYNConverter.class)
@Column(name = "isEssentialYn", updatable = false, insertable = false)
private Boolean isEssential;
}
제목 검색, 본문 검색, 작성자 검색이 되어야 하고..
오타가 나더라도 비슷한 단어가 있으면 검색이 되어야하며..
삭제된 글에서는 검색하지 않아야 하고..
딱 이정도만 간단하게 하면 될 것 같다.
아니면 검색어 추천 기능 정도 나중에 추가할 수 있지 않을까 하는데
일단은 검색 버튼을 눌렀을때 요청을 보내는 api부터 만들어야겠따.
사실 검색에 대한 구체적인 기획안은 없고 내가 만들고 싶은대로 만들면 되는 부분이라서
일단은 이렇게만 구현할 생각이다.
다음날 로직에 대해서 생각을 해봤는데 값을 리턴하는데에는 두 가지 방법이 있다.
1. 검색에 필요한 필드 + DB pk를 저장하여 결과값(20개 정도의 결과)의 아이디로 DB에서 조회하여러 리턴하는 방법
- 장점 :
-> 검색 필드와 아이디만 저장하므로 저장 공간이 줄어듦.
-> Id로 DB조회를 한 후 리턴하기 때문에 데이터 일관성을 유지할 수 있다.
- 단점 : ES조회 후 DB 조회를 한번 더 하기 때문에 비용이 증가한다.
해서,
데이터 크기가 크고 전체 데이터가 자주 업데이트 되는 경우,
검색 이후 상세 정보가 필수적인 경우,
이 방법을 선택하는 것이 좋고,
2. 리턴에 필요한 필드를 모두 es에 저장하고 결과 그대로 리턴하는 방법
- 장점 : DB를 조회하지 않기 때문에 비용감소, 빠른 응답 속도, 설계 단순.. 등
- 단점 :
-> 데이터 중복. ES와 DB에 같은 데이터를 두번 저장하여 저장 공간을 많이 쓴다.
-> 데이터 일관성 문제. 데이터가 수정될 때 동기화를 신경 써야한다.
특히, ES는 수정이 필요한 필드만 수정하지 않고 이름만 바뀌더라도 전부 다 새로 저장하는 로직이라서
수정 삭제가 빈번한 경우에 그닥 효율적이지 않다고 한다.
이는 데이터 크기가 상대적으로 작고, 수정이 빈번하지 않은 경우,
데이터의 빠른 조회가 필요하고 데이터가 항상 최신일 필요가 없는 경우에 적합할 수 있다.
현재 구현하려고 하는 기능은 공지사항 검색인데 기획상 수정이 안되고 추가 삭제만 되게끔 구현하여(수정하고 싶으면 삭제하고 다시 올려야 함.)
그냥 리턴할 정보들을 다 저장하기로 했다. (2번 방법 선택!!)
이후 유저 이름 검색, 일정 검색 등을 구현할 예정인데
얘네는 수정도 많고 어차피 get요청으로 상세보기를 할 것 같아서 1번을 선택할 것이다.
완성 코드는
1. 인덱스 형태
package com.jetty.ssafficebe.search.document;
import jakarta.persistence.Id;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.format.annotation.DateTimeFormat;
@Getter
@Setter
@Document(indexName = "notice")
@Mapping(mappingPath = "/elasticsearch/notice-mappings.json")
@Setting(settingPath = "/elasticsearch/notice-settings.json")
public class ESNotice {
@Id
@Field(type = FieldType.Keyword)
private Long noticeId;
@Field(type = FieldType.Text)
private String title;
@Field(type = FieldType.Text)
private String content;
@Field(type = FieldType.Date, format = {}, pattern = "uuuu-MM-dd'T'HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime createdAt;
@Field(type = FieldType.Date, format = {}, pattern = "uuuu-MM-dd'T'HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime startDateTime;
@Field(type = FieldType.Date, format = {}, pattern = "uuuu-MM-dd'T'HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime endDateTime;
@Field(type = FieldType.Keyword)
private String isEssentialYn;
@Field(type = FieldType.Keyword)
private String noticeTypeCd;
@Field(type = FieldType.Keyword)
private Long createUserId;
@Field(type = FieldType.Keyword)
private String profileImageUrl;
@Field(type = FieldType.Text)
private String createUserName;
}
@Document는 서버를 실행 시킬 때 컴포넌트 어노테이션으로, elasticsearch의 인덱스를 자동으로 만들어주면서 인덱스라는 의미를 나타내는 어노테이션이다.
JPA의 @Entity와 같은 역할을 한다.
entity클래는 jakarta.Id를 사용했지만 elasticsearch는 Id를 명시할때 springframework.data에서 제공하는 id클래스를 무조건 붙여야된다고 한다.
저렇게만 하고 실행시키면 알아서 인덱스가 만들어지는데
우리의 프로젝트는 한글 검색이 잘되게 구현하는 것이 목적이기 때문에 mappings,json파일과 settings.json파일을 따로 만들어 연결 시켜 분석기를 설정해주었다.
한글 분석이 필요한 부분에
"analyzer": "my_analyzer"
이 코드를 넣어주면 된다.
2. mappings.json
{
"properties": {
"noticeId": {
"type": "keyword"
},
"title": {
"type": "text",
"analyzer": "jetty_notice_analyzer"
},
"content": {
"type": "text",
"analyzer": "jetty_notice_analyzer"
},
"createdAt": {
"type": "date",
"format": "yyyy-MM-dd'T'HH:mm:ss||epoch_millis"
},
"startDateTime": {
"type": "date",
"format": "yyyy-MM-dd'T'HH:mm:ss||epoch_millis"
},
"endDateTime": {
"type": "date",
"format": "yyyy-MM-dd'T'HH:mm:ss||epoch_millis"
},
"isEssentialYn": {
"type": "keyword"
},
"noticeTypeCd": {
"type": "keyword"
},
"createUserId": {
"type": "keyword"
},
"profileImageUrl": {
"type": "keyword"
},
"createUserName": {
"type": "text",
"analyzer": "jetty_notice_analyzer"
}
}
}
3. settings.json
{
"number_of_shards": 1,
"number_of_replicas": 2,
"refresh_interval": "30s",
"max_ngram_diff": 19,
"codec": "best_compression",
"routing": {
"allocation": {
"include": { "_tier_preference": "data_content" }
}
},
"analysis": {
"filter": {
"my_ngram_filter": {
"type": "ngram",
"min_gram": 1,
"max_gram": 20
}
},
"tokenizer": {
"my_nori_tokenizer": {
"type": "nori_tokenizer",
"decompound_mode": "mixed"
}
},
"analyzer": {
"jetty_notice_analyzer": {
"type": "custom",
"tokenizer": "my_nori_tokenizer",
"filter": ["my_ngram_filter"]
}
}
}
}
일단 요기까지 하고 다음엔 Controller, Repository, Service쪽을 구현하려고 한다!
'Database > Elasticsearch' 카테고리의 다른 글
[ES] Elasticsearch8.x.x & SpringBoot 연동 세팅하기 (0) | 2024.09.27 |
---|---|
[ES] Elasticsearch & Kibana 8.x.x 로컬에서 실행하기 (0) | 2024.09.27 |