
앞선 게시글을 통해 Elasticsearch의 특징과 핵심 개념 등을 살펴 보았습니다. 이번 글에서는 진행하고 있는 개인 프로젝트에 Elasticsearch를 직접 적용하여 효과적인 검색이 가능하도록 구현해보겠습니다.
현재 프로젝트는 '개인 맞춤형 뉴스 제공'을 목적으로 스프링 스케줄러를 통해 네이버 뉴스 기사를 수집하여 데이터로 활용하고 있습니다. 이에 엘라스틱서치를 활용하여 수집되는 뉴스 데이터를 쌓아두고, 강력한 검색 기능을 제공할 수 있도록 하겠습니다.
우선 엘라스틱서치를 도서관을 예를 들어 진행 중인 프로젝트에 어떤 이점이 있는지 살펴보겠습니다.
이와 같이 '개인 맞춤형 뉴스 제공'을 목표로 하는 프로젝트에 엘라스틱서치를 통해 아래와 같은 이점을 취할 수 있을 것으로 예상됩니다.
강력한 전문(Full-text) 검색 : 사용자가 뉴스 제목이나 내용의 일부만으로 원하는 기사를 빠르고 정확하게 찾을 수 있습니다. (SQL의 LIKE '%...%' 와는 비교할 수 없는 성능과 정확도)
콘텐츠 기반 추천의 초석 : 특정 뉴스 기사와 "유사한 내용의 다른 뉴스"를 찾는 것이 매우 쉬워집니다. 예를 들어, 사용자가 '인공지능'에 대한 뉴스를 좋아했다면, Elasticsearch에 "이 기사와 비슷한 기사들을 찾아줘"라고 요청하여 관련 뉴스를 추천해 줄 수 있습니다.
다양한 통계 및 필터링 : 카테고리별, 기간별, 키워드별 뉴스 집계 및 필터링을 매우 빠른 속도로 처리하여 관리자 대시보드나 사용자 필터 기능을 구현하는 데 유리합니다.
# Docker로 Elasticsearch 8.14.1 버전 실행 명령어
docker run -d --name simple-elastic -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:8.14.1
Spring Boot 프로젝트가 Elasticsearch와 통신할 수 있도록 관련 라이브러리를 build.gradle 파일에 추가합니다.
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'

Elasticsearch 서버 연결을 위해 application.yml 에 설정 정보를 추가합니다. 일반적으로 Elasticsearch는 9200번 포트로 통신합니다. 로컬 개발 환경을 기준으로, localhost:9200으로 접속하도록 설정합니다.
elasticsearch:
uris: http://localhost:9200
이제 Elasticsearch에 어떤 데이터를, 어떤 형태로 저장할지 설계합니다. 이 역할을 하는 것이 바로 Document 클래스입니다.
@Document
Elasticsearch에서는 @Document 어노테이션을 붙인 클래스를 만들어 인덱스(Index)에 저장될 문서(Document) 데이터를 표현합니다.
NewsDocument.java 파일 생성:
검색에 필요한 필드만 모아서 NewsDocument 라는 새로운 클래스를 만듭니다. 이 클래스는 com.ccp.simple.document 라는 새로운 패키지에 생성하여, DB용 domain 객체와 명확히 분리합니다.
이 클래스에는 다음과 같은 어노테이션을 사용하여 각 필드를 어떻게 저장하고 검색할지 Elasticsearch에게 알려줍니다.
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "news") // "news"라는 이름의 인덱스에 문서를 저장
@Setting(settingPath = "elasticsearch/nori-settings.json") // nori 분석기 설정 파일 경로 지정
public class NewsDocument {
@Id
private Long newsId; // 뉴스 원본 ID
@Field(type = FieldType.Text, analyzer = "nori_analyzer")
private String title;
@Field(type = FieldType.Text, analyzer = "nori_analyzer")
private String description;
@Field(type = FieldType.Keyword) // Keyword 타입 사용
private String link;
@Field(type = FieldType.Date)
private LocalDateTime pubDt;
}
{
"analysis": {
"analyzer": {
"nori_analyzer": {
"tokenizer": "nori_tokenizer"
}
}
}
}
@Document(indexName = "news"): 이 클래스의 데이터는 Elasticsearch의 "news"라는 인덱스에 저장하라는 의미입니다.@Id: 이 필드가 문서의 고유 식별자(Primary Key)임을 나타냅니다.@Field(type = FieldType.Text): 이 필드는 전문(Full-text) 검색이 가능한 텍스트 데이터임을 나타냅니다. Elasticsearch는 이 필드의 내용을 단어 단위로 쪼개고 분석(Analyze)하여, 내용 기반 검색이 가능하도록 만듭니다. (title, description에 적합)@Field(type = FieldType.Date): 날짜 형식의 데이터임을 나타냅니다.nori 분석기란?
Elasticsearch가 한국어 텍스트를 더 잘 이해하도록 도와주는 형태소 분석기입니다.
'nori' 분석을 사용하면 "뉴스를 검색합니다" 라는 문장을 단순히 공백으로 자르는 것이 아니라, 의미를 가진 최소 단위인 형태소, 즉 '뉴스', '검색', '하다'로 분해합니다. 사용자가 "검색하는 뉴스"라고 입력해도 "뉴스를 검색합니다"라는 제목의 기사를 조회할 수 있게 되어 검색 품질이 비약적으로 향상됩니다.
Elasitcsearch에 기본적으로 포함되어 있어 별도의 설치 없이 바로 사용할 수 있습니다.



Spring Data에서는 ElasticsearchRepository 인터페이스를 상속받아 엘라스틱서치 관련 기본적인 CRUD 메소드를 구현할 수 있습니다. (예전에 공부했던 JPA 와 비슷한 형식)
덕분에 별도의 SQL 쿼리를 작성하지 않고도 newsSearchRepository.save(), newsSearchRepository.findById(), newsSearchRepository.delete() 와 같은 메소드를 바로 사용할 수 있어 매우 편리합니다.
package com.ccp.simple.repository;
import com.ccp.simple.document.NewsDocument;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface NewsSearchRepository extends ElasticsearchRepository<NewsDocument, Long> {
}
뉴스 수집 시 PostgreSQL에 저장함과 동시에 Elasticsearch에도 저장(Indexing) 하도록 기존 뉴스 수집 로직을 수정합니다.
// DB 뉴스 저장
newsMapper.insertNews(news);
// 1. DB 뉴스 저장
newsMapper.insertNews(news);
// 2. Elasticsearch에 뉴스 인덱싱
NewsDocument newsDocument = NewsDocument.builder()
.newsId(newsId)
.title(news.getTitle())
.description(news.getDescription())
.link(news.getLink())
.pubDt(news.getPubDt())
.build();
newsSearchRepository.save(newsDocument);
// 뉴스 검색
@GetMapping("/news/search")
public List<NewsDocument> searchNews(@RequestParam("q") String query) {
return newsService.searchNews(query);
}
@Override
public List<NewsDocument> searchNews(String query) {
// 1. multi_match_query 생성
NativeQuery nativeQuery = new NativeQueryBuilder()
.withQuery(q -> q
.multiMatch(mmq -> mmq
.fields("title", "description")
.query(query)
)
)
.build();
// 2. 검색 실행
SearchHits<NewsDocument> searchHits = elasticsearchOperations.search(nativeQuery, NewsDocument.class);
// 3. 결과 반환
return searchHits.getSearchHits().stream()
.map(hit -> hit.getContent())
.collect(Collectors.toList());
}





지금까지 Spring Boot 환경에 Elasticsearch를 성공적으로 연동하고, nori 한국어 분석기를 적용하여 RDBMS의 한계를 뛰어넘는 효율적인 전문 검색 기능을 구현했습니다.
다중 필드 동시 검색: 사용자는 검색어 하나만 입력했지만, 시스템은 multi_match 쿼리를 통해 뉴스의 제목과 본문 내용을 동시에 탐색하여 관련도 높은 결과를 찾아냅니다.
한국어 형태소 분석: nori 분석기 덕분에, 사용자가 "뉴스를 검색하는 방법"이라고 검색해도, "뉴스 검색 방법"이나 "뉴스의 검색"이 포함된 문서를 찾아낼 수 있습니다. 이처럼 단어의 원형과 조사를 이해하는 지능적인 검색이 가능해졌습니다.
높은 성능과 확장성: 수백만 건의 문서가 쌓이더라도 Elasticsearch는 거의 실시간에 가까운 검색 속도를 보장합니다. 이는 사용자에게 쾌적한 검색 경험을 제공할 뿐만 아니라, 향후 데이터가 증가하더라도 안정적인 성능을 유지할 수 있는 확장성을 확보했음을 의미합니다.