๐ 1. Elasticsearch๋?
Elasticsearch๋ ๋ถ์ฐ ๊ฒ์ ๋ฐ ๋ถ์ ์์ง์ผ๋ก, ๋๊ท๋ชจ ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ๊ฒ์ํ๊ณ ๋ถ์ํ๋ ๋ฐ ์ต์ ํ๋ ์์คํ !!
๊ฒ์, ๋ก๊ทธ ์์ง, ๋ฐ์ดํฐ ๋ถ์ ๋ฑ์ ์ฉ๋๋ก ์ฌ์ฉ๋๋ค.
โ 1-1. Elasticsearch์ ์ฃผ์ ๊ฐ๋
- ์ธ๋ฑ์ค(Index)
๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ๊ธฐ ์ํด ๊ตฌ์ฑ๋ ๊ธฐ๋ณธ์ ์ธ ์ ์ฅ ๋จ์. ๊ฐ ์ธ๋ฑ์ค๋ ๋ฌธ์(document)์ ์งํฉ์ผ๋ก ๊ตฌ์ฑ, ์ด ๋ฌธ์๋ค์ JSON ํ์์ผ๋ก ์ ์ฅ๋๋ค. - ์ค๋(Shard)์ ๋ ํ๋ฆฌ์นด(Replica)
Elasticsearch๋ ๋ฐ์ดํฐ๋ฅผ ์ค๋(shard)๋ก ๋๋์ด ์ฌ๋ฌ ์๋ฒ์ ๋ถ์ฐ ์ ์ฅ, ์ด๋ฅผ ๋ ํ๋ฆฌ์นด(replica)๋ก ๋ณต์ ํ์ฌ ๊ณ ๊ฐ์ฉ์ฑ์ ๋ณด์ฅํ๋ค. - ๊ฒ์ ๋ฐ ๋ถ์
์ฌ์ฉ์๋ ๊ฒ์ ์ฟผ๋ฆฌ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ๊ณ , ๋ถ์ํ ์ ์๋ค. ์ ์ฒด ํ ์คํธ ๊ฒ์ ๋ฐ ๊ณ ๊ธ ํํฐ๋ง ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. - Mapping๊ณผ Analyzer
Mapping์ ํตํด ๋ฌธ์์ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๊ณ , Analyzer๋ฅผ ํตํด ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๋ถ์ํ ์ง ๊ฒฐ์ ํ๋ค. ์ด๋ ํ ์คํธ ํ๋๊ฐ ์ธ๋ฑ์ฑ๋ ๋ ํ ํฐํ์ ๋ถ์ฉ์ด ์ ๊ฑฐ ๊ฐ์ ์ฒ๋ฆฌ๊ฐ ์ด๋ฃจ์ด์ง๊ณ , ์์ถ์ด ์ ์ฉ๋๊ธฐ๋ ํ๋ค.
โ 1-2. Elasticsearch์ ๊ฒ์ ์ฑ๋ฅ ์ต์ ํํ๋ ๋ฐฉ๋ฒ
Elasticsearch๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ๊ฒ์ํ๊ณ ๋ถ์ํ๊ธฐ ์ํด ๋ค์ํ ์ต์ ํ ๊ธฐ๋ฒ์ ์ฌ์ฉํ๋๋ฐ, ์ฃผ์ ๊ธฐ๋ฒ์ ์๋์ ๊ฐ๋ค.
๋ชฐ๋ผ๋ ์ฌ์ฉํ๋๋ฐ ์ง์ฅ์ ์๋ค!! ์ฝ๊ณ ๋์ด๊ฐ์.๐
1๏ธโฃ ์ค๋ฉ ๋ฐ ๋ถ์ฐ ์ฒ๋ฆฌ ์ต์ ํ
- ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ๋ ธ๋์ ๋ถ์ฐ ์ ์ฅํ๊ณ ์ค๋/๋ ํ๋ฆฌ์นด๋ฅผ ํ์ฉํด ๋ถํ ๋ถ์ฐ ๋ฐ ๊ณ ๊ฐ์ฉ์ฑ ํ๋ณด.
- ์๋ ์ค๋ ๋ฆฌ๋ฐธ๋ฐ์ฑ๊ณผ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ํตํด ํด๋ฌ์คํฐ ์ฑ๋ฅ ์ ์ง.
2๏ธโฃ ์ธ๋ฑ์ฑ ํจ์จํ
- ํ์ํ ํ๋๋ง ์ธ๋ฑ์ฑ(์๋ธ์ ์ธ๋ฑ์ฑ)ํ๊ฑฐ๋ ๋ค์ค ์ธ๋ฑ์ค๋ฅผ ํ์ฉํด ์ ์ฅ ๊ณต๊ฐ๊ณผ ๊ฒ์ ์๋ ์ต์ ํ.
- ๋๋ ๋ฐ์ดํฐ๋ ๋ฐฐ์น ์ธ๋ฑ์ฑ์ผ๋ก ์ฒ๋ฆฌํด ๋ฆฌ์์ค ์ ์ฝ.
3๏ธโฃ ๋ฐ์ดํฐ ์์ถ ๋ฐ ์ ์ฅ ์ต์ ํ
- LZ4 ๋ฑ์ ์์ถ ์๊ณ ๋ฆฌ์ฆ์ ํ์ฉํด ์ ์ฅ ๊ณต๊ฐ ์ ์ฝ ๋ฐ ๋น ๋ฅธ ๊ฒ์ ์ง์.
4๏ธโฃ ๊ฒ์ ์ฟผ๋ฆฌ ๋ฐ ๊ตฌ์กฐ ์ต์ ํ
- ๋ถ์ฉ์ด ์ ๊ฑฐ, ์ด๊ทผ ๋ถ์ ๋ฑ ํ๋ ์ด์ ๊ณผ ํ ํฐํ ์ ์ฉ.
- Bool Query ๋ฑ์ผ๋ก ๋ณตํฉ ์ฟผ๋ฆฌ ๊ตฌ์กฐ ์ต์ ํ.
- ํํฐ/์ฟผ๋ฆฌ ๋ถ๋ฆฌ ๋ฐ ์บ์ฑ ํ์ฉ์ผ๋ก ์๋ต ์๊ฐ ๋จ์ถ.
5๏ธโฃ ์ธ๋ฑ์ค ๊ตฌ์กฐ ๋ฐ ๋ฆฌํ๋ ์ ์ ์ด
- ์ญ ์ธ๋ฑ์ค๋ฅผ ํ์ฉํด ํ ์คํธ ๊ฒ์ ์ต์ ํ.
- ๋ฆฌํ๋ ์ ๊ฐ๊ฒฉ ์กฐ์ ๋ก ์ฑ๋ฅ๊ณผ ๋ฐ์ดํฐ ๋ฐ์ ์๋ ๊ท ํ ์กฐ์ .
โญ์ฐธ๊ณ ์๋ฃ
https://goodbyeanma.tistory.com/160
๐ 2. ๋ฐ์ดํฐ ์ฝ์
โ 2-1. ์ธ๋ฑ์ค ์์ด ๋ฐ์ดํฐ ์ฝ์
์์ : products๋ผ๋ ์ธ๋ฑ์ค ์์ด ๋ฐ์ดํฐ ์ฝ์
PUT http://localhost:9200/products/_doc/1 { "name": "๋ ธํธ๋ถ", "price": 1500000, "category": "์ ์์ ํ" } |
โก๏ธ products๋ผ๋ ์ธ๋ฑ์ค๊ฐ ์๋ ์์ฑ๋จ
โก๏ธ _doc/1 → ๋ฌธ์ ID 1๋ก ์ ์ฅ๋จ
โ 2-2. ๋ช ์์ ์ผ๋ก ์ธ๋ฑ์ค ์ ์
์ค๋ฌด์์๋ ๋ช
์์ ์ผ๋ก ์ธ๋ฑ์ค ์ค๊ณ๋ฅผ ๋จผ์ ํจ!
ElasticSearch๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๋ "๋งคํ(Mapping)"์ ๋ฏธ๋ฆฌ ์ค์ ํ๋ ๊ฒ ์ข์.
์ ๊ทธ๋ฌ๋ฉด ํ๋ ํ์
์ด ์๋์ผ๋ก ์ง์ ๋๋๋ฐ, ๋์ค์ ๋ฐ๊พธ๊ธฐ ์ด๋ ค์์!
โ ๋ช ์์ ์ผ๋ก ์ธ๋ฑ์ค๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ
PUT http://localhost:9200/products { "mappings": { "properties": { "name": { "type": "text" }, "price": { "type": "integer" }, "category": { "type": "keyword" } } } } |
โก๏ธ name, price, category ํ๋๊ฐ ์ ์๋ products ์ธ๋ฑ์ค๊ฐ ๋ง๋ค์ด์ง!
๐3. Spring Boot Elasticsearch ์ฌ์ฉ๋ฒ
โ ์์ฝ
1๏ธโฃ Elasticsearch ์๋ฒ ์คํ → Gradle์ ์์กด์ฑ ์ถ๊ฐ.
2๏ธโฃ Spring Boot์์ ์ค์ ์ถ๊ฐ (application.yml ๋๋ application.properties).
3๏ธโฃ Elasticsearch Repository์ ์ฐ๋ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌ.
4๏ธโฃ ํ์ํ ๊ฒฝ์ฐ Elasticsearch RestTemplate์ ์ฌ์ฉํ์ฌ ์ปค์คํ ์ฟผ๋ฆฌ ์์ฑ.
โ 3-1. Elasticsearch ์ค์น ๋ฐ ์คํ
๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ์ค์น ๋ฐ ์คํํ ์ ์๋ค.
๐น Docker๋ก Elasticsearch ์คํ
docker run -d --name elasticsearch \
-p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
docker.elastic.co/elasticsearch/elasticsearch:8.11.2
๐น Elasticsearch ๋ค์ด๋ก๋ ํ ์คํ
- Elasticsearch ํํ์ด์ง์์ ์ค์น ํ์ผ ๋ค์ด๋ก๋.
- ์์ถ์ ํด์ ํ๊ณ bin/elasticsearch๋ฅผ ์คํ. (window๋ .bat) (๊ด๋ฆฌ์ ๊ถํ์ผ๋ก)
โ 3-2. ์์กด์ฑ ์ถ๊ฐ
Elasticsearch์ ํต์ ํ๊ธฐ ์ํด Spring Data Elasticsearch gradle ์์กด์ฑ์ ์ถ๊ฐํ๋ค.
dependencies {
//spring-boot-starter-data-elasticsearch (์ถ์ฒ)
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
// Spring Data Elasticsearch (์ถ์ฒx)
implementation 'org.springframework.data:spring-data-elasticsearch'
// Elasticsearch-java (๊ณ ๊ธ ์ค์ ํ์์)
implementation 'co.elastic.clients:elasticsearch-java'
}
๐ก์ด๋ค ๊ฑธ ์จ์ผ ํ ๊น?
โ
spring-data-elasticsearch
→ Spring ์ ํ๋ฆฌ์ผ์ด์
์์ ๊ธฐ๋ณธ์ ์ธ ๊ฒ์ ๋ฐ CRUD (@Document + ElasticsearchRepository, ElasticsearchTemplate )
โ Spring-boot-starter-data-elasticsearch
→ Spring Boot ํ๋ก์ ํธ์์ Elasticsearch๋ฅผ ์ฝ๊ฒ ์ฐ๋(์๋ ์ค์ ์ง์)
→ spring-data-elasticsearch ํฌํจ + Elasticsearch RestClient + ์๋ ์ค์ ์ง์
โ Elasticsearch-java
→ ๊ธฐ๋ฅ์ ์ธ๋ฐํ๊ฒ ์ ์ด (๊ณ ๊ธ ์ฟผ๋ฆฌ ๊ฐ๋ฅ)
→ Elasticsearch ์๋ฒ์์ HTTP ์์ฒญ์ ํตํด ์ง์ ํต์ (์ ์์ค ํด๋ผ์ด์ธํธ)
→ spring-data-elasticsearch์ ์๋ก ๋ ๋ฆฝ์ ์ด๋ฏ๋ก, ํจ๊ป ์ฌ์ฉํ ์๋ ์๋ค.
Spring Boot์์ ๋๋ถ๋ถ spring-boot-starter-data-elasticsearch๋ง ํ์! ๐
โ 3-3. Spring Boot ์ค์
application.yml ๋๋ application.properties์ Elasticsearch ์๋ฒ ์ ๋ณด๋ฅผ ์ถ๊ฐ.
spring.elasticsearch.rest.uris=http://localhost:9200 spring.elasticsearch.username=elastic spring.elasticsearch.password=changeme # ๋น๋ฐ๋ฒํธ ์ด๊ธฐํ: bin/elasticsearch-reset-password -u elastic ์ฝ์์ ์๋ก์ด ๋น๋ฐ๋ฒํธ๊ฐ ์ถ๋ ฅ๋๋ค. |
โ 3-4. Elasticsearch Repository ์์ฑ
Elasticsearch ๋ฐ์ดํฐ๋ฅผ CRUD ๋ฐฉ์์ผ๋ก ๋ค๋ฃฐ ์ ์๋๋ก Spring Data Elasticsearch Repository๋ฅผ ์ฌ์ฉํฉ๋๋ค.
โ 3-4-1. ์ํฐํฐ ํด๋์ค ์ ์
Elasticsearch์ ์ ์ฅํ ๋ฐ์ดํฐ๋ฅผ ์ ์ํ๋ค.
์ฐธ๊ณ ๋ก JPA(@Entity) + Elasticsearch(@Document)๋ ๊ฐ์ด ์ฌ์ฉ ๊ฐ๋ฅ.
๊ทธ๋ฌ๋ RDB์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ElasticSearch์ ์ ์ฅํ ํ์ ์์ด์ ๋ฐ๋ก ๊ด๋ฆฌํ๋ค!! (๊ฒ์ ํ๋๋ง ์ ์ฅํ๋ฉด ๋จ!!)
import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; @Document(indexName = "products") //์ธ๋ฑ์ค ์ด๋ฆ ์ง์ public class Product { @Id private String id; private String name; private double price; // Getters and Setters } |
โ 3-4-1. Repository
1๏ธโฃ๊ธฐ๋ณธ ์ฝ๋
Spring Data Elasticsearch์์ ElasticsearchRepository๋ฅผ ์์ํ๋ฉด ๊ธฐ๋ณธ์ ์ธ CRUD ์ ๊ณต!
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; @Repository public interface ProductSearchRepository extends ElasticsearchRepository<ProductSearch, Long> { } |
โ๏ธ์๋น์ค์์ ProductSearchRepository ํจ์ ํธ์ถ
- save(ProductSearch entity): ES์ ๋ฐ์ดํฐ ์ ์ฅ
- findById(Long id): ํน์ ID๋ก ์กฐํ
- deleteById(Long id): ๋ฐ์ดํฐ ์ญ์
2๏ธโฃ ์ปค์คํ
๊ฒ์ ๋ฉ์๋ ์ถ๊ฐ (๊ฒ์ ์กฐ๊ฑด ์ ์ฉ)
๊ฒ์ ์กฐ๊ฑด์ ์ถ๊ฐํ๊ณ ์ถ๋ค๋ฉด ๋ฉ์๋๋ฅผ ์ง์ ์ ์!
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface ProductSearchRepository extends ElasticsearchRepository<ProductSearch, Long> { // โ ์ด๋ฆ์ผ๋ก ๊ฒ์ (๋์๋ฌธ์ ๊ตฌ๋ถ ์์ด ๊ฒ์) List<ProductSearch> findByNameContainingIgnoreCase(String name); // โ ์นดํ ๊ณ ๋ฆฌ๋ก ๊ฒ์ (์์ ์ผ์น) List<ProductSearch> findByCategory(String category); // โ ์ด๋ฆ + ์ค๋ช ์์ ๊ฒ์ (OR ์กฐ๊ฑด) List<ProductSearch> findByNameContainingOrDescriptionContaining(String name, String description); } |
โ๏ธ findByNameContainingIgnoreCase("๋
ธํธ๋ถ") ๊ฐ์ ๋ฐฉ์์ผ๋ก ๊ฒ์ ๊ฐ๋ฅ!
โ๏ธ JPA์ฒ๋ผ findBy~ ํํ๋ก ๊ฒ์ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ค ์ ์์!
3๏ธโฃ ์ง์ ๊ฒ์ ์ฟผ๋ฆฌ ์คํ (Native Query ์ฌ์ฉ)
๋ณต์กํ ๊ฒ์ ์กฐ๊ฑด์ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด, @Query๋ฅผ ํ์ฉ!
import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface ProductSearchRepository extends ElasticsearchRepository<ProductSearch, Long> { // โ Multi-field ๊ฒ์ (name + description์์ ๊ฒ์) @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"name\", \"description\"]}}") List<ProductSearch> searchByNameOrDescription(String keyword); } |
โ๏ธ ์ด์ searchByNameOrDescription("๊ฒ์ด๋ฐ")์ ํธ์ถํ๋ฉด, ์ด๋ฆ(name) ๋๋ ์ค๋ช (description)์ "๊ฒ์ด๋ฐ"์ด ํฌํจ๋ ์ ํ์ ๊ฒ์ํ ์ ์์!
4๏ธโฃ QueryBuilder
โ๏ธ @Query๋ก ํํํ๊ธฐ ์ด๋ ค์ด ๋ณต์กํ ๊ฒ์ ์กฐ๊ฑด์ ์ ์ฉํ ๋
โ๏ธ ๊ฒ์ ์กฐ๊ฑด์ ๋์ ์ผ๋ก ๋ง๋ค๊ณ ์ถ์ ๋
โ๏ธ Elasticsearch์ matchQuery, boolQuery, multi_matchQuery, rangeQuery ๋ฑ์ ์ง์ ์ด์ฉํ๊ณ ์ถ์ ๋
import org.elasticsearch.index.query.QueryBuilders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.stereotype.Service; @Service public class CustomSearchService { @Autowired private ElasticsearchRestTemplate elasticsearchTemplate; public SearchHits<Product> searchByCustomQuery(String keyword) { return elasticsearchTemplate.search( org.springframework.data.elasticsearch.core.query.NativeSearchQuery.builder() .withQuery(QueryBuilders.matchQuery("description", keyword)) .build(), Product.class ); } } |
โ 3-5. Service์ Controller ์์ฑ
๐ ๋ฐ์ดํฐ ์ถ๊ฐ ์๋น์ค
โญ RDB์ ElasticSearch ๋๊ธฐํ ๋ฐฉ๋ฒ
1๏ธโฃ ์๋น์ค ๋ก์ง์์ ์ง์ ๋๊ธฐํ -> ์์ ๋ ์ด๊ฑฐ!!
๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ RDB์ ES์ ๋ชจ๋ ์ ์ฅ
2๏ธโฃ ๋น๋๊ธฐ ๋๊ธฐํ (Kafka ํ์ฉ)
RDB ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๊ฐ์ง → ElasticSearch์ ๋ฐ์
@Service public class ProductService { @Autowired private ProductRepository productRepository; // JPA ์ ์ฅ์ @Autowired private ProductSearchRepository productSearchRepository; // ES ์ ์ฅ์ @Transactional public void saveProduct(Products product) { productRepository.save(product); // RDB ์ ์ฅ // ๊ฒ์์ฉ ์ํฐํฐ๋ก ๋ณํ ํ ES ์ ์ฅ ProductSearch productSearch = new ProductSearch(); productSearch.setProductId(product.getProductId()); productSearch.setName(product.getName()); productSearch.setDescription(product.getDescription()); productSearch.setCategory(product.getCategory()); productSearchRepository.save(productSearch); // ES ์ ์ฅ } } |
๐ ๋ฐ์ดํฐ ์กฐํ ์๋น์ค(๊ฒ์)
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class ProductSearchService { @Autowired private ProductSearchRepository productSearchRepository; // โ ์ด๋ฆ์ผ๋ก ๊ฒ์ public List<ProductSearch> searchByName(String name) { return productSearchRepository.findByNameContainingIgnoreCase(name); } // โ ์นดํ ๊ณ ๋ฆฌ๋ก ๊ฒ์ public List<ProductSearch> searchByCategory(String category) { return productSearchRepository.findByCategory(category); } // โ ๋ณตํฉ ๊ฒ์ (์ด๋ฆ + ์ค๋ช ) public List<ProductSearch> searchByKeyword(String keyword) { return productSearchRepository.searchByNameOrDescription(keyword); } } |
๐ ์ปจํธ๋กค๋ฌ
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/search")
public class ProductSearchController {
@Autowired
private ProductSearchService productSearchService;
Private ProductService productService;
// โ
์ํ ์ถ๊ฐ API
// โ
๊ฒ์ API (ex: /search/name?query=๋
ธํธ๋ถ)
@GetMapping("/name")
public List<ProductSearch> searchByName(@RequestParam String query) {
return productSearchService.searchByName(query);
}
// โ
๊ฒ์ API (ex: /search/category?query=์ ์์ ํ)
@GetMapping("/category")
public List<ProductSearch> searchByCategory(@RequestParam String query) {
return productSearchService.searchByCategory(query);
}
// โ
๋ณตํฉ ๊ฒ์ API (ex: /search/keyword?query=๊ฒ์ด๋ฐ)
@GetMapping("/keyword")
public List<ProductSearch> searchByKeyword(@RequestParam String query) {
return productSearchService.searchByKeyword(query);
}
}
โ 3-6. Elasticsearch ์์ ๋ฐ ํ ์คํธ
โ ์ด์ API๋ฅผ ํธ์ถํ๋ฉด ๊ฒ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์!
- Elasticsearch ์๋ฒ๊ฐ ์คํ ์ค์ธ์ง ํ์ธ(http://localhost:9200์ ์ ์).
- Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํ.
- Postman, cURL, ๋๋ ๋ธ๋ผ์ฐ์ ์์ API๋ฅผ ํ ์คํธ.
ProductService์์ ์ฌ์ฉ ์์
GET http://localhost:8080/search/name?query=๋
ธํธ๋ถ
GET http://localhost:8080/search/category?query=์ ์์ ํ
GET http://localhost:8080/search/keyword?query=๊ฒ์ด๋ฐ
๐จ 4.Error
๐จhttp ์ ์ ์ค๋ฅ
ProductServiceApplicationTests > contextLoads() FAILED java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180 Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:804 Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:804 Caused by: org.springframework.beans.factory.BeanCreationException at AbstractAutowireCapableBeanFactory.java:1808 Caused by: org.springframework.beans.BeanInstantiationException at BeanUtils.java:222 Caused by: org.springframework.dao.DataAccessResourceFailureException at ElasticsearchExceptionTranslator.java:107 Caused by: java.lang.RuntimeException at ElasticsearchExceptionTranslator.java:62 Caused by: javax.net.ssl.SSLHandshakeException at RestClient.java:929 Caused by: javax.net.ssl.SSLHandshakeException at Alert.java:131 Caused by: sun.security.validator.ValidatorException at PKIXValidator.java:439 sun.security.provider.certpath.SunCertPathBuilderException at SunCertPathBuilder.java:148 1 test completed, 1 failed
๐ก ์์ธ ๋ถ์
- javax.net.ssl.SSLHandshakeException: SSL ํธ๋์ ฐ์ดํฌ ์ค์ ๋ฌธ์ ๊ฐ ๋ฐ์.
- sun.security.validator.ValidatorException: PKIXValidator์ SunCertPathBuilderException: ์ธ์ฆ์์ ์ ํจ์ฑ์ ๊ฒ์ฆํ๋ ๊ณผ์ ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์์ ์๋ฏธ.
๐ ํด๊ฒฐ ๋ฐฉ๋ฒ
Elasticsearch ์๋ฒ์์ ์ฌ์ฉํ๋ SSL ์ธ์ฆ์๋ฅผ ํด๋ผ์ด์ธํธ JVM์ ์ ๋ขฐํ ์ ์๋ ์ ์ฅ์(truststore)์ ์ถ๊ฐ.
1๏ธโฃ Elasticsearch ์ธ์ฆ์ ๋ค์ด๋ก๋
Elasticsearch ์๋ฒ๊ฐ ์ฌ์ฉํ๋ SSL ์ธ์ฆ์๋ฅผ ๋ธ๋ผ์ฐ์ ์์ ๋ค์ด๋ก๋ํ๊ฑฐ๋, openssl ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ ์ธ์ฆ์๋ฅผ ๋ค์ด๋ก๋ํ ์ ์๋ค.
๐ openssl ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉ
openssl s_client -connect localhost:9200 -showcerts
๐ ๋ธ๋ผ์ฐ์ ์์ ๋ค์ด๋ก๋
2๏ธโฃJava Truststore์ ์ธ์ฆ์ ์ถ๊ฐ
์ธ์ฆ์๋ฅผ ๋ค์ด๋ก๋ํ ํ, keytool์ ์ฌ์ฉํ์ฌ Java truststore์ ์ถ๊ฐํ๋ค.
keytool -importcert -file elasticsearch.crt -keystore cacerts -alias elasticsearch
C:\Windows\System32>keytool -importcert -file C:\{my-service}\src\main\resources\WIN-325LDHGUD53.crt -keystore "C:\Program Files\Java\jdk-17\lib\security\cacerts" -alias elasticsearch
ํค ์ ์ฅ์ ๋น๋ฐ๋ฒํธ ์
๋ ฅ:
// …
์ด ์ธ์ฆ์๋ฅผ ์ ๋ขฐํฉ๋๊น? [์๋์ค]: ์
์ธ์ฆ์๊ฐ ํค ์ ์ฅ์์ ์ถ๊ฐ๋์์ต๋๋ค.
3๏ธโฃSpring Boot์์ Truststore ์ฌ์ฉ ์ค์
Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ด ์ธ์ฆ์๋ฅผ ์ ๋ขฐํ๋ ค๋ฉด, application.properties ๋๋ application.yml ํ์ผ์์ truststore ๊ฒฝ๋ก๋ฅผ ์ค์ ํด์ผ ํ๋ค.
xpack.security.http.ssl.truststore.path: classpath:WIN-325LDHGUD53.crt
xpack.security.http.ssl.truststore.password: password
4๏ธโฃElasticsearch์ ๋ํ HTTPS ๋ฐ ์ธ์ฆ์ ์ค์ ์ ๊ฒ
Elasticsearch์ elasticsearch.yml ํ์ผ์์ SSL ๊ด๋ จ ์ค์ ์ ์ ๊ฒ.
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: classpath:keystore.p12
xpack.security.http.ssl.keystore.password: your_keystore_password
# https ์์ฒญ
xpack.security.enabled: true
xpack.security.http.ssl.enabled: true
# Keystore for server-side SSL/TLS
xpack.security.http.ssl.keystore.path: classpath:keystore.p12
xpack.security.http.ssl.keystore.password: sumin
# Truststore for client authentication (if needed)
xpack.security.http.ssl.truststore.path: classpath:WIN-325LDHGUD53.crt
xpack.security.http.ssl.truststore.password: password
5๏ธโฃ ๊ฒฐ๊ณผ
๐จ Could not rename log file 'logs/gc.log' to 'logs/gc.log.08' (Permission denied).
logs/gc.log ๋๋ ๋ค๋ฅธ ๋ก๊ทธ ํ์ผ์ ๋ํ ์ฐ๊ธฐ ๊ถํ์ด ์์ด์ ๋ฐ์.
๊ด๋ฆฌ์ ๊ถํ ์คํ
๐ 5. ์คํค๋ง ํ์ธ ๋ฐฉ๋ฒ
Postman์์ ํ์ธ
Get https://localhost:9200/_cat/indices?v
Authorization
- ์์ฒญ์ Authorization ํญ์ ์ ํ.
- Type์ Basic Auth๋ก ์ค์ .
- username๊ณผ password ํ๋์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ๋ ฅ.
โญ์ธ์ฆ ๋ฐฉ๋ฒ
โ ์ธ์ฆ์ ์์ ๋นํ์ฑํํ๋ ค๋ฉด? (๊ฐ๋ฐ ํ๊ฒฝ์์๋ง)
elasticsearch.yml ์ค์ ๋ณ๊ฒฝ (๋ณด์ ๋นํ์ฑํ)
xpack.security.enabled: false
โ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ชจ๋ฅธ๋ค๋ฉด? (์ฌ์ค์ ๋ฐฉ๋ฒ)
elasticsearch-reset-password ๋ช ๋ น์ด๋ก ์ฌ์ค์
bin/elasticsearch-reset-password -u elastic
โ๏ธ ์คํํ๋ฉด ์๋ก์ด ๋น๋ฐ๋ฒํธ๊ฐ ์์ฑ๋จ
โ๏ธ Elasticsearch๋ฅผ ์ฌ์์ํด์ผ ์ ์ฉ๋จ!
systemctl restart elasticsearch # Linux
.\bin\elasticsearch.bat # Windows (CMD์์ ์คํ)
๐ elasticSearch ์ธ๋ฑ์ค ๊ด๋ฆฌ
์์ | ๋ฐฉ๋ฒ | ์ค๋ช |
ํ์ฌ ์ธ๋ฑ์ค ํ์ธ | GET _cat/indices?v | ์กด์ฌํ๋ ๋ชจ๋ ์ธ๋ฑ์ค ์กฐํ |
์ธ๋ฑ์ค ๊ตฌ์กฐ ํ์ธ | GET my_index/_mapping | ํน์ ์ธ๋ฑ์ค์ ํ๋ ๊ตฌ์กฐ ํ์ธ |
๋ฐ์ดํฐ ์กฐํ | GET my_index/_search?pretty | ์ ์ฅ๋ ๋ฐ์ดํฐ ์ผ๋ถ ํ์ธ |
์ธ๋ฑ์ค ์ญ์ | DELETE my_index | ํน์ ์ธ๋ฑ์ค ์ด๊ธฐํ |
์๋ก์ด ์ธ๋ฑ์ค ์์ฑ | PUT my_index | ์๋ก์ด ์ธ๋ฑ์ค ๋ง๋ค๊ธฐ |
Spring Boot์์ ์๋ ๊ด๋ฆฌ | @Document(indexName = "products") | ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ์๋ ์์ฑ |
๐ 6. RDB๋ฅผ ElasticSearch๋ก ๋ง์ด๊ทธ๋ ์ด์
RDB๋ฅผ ์ด์ฉํ๊ณ ์๋ ๋์ค์ elasitcSearch๋ฅผ ๋์ ํ ๊ฒฝ์ฐ, ๋ฐ์ดํฐ๋ฅผ ๋ณํ ํ ์์ธ(Indexing)ํ๋ ๊ณผ์ ์ด ํ์!
โ 6-1. ๋ง์ด๊ทธ๋ ์ด์ ๋ฐฉ์
1๏ธโฃ Batch ๋ฐฉ์ (๋ฐฐ์น ๋๊ธฐํ, ์ผ๊ด ์ ํ) → ์ฒ์ ๋ฐ์ดํฐ ์ฎ๊ธธ ๋ ์ถ์ฒ
- ์ผ์ ์ฃผ๊ธฐ๋ง๋ค RDB ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ ES๋ก ์ ์ก
- ETL ๋๊ตฌ ๋๋ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉ
- ์ ํฉํ ๊ฒฝ์ฐ: ์ด๊ธฐ ๋ง์ด๊ทธ๋ ์ด์
, ๊ฒ์ ๋ฐ์ดํฐ๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋์ง ์๋ ๊ฒฝ์ฐ
2๏ธโฃ CDC ๋ฐฉ์ (Change Data Capture) → ์ค์๊ฐ ๋๊ธฐํ
- RDB์ ๋ณ๊ฒฝ์ฌํญ(INSERT, UPDATE, DELETE)์ ๊ฐ์งํ์ฌ ES๋ก ๋๊ธฐํ
Debezium, Kafka Connect ๋ฑ์ ํ์ฉ - ์ ํฉํ ๊ฒฝ์ฐ: ์ค์๊ฐ ๊ฒ์ ๋ฐ์ดํฐ ๋๊ธฐํ๊ฐ ํ์ํ ๊ฒฝ์ฐ
3๏ธโฃ ์ ํ๋ฆฌ์ผ์ด์ ์ง์ ์ฐ๋ → ์ฝ๋์์ ์ง์ ๋๊ธฐํ
- Spring Boot ๋ฑ์์ DB ์ ์ฅ ์ ES์๋ ๋์์ ์ ์ฅ
- ์๋น์ค ๋ก์ง์ ์ง์ ๋ฐ์
- ์ ํฉํ ๊ฒฝ์ฐ: ๋๊ธฐํ ๋ก์ง์ ์ง์ ๊ด๋ฆฌํ ์ ์๋ ๊ฒฝ์ฐ
โ 6-2. ๋ฐฉ๋ฒ๋ณ ์์ธ ์ค๋ช & ๊ตฌํ ์์
1๏ธโฃ Batch ๋ฐฉ์ (๋ฐฐ์น ์คํฌ๋ฆฝํธ ์ด์ฉ) → Python, Logstash ํ์ฉ
- ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๋ง์ด๊ทธ๋ ์ด์ ํ ๋ ์ฌ์ฉ
- Python, Logstash, JDBC ๋ฑ์ ํ์ฉ
- ์์ : Python + Elasticsearch Bulk API
import mysql.connector from elasticsearch import Elasticsearch, helpers # MySQL ์ฐ๊ฒฐ db = mysql.connector.connect( host="localhost", user="root", password="password", database="ecommerce" ) cursor = db.cursor(dictionary=True) # Elasticsearch ์ฐ๊ฒฐ es = Elasticsearch(["http://localhost:9200"]) # MySQL์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ cursor.execute("SELECT id, name, description FROM products") products = cursor.fetchall() # Elasticsearch๋ก ๋ฐ์ดํฐ ๋ฃ๊ธฐ actions = [ { "_index": "products", "_id": product["id"], "_source": product } for product in products ] helpers.bulk(es, actions) print("๋ฐ์ดํฐ ๋ง์ด๊ทธ๋ ์ด์ ์๋ฃ!") |
2๏ธโฃ CDC ๋ฐฉ์ (Debezium + Kafka ํ์ฉ)
RDB ํ ์ด๋ธ ๋ณ๊ฒฝ์ ์๋์ผ๋ก ๊ฐ์งํ์ฌ ElasticSearch์ ๋ฐ์
๐น ๊ตฌ์ฑ ์์
- Debezium → RDB ๋ณ๊ฒฝ ๊ฐ์ง
- Kafka Connect → ๋ณ๊ฒฝ ๋ฐ์ดํฐ ์ ๋ฌ
- Elasticsearch Sink Connector → ES์ ์ ์ฅ
๐น์์
MySQL ( MySQL binlog ์ค์ )
→ Kafka ( Kafka + Debezium + ElasticSearch ์ฐ๋ )
→ ElasticSearch ( ๋ณ๊ฒฝ ๊ฐ์ง ํ ์๋ ๋ฐ์ )
3๏ธโฃ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ง์ ์ฐ๋ (Spring Boot)
- ์๋น์ค ์ฝ๋์์ DB ์ ์ฅ๊ณผ ๋์์ ES์๋ ์ ์ฅ
- ์์ : Spring Boot + Spring Data Elasticsearch
@Service public class ProductService { @Autowired private ProductRepository productRepository; // JPA์ฉ @Autowired private ElasticsearchProductRepository esRepository; // ElasticSearch์ฉ @Transactional public void saveProduct(Product product) { productRepository.save(product); // RDB ์ ์ฅ esRepository.save(product); // ElasticSearch ์ ์ฅ } } |
๐ฅ6-3. ์ด๋ค ๋ฐฉ์์ด ๊ฐ์ฅ ์ข์๊น?
๋ฐฉ์ | ์ฅ์ | ๋จ์ | ์ ํฉํ ๊ฒฝ์ฐ |
Batch ๋ฐฉ์ | ์ด๊ธฐ ๋ง์ด๊ทธ๋ ์ด์ ๊ฐ๋จ, ๋น ๋ฆ | ์ค์๊ฐ ๋ฐ์ ์ด๋ ค์ | ์ด๊ธฐ ๋ง์ด๊ทธ๋ ์ด์ |
CDC ๋ฐฉ์ | ์ค์๊ฐ ๋๊ธฐํ, ๋ณ๊ฒฝ ์ฌํญ ์๋ ๋ฐ์ | Kafka ๋ฑ ์ถ๊ฐ ๊ตฌ์ฑ ํ์ | ๊ฒ์ ๋ฐ์ดํฐ๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ |
์ ํ๋ฆฌ์ผ์ด์ ์ง์ ์ฐ๋ | ๋๊ธฐํ ๋ก์ง์ ์ง์ ๊ด๋ฆฌ ๊ฐ๋ฅ | ์๋น์ค ์ฝ๋ ๋ณต์กํด์ง | ๊ฒ์ ๋ฐ์ดํฐ๊ฐ ์ ๊ณ , ํธ๋ํฝ์ด ์ ์ ๊ฒฝ์ฐ |
๐ ๊ฒฐ๋ก
1๏ธโฃ ํ ๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์ฎ๊ธธ ๋ → Batch ๋ฐฉ์
2๏ธโฃ ์ค์๊ฐ ๋๊ธฐํ ํ์ํ๋ฉด → CDC ๋ฐฉ์(Debezium, Kafka)
3๏ธโฃ ์ ํ๋ฆฌ์ผ์ด์
์์ ์ง์ ๊ด๋ฆฌ ๊ฐ๋ฅํ๋ฉด → ์ง์ ๋๊ธฐํ
๐์ค๋ฌด์์๋ ๋ณดํต 1๏ธโฃ+2๏ธโฃ ์กฐํฉ
'Backend > spring cloud (MSA)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
OpenFeign: MSA ์๋น์ค ๊ฐ ํต์ (0) | 2025.04.10 |
---|---|
๋ฐ์ดํฐ ํตํฉ ์กฐํ ๋ฐฉ๋ฒ ์ค๊ณ (0) | 2025.04.06 |
MSA ํ๊ฒฝ์์ ์ธ์ฆ/ํ ํฐ ์ฌ์์ฒญ (0) | 2025.04.06 |
์ค์๊ฐ ๋ฐ์ดํฐ ์ ์ก (0) | 2025.02.23 |
[Kafka] ๊ณ ๊ธ ์ค์ (0) | 2025.02.23 |