서론
오늘날처럼 데이터가 폭발적으로 증가하는 시대에는 방대한 양의 데이터에서 필요한 정보를 효율적으로 검색하는 것이 핵심적인 요구사항이 되었습니다.
Elasticsearch는 분산형, 고가용성, 고성능 등의 특징을 갖춘 강력한 오픈소스 검색 엔진으로, 대규모 데이터의 전문 검색을 빠르게 처리할 수 있습니다.
반면 Spring Boot는 간결한 개발 방식과 풍부한 생태계를 통해 개발자가 애플리케이션을 신속하게 구축할 수 있도록 돕습니다.
본문에서는 Spring Boot 프로젝트에 Elasticsearch를 통합하여 전문 인덱싱 및 쿼리 기능을 구현하는 방법을 상세히 소개합니다.
1. Elasticsearch 핵심 메커니즘 분석
1.1. 역색인: 속도의 근원
Elasticsearch는 역색인 구조를 채택하여 Term와 문서 ID의 매핑을 통해 고속 검색을 구현합니다.
문서 1: "Spring Boot Elasticsearch 통합 실전"
문서 2: "고성능 검색 아키텍처 설계"
역색인: Spring -> [1] Boot -> [1] Elasticsearch -> [1] 검색 -> [2] 아키텍처 -> [2]
기존 데이터베이스의 B+ 트리 인덱스와 비교하여 역색인은 퍼지(fuzzy) 검색 성능을 100배 이상 향상시킵니다.
1.2. 분산 아키텍처 설계
- 샤드(Shard): 인덱스 데이터를 수평으로 분할하여 병렬 처리를 지원합니다 (기본 5개의 주 샤드).
- 복제본(Replica): 각 샤드는 1개의 복제본을 가지며, 고가용성과 로드 밸런싱을 보장합니다.
- 준실시간(NRT: Near Real-time): 쓰기 작업 후 1초 이내에 검색 가능하며, _refresh 인터페이스를 통해 강제로 새로 고칠 수 있습니다.
2. Spring Boot 통합 실전
2.1. 환경 준비 및 의존성 설정
권장 버전 조합:
- Spring Boot 2.7.x
- Elasticsearch 7.17.x (호환성 좋음)
- IK Analyzer 7.17.0
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>com.github.yangtu222</groupId>
<artifactId>ik-analyzer</artifactId>
<version>7.17.0</version>
</dependency>
application.properties에 Elasticsearch 연결 설정을 추가합니다.
spring.elasticsearch.rest.uris=http://localhost:9200
보안 Elasticsearch 클러스터를 사용하는 경우 사용자 이름과 비밀번호도 설정해야 합니다.
spring.elasticsearch.rest.username=your-username
spring.elasticsearch.rest.password=your-password
2.2. 엔티티 클래스 매핑
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Document(indexName = "articles")
public class Article {
@Id
private String id;
@Field(type = FieldType.Text)
private String title;
@Field(type = FieldType.Text)
private String content;
// 생성자, Getter 및 Setter 메서드
public Article() {}
public Article(String title, String content) {
this.title = title;
this.content = content;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
위 코드에서 @Document 어노테이션은 이 엔티티 클래스가 articles라는 Elasticsearch 인덱스에 해당함을 지정합니다. @Id 어노테이션은 문서의 고유 식별자를 나타냅니다. @Field 어노테이션은 필드의 타입을 지정하는 데 사용되며, 여기서는 FieldType.Text를 사용하여 해당 필드가 텍스트 타입이며 전문 검색에 적합함을 나타냅니다.
2.3. Repository 인터페이스 생성
Article 엔티티에 대한 CRUD 작업 및 쿼리를 위해 ElasticsearchRepository를 상속하는 인터페이스를 생성합니다.
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface ArticleRepository extends ElasticsearchRepository<Article, String> {}
2.4. 전문 인덱스 쿼리 구현
Service 레이어에서 데이터를 삽입하는 메서드를 구현합니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ArticleService {
@Autowired
private ArticleRepository articleRepository;
public Article saveArticle(Article article) {
return articleRepository.save(article);
}
public List<Article> saveArticles(List<Article> articles) {
return articleRepository.saveAll(articles);
}
}
QueryBuilder를 사용하여 쿼리 조건을 구성하는 전문 검색 메서드를 구현합니다.
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ArticleSearchService {
@Autowired
private ElasticsearchOperations elasticsearchOperations;
public List<Article> searchArticles(String keyword) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
.build();
SearchHits<Article> searchHits = elasticsearchOperations.search(searchQuery, Article.class);
return searchHits.getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
}
위 코드에서는 MultiMatchQuery를 사용하여 title 및 content 필드에 대한 전문 검색을 수행합니다.
2.5. 컨트롤러 레이어
Service 레이어의 메서드를 RESTful API로 노출하는 컨트롤러를 생성합니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/articles")
public class ArticleController {
@Autowired
private ArticleService articleService;
@Autowired
private ArticleSearchService articleSearchService;
@PostMapping
public Article saveArticle(@RequestBody Article article) {
return articleService.saveArticle(article);
}
@PostMapping("/batch")
public List<Article> saveArticles(@RequestBody List<Article> articles) {
return articleService.saveArticles(articles);
}
@GetMapping("/search")
public List<Article> searchArticles(@RequestParam String keyword) {
return articleSearchService.searchArticles(keyword);
}
}
3. 테스트
Spring Boot 애플리케이션을 시작하고 Postman 또는 다른 도구를 사용하여 테스트합니다.
3.1. 데이터 삽입
http://localhost:8080/articles로 POST 요청을 보내고, 요청 본문에 JSON 형식의 Article 객체를 포함합니다.
{
"title": "Spring Boot Elasticsearch 통합",
"content": "이 문서는 Spring Boot가 Elasticsearch를 통합하여 전문 인덱싱 쿼리를 구현하는 방법을 상세히 설명합니다."
}
3.2. 전문 검색
http://localhost:8080/articles/search?keyword=Elasticsearch로 GET 요청을 보내면 Elasticsearch 키워드를 포함하는 문서 목록을 쿼리할 수 있습니다.
4. 데이터 동기화 방안 비교
4.1. 동기화 전략 선택
양방향 쓰기 동기화 | 낮음 | 낮음 | 낮음 | 소규모 단일 앱 |
메시지 큐 비동기 | 높음 | 중간 | 중간 | 마이크로서비스 |
Canal binlog 리스닝 | 매우 높음 | 낮음 | 높음 | 대규모 분산 시스템 |
4.2. 메시지 큐 비동기 동기화 예시
// 상품 서비스에서 메시지 발행
@PostMapping("/product")
public Product createProduct(@RequestBody Product product) {
productService.save(product);
rabbitTemplate.convertAndSend("product-exchange",
"product.create", product.getId());
return product;
}
// 검색 서비스에서 메시지 수신 처리
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(name = "product-exchange"),
value = @Queue(name = "product.queue"),
key = "product.*"))
public void syncProduct(Long productId) {
Product product = productService.getById(productId);
elasticsearchRepository.save(product);
}
5. 고급 검색 기능 구현
5.1. 다중 필드 가중치 검색
public Page<Article> search(String keyword, int page, int size) {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword)
.field("title", 2.0f) // 제목 가중치 2배
.field("content")
.type(MultiMatchQueryBuilder.Type.BEST_FIELDS))
.withPageable(PageRequest.of(page, size))
.build();
return elasticsearchOperations.search(query, Article.class);
}
5.2. 검색 제안 (Completion Suggester)
@GetMapping("/suggest")
public List<String> suggest(@RequestParam String prefix) {
CompletionSuggestionBuilder suggestion = SuggestBuilders
.completionSuggestion("title.suggest")
.prefix(prefix)
.skipDuplicates(true);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
.suggest(new SuggestBuilder().addSuggestion("title-suggest", suggestion));
SearchResponse response = restHighLevelClient.search(
new SearchRequest("articles").source(sourceBuilder),
RequestOptions.DEFAULT);
return response.getSuggest()
.getSuggestion("title-suggest")
.getEntries().stream()
.flatMap(e -> e.getOptions().stream())
.map(Suggest.Suggestion.Entry.Option::getText)
.collect(Collectors.toList());
}
6. 성능 튜닝 방안
6.1. 인덱스 최적화 전략
PUT /articles/_settings
{
"index": {
"refresh_interval": "30s", // 새로 고침 빈도 감소
"translog.durability": "async",
"number_of_replicas": 1
}
}
6.2. 쿼리 성능 최적화
- 캐싱 전략: 요청 캐시 활성화 ?request_cache=true
- 페이지네이션 최적화: from+size 대신 search_after 사용 (깊은 페이지네이션 시나리오)
- 필드 필터링: _source를 지정하여 반환 필드 필터링
'개발 언어 > Java, Javascript' 카테고리의 다른 글
Java – Stream 스트림의 고급 사용법 (3) | 2025.05.27 |
---|---|
개발 효율을 2배로! 숨겨진 꿀템, Top 10 무료 Tailwind 기반 UI 라이브러리 (2) | 2025.05.13 |
놓치면 후회할 12가지 오픈소스 풀스택 JavaScript 프로젝트 (0) | 2025.05.11 |
React + Tailwind로 만드는 웹 테트리스 게임 (2) | 2025.04.24 |
[Java] 자바 Graphics - 마우스로 선그리기 소스예제 (0) | 2012.05.14 |