๐ ๏ธ ๊ฐ๋ฐ ํ๊ฒฝ
- Kotlin : 1.9.25
- Spring boot : 3.3.4
- Github
์ Elasticsearch๋ฅผ ์ฌ์ฉํ ๊น?
DB์์ ํน์ ๋ฌธ์์ด์ด ํฌํจ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ ๋ LIKE ์ฐ์ฐ์ ์ฌ์ฉํ๊ฒ ๋๋ค. ์๋ฅผ ๋ค์ด, 5๋ง ๊ฐ์ ํธ์์ ์ํ์ด ์๋ค๊ณ ์๊ฐํด๋ณด์. ๋ด๊ฐ '์ซ๋'์ ๊ฒ์ํ๋ ค๋ฉด, product ํ
์ด๋ธ์์ LIKE '%์ซ๋%' ๊ฐ์ ์กฐ๊ฑด์ผ๋ก ๊ฐ์ ์ฐพ๊ฒ ๋๋ค.
B-Tree ์ธ๋ฑ์ค๋ ์ ๋ ฌ๋ ํค๋ฅผ ๋ฐํ์ผ๋ก '์ด๋๋ถํฐ ์ด๋๊น์ง'๋ผ๋ ๋ฒ์๋ฅผ ๋น ๋ฅด๊ฒ ์ขํ ํ์ํ๋ ๊ตฌ์กฐ์ด๋ค. ๋๋ฌธ์, LIKE '์ซ๋%'์ ๊ฒฝ์ฐ ์ธ๋ฑ์ค๋ฅผ ํ ์ ์์ง๋ง, LIKE '%์ซ๋%'(leading wildcard) ๋ ๋ฌธ์์ด์ ์์(prefix)์ด ๊ณ ์ ๋์ง ์๊ธฐ ๋๋ฌธ์, ์ธ๋ฑ์ค๋ก ํ์ ๋ฒ์๋ฅผ ๋ง๋ค๊ธฐ๊ฐ ์ด๋ ต๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ์ตํฐ๋ง์ด์ ๋ ํ ์ด๋ธ ์ ์ฒด ๋๋ ์ธ๋ฑ์ค ์ ์ฒด๋ฅผ ํ์ผ๋ฉด์ ์กฐ๊ฑด์ ๊ฒ์ฌํ๋ ์คํ ๊ณํ์ผ๋ก ๊ธฐ์ธ๊ธฐ ์ฝ๊ณ , ๋ฐ์ดํฐ๊ฐ ๋ง์์ง์๋ก ๋น์ฉ์ด ๊ธ๊ฒฉํ ์ปค์ง ์ ์๋ค.
์ฆ, ๋ฐ์ดํฐ๊ฐ ๋ง์ ์ํฉ์์ LIKE '%keyword%' ๊ธฐ๋ฐ ๊ฒ์์ RDB ์
์ฅ์์ ๊ตฌ์กฐ์ ์ผ๋ก ๋ถ๋ฆฌํด์ง๊ธฐ ์ฝ๋ค. ๊ทธ๋ ๋ค๋ฉด Elasticsearch๋ ์ด๋ค ์ด์ ์ด ์๊ธธ๋ ๊ฒ์์ ๋ง์ด ์ฌ์ฉ๋ ๊น?
๊ฒ์ ๊ตฌ์กฐ
RDB์ B-Tree ์ธ๋ฑ์ค๊ฐ ์ ๋ ฌ๋ ๊ฐ์์ ๋ฒ์๋ฅผ ๋น ๋ฅด๊ฒ ์ค์ฌ๋๊ฐ๋ ๋ฐฉ์์ด๋ผ๋ฉด, Elasticsearch๋ ํ ์คํธ๋ฅผ ์ ์ฅ(์์ธ)ํ ๋ ๋ถ์(analyze) ๊ณผ์ ์ ๊ฑฐ์ณ ํ ํฐ(term) ๋จ์๋ก ๋ถํดํ๊ณ , “ํ ํฐ → ๋ฌธ์ ID ๋ชฉ๋ก” ํํ์ ์ญ์์ธ(inverted index)์ ์์ฑํ๋ค.
์๋ฅผ ๋ค์ด "๋๋ฐ์ด ์ซ๋ ์ฟ ํค", "์ซ๋์ซ๋ ๋ฌ๊ณ ๋", "์ซ๋ํ ์ด์ฝ์นฉ ์ฟ ํค" ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์์ธํ๋ค๊ณ ๊ฐ์ ํด๋ณด์(ํ ํฐ ๋ถํด ๊ฒฐ๊ณผ๋ ๋ถ์๊ธฐ ์ค์ ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์๋ค). Elasticsearch๋ ๊ฐ๋ ์ ์ผ๋ก ์๋์ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ์ ์งํ๋ค.
| ๋ฌธ์ | ๋ฐ์ดํฐ |
|---|---|
| 1 | ๋๋ฐ์ด ์ซ๋ ์ฟ ํค |
| 2 | ์ซ๋์ซ๋ ๋ฌ๊ณ ๋ |
| 3 | ์ซ๋ํ ์ด์ฝ์นฉ ์ฟ ํค |
| ํ ํฐ | ๋ฌธ์ ID ๋ชฉ๋ก |
|---|---|
| ์ซ๋ | 1, 2, 3 |
| ์ฟ ํค | 1, 3 |
์ฆ, “์ซ๋”์ ๊ฒ์ํ๋ฉด ์ ์ฒด ๋ฌธ์๋ฅผ ํ๋ ๋์ , ์ญ์์ธ์์ “์ซ๋” ํ ํฐ์ ๋ฌธ์ ํ๋ณด ๋ชฉ๋ก์ ๋จผ์ ๋น ๋ฅด๊ฒ ์ฐพ๊ณ , ๊ทธ ํ๋ณด๋ค์ ๋ํด์๋ง ์ ์ ๊ณ์ฐ(๊ด๋ จ๋)๊ณผ ์ ๋ ฌ์ ์ํํ๋ค. ์ด ๋๋ฌธ์ ๋ฐ์ดํฐ๊ฐ ์ปค์ง์๋ก ์ค์บ ๊ธฐ๋ฐ ๊ฒ์๋ณด๋ค ํจ์ฌ ์ ๋ฆฌํด์ง ์ ์๋ค.
๋ค๋ง ์ด๋ฐ ๊ตฌ์กฐ๋ฅผ ์ ์งํ๋ ค๋ฉด ์์ธ ์์ ์ ํ ํฐ ๋ถ์๊ณผ ์ญ์์ธ ๊ฐฑ์ ์์ ์ด ํ์ํ๋ฏ๋ก, ์ผ๋ฐ์ ์ผ๋ก ๊ฒ์ ์ฑ๋ฅ์ ์ป๋ ๋์ ์ฐ๊ธฐ(์์ธ) ๋น์ฉ์ด ์ถ๊ฐ๋๋ ํธ๋ ์ด๋์คํ๊ฐ ์กด์ฌํ๋ค.
์ ์ฉ ๋ฐฉ๋ฒ
Elasticsearch๋ ์ฃผ๋ก Kibana์ ํจ๊ป ์ฌ์ฉํ๋ค. Kibana๋ Elasticsearch์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์ ์นํ์ ์ธ ๋์๋ณด๋์ ๊ทธ๋ํ, ์ฐจํธ ๋ฑ์ผ๋ก ์๊ฐํํ๊ณ ํ์ํ๋ ์คํ ์์ค ์น ์ธํฐํ์ด์ค์ด๋ค.
์ฆ, Elasticsearch๊ฐ ๋ฐ์ดํฐ ์ ์ฅ ๋ฐ ๋ถ์ ์์ง์ด๋ผ๋ฉด, Kibana๋ ๊ทธ ์์ง์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๊ณ , ๋ถ์ํ๊ณ , ๊ด๋ฆฌํ๋ ๋ชจ๋ํฐ๋ง ์ญํ ์ธ ๊ฒ์ด๋ค. Kibana๊ฐ ๊ผญ ํ์ํ์ง ์์ ๊ฒฝ์ฐ, Docker ์ค์ ์์ ์ ์ธํด๋ ๋ฌด๋ฐฉํ๋ค.
Docker ์ค์
docker-compose์์ elasticsearch:build:context์๋ Dockerfile์ ๊ฒฝ๋ก๋ฅผ ์ ์ด์ฃผ๋ฉด ๋๋ค.
# /elasticsearch/Dockerfile
FROM docker.elastic.co/elasticsearch/elasticsearch:8.18.1
RUN elasticsearch-plugin install --batch analysis-nori
# docker-compose.yaml
services:
elasticsearch:
build:
context: ./elasticsearch
dockerfile: Dockerfile
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.enrollment.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
kibana:
image: docker.elastic.co/kibana/kibana:8.18.1
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch
volumes:
es_data:
๋ณดํต ๋ก์ปฌ์ IDE๋ฅผ ํตํด ์ฑ์ ์คํํ๊ณ , ๋ฐฐํฌ ํ๊ฒฝ์์๋ Docker๋ก ๋์ฐ๊ธฐ ๋๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด application.yaml์ ์ค์ ํด์ค์ผ ํ๋ค.
# local
spring:
elasticsearch:
uris: http://localhost:9200
# prod
spring:
elasticsearch:
uris: http://elasticsearch:9200
๋ง์ฝ ๋ก์ปฌ์์๋ ์คํ๋ง์ Docker์ ์ฌ๋ ค์ ์ฌ์ฉํ ๊ฒฝ์ฐ prod์ ๋์ผํ๊ฒ ๋๋ฉด ๋๋ค.
ํตํฉ yaml์ ๊ฐ์ง๊ณ , env๋ฅผ ํตํด ์ด์ํ ๊ฒฝ์ฐ env ํ์ผ์ ์ฃผ์๋ฅผ ์์ฑํ๊ณ
uris: ${ES_URL}์ฒ๋ผ ์์ฑํด์ฃผ๋ฉด ๋๋ค.
์์กด์ฑ ์ค์
Elasticsearch๋ฅผ Spring Boot์์ ์ฌ์ฉํ๋ ค๋ฉด spring-boot-starter-data-elasticsearch ์์กด์ฑ์ ์ถ๊ฐํ๋ฉด ๋๋ค.
implementation("org.springframework.boot:spring-boot-starter-data-elasticsearch")
Spring Boot๋ฅผ ์ฐ๋ ๊ฒฝ์ฐ, ๋๋ถ๋ถ์ Boot๊ฐ Spring Data Elasticsearch ๋ฒ์ ์ ํจ๊ป ๋ง์ถฐ์ฃผ๊ธฐ ๋๋ฌธ์ ์์กด์ฑ ๋ฒ์ ์ ์ง์ ๊ณ ์ ํ ์ผ์ด ๋ง์ง ์๋ค. ๋ฐ๋๋ก Boot๊ฐ ์๋ ์ผ๋ฐ Spring ํ๊ฒฝ์ด๋ผ๋ฉด, ์ฌ์ฉ ์ค์ธ Spring/Data ์กฐํฉ๊ณผ ๋ฒ์ ํธํํ๋ฅผ ๋ณด๊ณ ํธํ๋๋ ๋ฒ์ ์ ๋ง์ถฐ์ผ ํ๋ค.
RDB์ Elasticsearch ์ญํ ๋ถ๋ฆฌ
๊ตฌ์กฐ๋ฅผ ์ก์ ๋ ํต์ฌ์ ์ญํ ์ ๋ถ๋ฆฌํ๋ ๊ฒ์ด๋ค.
- RDB: ์ง์ค์ ์์ฒ(source of truth). ๋ฐ์ดํฐ ์์ฑ/์์ /์ญ์ ๊ฐ์ ์ํ ๋ณ๊ฒฝ์ด ํ์ ๋๋ ๊ณณ
- Elasticsearch(ES): ๊ฒ์ ์ ์ฉ read model. ๊ฒ์ ์ฑ๋ฅ/๊ฒ์ ํ์ง์ ์ํด ํ์ํ ํ๋๋ฅผ ๋ณ๋๋ก ์ ์ฅํด ์กฐํ์ ์ฌ์ฉ
์ฆ, ์ค์ ์ํ ๋ณ๊ฒฝ์ RDB์์ ์ฒ๋ฆฌํ๊ณ ES๋ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ผ๊ฐ๋ ๋ณต์ ๋ณธ์ผ๋ก ์ด์ํ๋ ๊ฒ์ด ์ข๋ค. ๋ฐ๋ผ์ ์ด์ ๊ด์ ์์๋ ๋๊ธฐํ ์ง์ฐ์ด๋ ์คํจ๋ฅผ ๊ณ ๋ คํด ์ฌ์๋/์ฌ์์ธ ๊ฐ์ ๋ณต๊ตฌ ๊ฒฝ๋ก๋ฅผ ์ค๋นํ๊ณ , ์ต์ข ์ ์ผ๋ก ์ผ๊ด๋๊ฒ(eventually consistent) ๋ง์ถฐ์ง๋๋ก ์ค๊ณํ๋ ๊ฒ์ด ์ค์ํ๋ค.
Document ์ ์
ES์ ์ ์ฅ๋๋ ํํ๋ RDB ํ ์ด๋ธ๊ณผ 1:1๋ก ๋ง์ถ ํ์๊ฐ ์๋ค. ๊ฒ์๊ณผ ์ ๋ ฌ์ ํ์ํ ํ๋๋ง ๋ฝ์์ Document๋ก ์ ์ํ๋ฉด ๋๋ค.
@Document(indexName = "product", createIndex = true)
data class ProductDocument(
@Id
val productId: Long,
@Field(type = FieldType.Text, analyzer = "nori")
val title: String,
val price: Int
)
์ฌ๊ธฐ์ @Field(type = FieldType.Text, analyzer = "nori")๋ ํด๋น ํ๋๋ฅผ 'ํ๊ตญ์ด ํํ์ ๋ถ์ ๊ธฐ๋ฐ์ผ๋ก ํ ํฐํํด์ ์์ธ/๊ฒ์ํ๊ฒ ๋ค'๋ ์๋ฏธ๋ค. ํ๊ตญ์ด๋ ๊ณต๋ฐฑ๋ง์ผ๋ก ๋จ์ด ๊ฒฝ๊ณ๋ฅผ ์ก๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ๊ฐ ๋ง์์, ํ๊ธ ๊ฒ์ ํ์ง์ ์ํด nori ๋ถ์๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ๊ตฌ์ฑ์ด ํํ๋ค.
nori๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด Elasticsearch์ analysis-nori ํ๋ฌ๊ทธ์ธ์ด ์ค์น๋์ด ์์ด์ผ ํ์ง๋ง, ์ฐ๋ฆฌ๋ ์ด๋ฏธ Dockerfile์์ ํ๋ฌ๊ทธ์ธ ์ค์น๋ฅผ ์ด๋ฏธ ํฌํจํ๊ธฐ ๋๋ฌธ์, ์ปจํ ์ด๋๊ฐ ๋น๋/์คํ๋ ๋ ํจ๊ป ๋ฐ์๋๋ฉฐ ๋ณ๋์ ์ถ๊ฐ ์์ ์ด ํ์ํ์ง ์๋ค.
์ฐธ๊ณ ๋ก createIndex = true๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ์ธ๋ฑ์ค ์์ฑ/๋งคํ ์ ์ฉ์ ์๋์ผ๋ก ์๋ํ๋ ์ต์ ์ด๋ค. ๋ก์ปฌ์์๋ ํธํ์ง๋ง, ์ด์์์๋ ์ธ๋ฑ์ค ๊ด๋ฆฌ ์ ์ฑ (๋ฒ์ ์ธ๋ฑ์ค, ๋ง์ด๊ทธ๋ ์ด์ , alias ์ด์ ๋ฑ)์ ๋ฐ๋ผ ๋๊ณ ๋ณ๋๋ก ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ๋ ๋ง๋ค.
Repository ์ ์
๋ง์ง๋ง์ผ๋ก ElasticsearchRepository๋ฅผ ๋ง๋ค๋ฉด JPA Repository์ฒ๋ผ ES ๋ฌธ์์ ๋ํด ์ ์ฅ/์กฐํ๊ฐ ๊ฐ๋ฅํด์ง๋ค.
interface ProductEsRepository : ElasticsearchRepository<ProductDocument, Long>
์ค์ ์ฌ์ฉ
ES๋ฅผ ์ฌ์ฉํ ๋ ๊ฐ์ฅ ์ฃผ์ํด์ผํ ์ ์ RDB์ ES๋ฅผ ๋๊ธฐํํ๋ ๊ฒ์ด๋ค. ์ฆ, ES์ Document๋ก ์ถ๊ฐํ RDB ํ ์ด๋ธ์ Create, Update, Delete๊ฐ ์ผ์ด๋ ๊ฒฝ์ฐ, ES์๋ ํจ๊ป ์ ์ฉ์ ํด์ฃผ์ด์ผ ํ๋ค.
fun save(dto: ProductDto) {
val product = dto.toEntity()
productRdbRepository.save(product)
productEsRepository.save(product.toDocument())
}
ElasticsearchRepository์ ๋๋ถ๋ถ ํจ์๊ฐ JPA์ ์ ์ฌํ๊ธฐ ๋๋ฌธ์ ํฐ ์ด๋ ค์์ ์๊ฒ ์ง๋ง, ํ์ํ ํจ์๋ฅผ ์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ ๊ณต์ ๋ฌธ์๋ฅผ ํตํด ๋ณด๋ฉด ๋์์ด ๋ ๊ฒ์ด๋ค.
'Spring > ๊ธฐ๋ฅ ์ ๋ฆฌ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [JUnit5] - @Nested ํ ์คํธ ์์ ์ง์ (3) | 2023.10.08 |
|---|---|
| [Spring] - Jsoup์ ์ด์ฉํ ํฌ๋กค๋ง (2) | 2023.09.12 |
| [Spring] - SpringBoot 3.x ๋ฒ์ ์์ Swagger ์ฌ์ฉํ๊ธฐ (1) | 2023.09.10 |
| [Spring] - Naver Api๋ก ์ํ ๊ฒ์ ๋ชฉ๋ก ๋ฐ์์ค๊ธฐ (0) | 2023.09.10 |
| [Spring] - record DTO (0) | 2023.09.10 |