검색이란?
검색은 정보를 찾기 위해 인터넷이나 컴퓨터 데이터베이스 등을 통해
특정한 키워드나 질의문을 이용하여 원하는 정보를 찾는 과정을 말한다.
검색 엔진을 통해 수많은 웹 페이지, 문서, 이미지, 동영상 등이 색인화되어 있어
사용자가 키워드를 입력하면 해당 키워드와 관련된 정보를 검색 결과로 제공한다.
검색은 다양한 형태로 이루어질 수 있다.
가장 기본적인 검색은 텍스트 기반의 검색이며, 사용자가
키워드나 질의문을 입력하여 관련된 문서를 찾는다.
이외에도 이미지 검색, 동영상 검색, 뉴스 검색, 지도 검색 등 다양한 형태의 검색이 가능하다.
검색 엔진은 크롤러라고 불리는 프로그램을 이용하여 웹 페이지를 수집하고,
이를 색인화하여 검색 결과를 생성한다.
대표적인 검색 엔진으로는 Elasticsearch 가 존재한다.
RDB는 검색엔진으로써 부적절한가?
전통적으로 사용하고 있는 RDB를 검색엔진으로 사용하기엔 부적절하느냐에 질문에는
무조건 부적절하다고 말할 수 밖에 없다.
그 이유는 아래와 같다.
1. 검색 성능의 한계
RDB는 주로 트랜잭션 처리와 데이터 일관성을 위해 설계되었다.
따라서 대량의 데이터를 실시간으로 검색하고 쿼리 하기에는 제약이 생긴다.
RDB는 주로 인덱스를 활용하여 검색 성능을 향상시키지만,
대용량 데이터나 복잡한 검색 조건을 다룰 때는 성능이 저하된다.
2. 구조적인 제한
RDB는 정해진 스키마에 따라 데이터를 구조화하고 저장한다.
이는 데이터의 일관성과 무결성을 보장하는 장점이 있지만,
유연성과 다양성 측면에서는 제한적일 수 있다.
즉, RDB는 사전에 정의된 테이블과 컬럼 구조에 맞추어 데이터를 저장하므로,
실시간으로 변화하는 데이터나 비정형 데이터를 효과적으로 처리하기 어렵다.
3. 검색 범위 제한
RDB는 일반적으로 정형화된 데이터를 처리하는 데 최적화되어 있다.
그러나 텍스트 기반의 검색과 같은 비정형 데이터를 다루는 데에는 제한이 있다.
RDB는 특정 컬럼에서 일치하는 값을 검색하는 데에는 용이하지만,
복잡한 텍스트 검색이나 유사성 기반의 검색을 수행하기에는 부적합할 수 있다.
4. 확장성의 어려움
RDB는 주로 수직적인 확장(Vertical Scaling)에 의존한다.
이는 단일 서버의 성능을 향상하는 방식으로 제한이 있다.
따라서 대규모 데이터나 많은 동시 사용자를 처리해야 하는 경우,
수평적인 확장(Horizontal Scaling)이 필요한 검색엔진에는 부적합할 수 있다.
RDB / 검색엔진의 차이점
1. RDB 의 텍스트 검색
RDB에서는 기본적으로, 인덱스(Index)라는 테이블 내의 데이터를 더욱 빠르게
찾아주기 위한 데이터 구조가 존재한다.
예를들어 상품 테이블에서 "초코"라는 단어가 들어가는 상품명을 찾기 위해서는
아래와 같은 구문을 써야 한다.
SELECT * FROM PRODUCT_TABLE WHERE prodt_nm LIKE '%초코%'
하지만, 위와 같은 쿼리를 사용하게 되면, 아무리 인덱스 설계와 튜닝을 잘해놨다고 하더라도
(%) wildcard 즉 모든 패턴 매칭 때문에, 인덱스를 절대로 사용할 수 없다.
즉, 무조건 테이블내의 전체 데이터를 스캔하는 방법뿐이다.
아래와 같이 예제 테이블에서 Tiger를 찾을 때 RDB 가 데이터를 찾아주는 방식을 확인해 보자.
당연하게도 테이블의 모든 데이터에 순차적으로 접근하여,
해당 DATA에 찾고자 하는 문자열이 있는지 확인해 줄 것이다.
2. Elasticsearch의 텍스트 검색
반면에, Elasticsearch는 RDB와 데이터를 저장하는 방식부터가 다르다.
역인덱스 (Inverted Index)라는 방법을 사용하는데,
역인덱스 기술은 검색을 하기에 아주 최적화된 인덱스 기술이다.
위와 같은 데이터가 주어졌을 때,
아래와 같이 저장을 수행한다.
특정 단어를 분석하여 해당 단어가 어느 위치에 있는지 적어준다고 생각하면 쉽다.
그럼 만약 'Tiger'라는 문자열이 포함된 데이터를 찾아주고 싶다면,
아래와 같은 방식으로 데이터를 더 빠르게 찾아갈 수 있다.
물론 실제 검색엔진의 구조는 훨씬 더 복잡하다.
아래와 그림과 같이 특정 데이터가 들어오게 되면,
필터링과 토큰화를 통해서, 검색 품질을 높이는 밑작업을 수행해 준다.
RDB / 검색엔진의 차이점 테스트
백문이 불여일타라고 하였다.
그럼 RDBMS에서 특정 데이터를 검색하는 것과,
Elasticsearch와 같은 전용 검색엔진에서 데이터를 검색하는 것이
얼마만큼의 차이가 발생하는지 확인해 보자.
RDBMS는 SQL SERVER를 사용할 것이다.
일단, 검색할 데이터 각각의 스키마는 아래와 같다.
[SQL SERVER]
[Elasticsearch]
PUT /elastic_dw_test
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "nori_tokenizer"
}
}
}
},
"mappings": {
"properties": {
"class_id": {
"type": "long"
},
"brand_no": {
"type": "integer"
},
"benefit_price": {
"type": "long"
},
"link_info": {
"type": "long"
},
"disp_nm": {
"type": "text",
"analyzer" : "my_analyzer"
}
}
}
}
현재 Sql Server와 Elasticsearch 내에는 위와 같은 형식의 스키마를
만족하는 데이터가 10만 건이 존재한다.
해당 데이터를 이용해서 실습해 보기로 하자.
JAVA 환경에서 검색테스트를 진행해 보자.
Controller 코드는 아래와 같다.
package com.test.SYJ_Mall;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.test.SYJ_Mall.elasticsearchDw.IElasticDwService;
import com.test.SYJ_Mall.sqlServerJpa.ISqlServerService;
@Controller
public class ElasticVersusRdb {
@Autowired
private IElasticDwService elasticService;
@Autowired
private ISqlServerService sqlService;
// 검색 페이지로 보내준다.
@RequestMapping(value = "/searchTestEsSql.action", method = { RequestMethod.GET , RequestMethod.POST})
public String searchTestEsSql(HttpServletRequest request, HttpServletResponse response) {
return "/test/searchtest";
}
// RDBMS 를 통해서 검색결과를 가져와준다.
@RequestMapping(value = "/sqlServerTime.action", method = { RequestMethod.GET , RequestMethod.POST})
@ResponseBody
public List<String> sqlServerTime(HttpServletRequest request, HttpServletResponse response) {
String keyword = request.getParameter("search_keyword");
long startTime = System.currentTimeMillis();
List<String> searchResult = sqlService.getSearchData(keyword);
long endTime = System.currentTimeMillis();
long timeElapsed = endTime - startTime;
searchResult.add(Long.toString(timeElapsed));
return searchResult;
}
// Elasticsearch 를 통해서 검색결과를 가져와준다.
@RequestMapping(value = "/elasticTime.action", method = { RequestMethod.GET , RequestMethod.POST})
@ResponseBody
public List<String> elasticTime(HttpServletRequest request, HttpServletResponse response) {
String keyword = request.getParameter("search_keyword");
long startTime = System.currentTimeMillis();
List<String> searchResult = elasticService.getSearchData(keyword);
long endTime = System.currentTimeMillis();
long timeElapsed = endTime - startTime;
searchResult.add(Long.toString(timeElapsed));
return searchResult;
}
}
SQL에서 데이터 처리를 해줄 Service를 따로 구성해 주고,
Elasticsearch에서 데이터를 처리해 준 Service 도 따로 구성해 주자.
컨트롤러 코드를 자세히 보면, 검색 처리를 기점으로 시간을 계산하는 걸 볼 수 있다.
즉, SQL에서 검색을 할 경우에 경과시간과
Elasticsearch에서 검색을 할 경우의 경과시간을 비교해 볼 수 있다.
1. SQL 검색
SQL과 관련된 코드는 JPA로 구성해 주겠다.
아키텍처 구성은 아래와 같다.
[ISqlServerService.java]
package com.test.SYJ_Mall.sqlServerJpa;
import java.util.List;
public interface ISqlServerService {
List<String> getSearchData(String keyword);
}
[SqlServerService.java]
package com.test.SYJ_Mall.sqlServerJpa;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.springframework.stereotype.Service;
@Service
public class SqlServerService implements ISqlServerService {
@Override
public List<String> getSearchData(String keyword) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("sqlserver");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
List<String> resultList = new ArrayList<>();
tx.begin();
try {
String queryString = "SELECT e.dispNm FROM MongoDwDTO e WHERE e.dispNm LIKE :keyword";
resultList = em.createQuery(queryString, String.class)
.setParameter("keyword", "%" + keyword + "%")
.getResultList();
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
em.close();
}
emf.close();
return resultList;
}
}
검색을 SQL- Like로 처리했을 경우에 검색 양상이다.
"긍정"이라는 단어를 포함하는 데이터를 찾아볼 것이다.
경과시간은 464ms이다.
실제로 실행계획은 아래와 같다.
위와 같이 LIKE 절 검색이기 때문에,
테이블 전체를 스캔한 다음에 데이터를 가져오는 모습을 볼 수 있다.
100000건의 데이터를 읽고 일치하는 45개의 데이터를 가져오고 있다.
2. Elasticsearch 검색
Elasticsearch와 관련된 구성 아키텍처는 아래와 같다.
[ElasticConfig.java]
package com.test.SYJ_Mall.elasticsearchDw;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic_id", "elastic_pw"));
RestClientBuilder builder = RestClient.builder(new HttpHost("ip_address", 9200))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
return new RestHighLevelClient(builder);
}
}
[IElasticDwService.java]
package com.test.SYJ_Mall.elasticsearchDw;
import java.util.List;
public interface IElasticDwService {
List<String> getSearchData(String keyword);
}
[ElasticDwService.java]
package com.test.SYJ_Mall.elasticsearchDw;
import java.util.ArrayList;
import java.util.List;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ElasticDwService implements IElasticDwService {
private final ElasticConfig elasticClient;
@Autowired
public ElasticDwService(ElasticConfig elasticClient) {
this.elasticClient = elasticClient;
}
@Override
public List<String> getSearchData(String keyword) {
List<String> searchResult = new ArrayList<>();
try {
SearchRequest searchRequest = new SearchRequest("elastic_dw_test");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.size(100); // 결과 데이터 크기를 100으로 설정
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("disp_nm", keyword);
matchQueryBuilder.analyzer("my_analyzer");
searchSourceBuilder.query(matchQueryBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = elasticClient.restHighLevelClient().search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
String dispNm = hit.getSourceAsMap().get("disp_nm").toString();
searchResult.add(dispNm);
}
return searchResult;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
검색을 Elasticsearch로 처리했을 경우에 검색 양상이다.
이번에도 마찬가지로 "긍정"이라는 단어를 포함하는 데이터를 찾아볼 것이다.
SQL로 검색할 때에는 경과시간이 464ms었는데,
검색엔진인 Elasticsearch로 검색을 한 결과는 28ms 가 나왔다.
Elasticsearch 가 약 20배가량 빠른 경과시간을 보여준다.
Elasticsearch는 RDB 와는 다르게 SCAN 작업이 존재하지 않는다.
(데이터를 저장할 때 역인덱스 형태로 저장하므로)
그러므로 아래와 같이 바로 45개의 데이터를 찾는 것을 볼 수 있다.
그럼 검색서비스를 구현할 경우에
단지 경과시간 때문에 전용 검색엔진을 사용하는 것일까?
당연히 아니다. SQL과 전용 검색엔진은
검색의 품질 자체도 완전하게 다르다.
만약 SQL을 통해서 "김치 열무"라는 단어를 검색했다고 가정해 보자.
그럼 SQL LIKE의 동작으로 인해서 꼭 "~~ 김치 열무~~" 의 패턴을 만족하는 데이터만 찾아줄 것이다.
테스트 테이블에는 위와 같은 패턴의 데이터가 없으므로 검색결과는 아무것도 나오지 않게 된다.
하지만, 검색 전용엔진인 Elasticsearch에서는 다르다.
각 단어별로 분석과 필터링을 거쳐서 역인덱스를 구성하므로
"김치 열무"라는 단어를 검색해도 비슷한 데이터를 찾아준다.
이와 같이 검색 서비스측면에서는 성능과 품질 모두
RDBMS 보다 Elasticsearch 가 앞서는 것을 볼 수 있다.
'Elasticsearch' 카테고리의 다른 글
[Elasticsearch] GC Log 분석 (0) | 2024.12.26 |
---|---|
[Elasticsearch] maximum shards open error (0) | 2023.12.27 |
[Elasticsearch] Nori 분석기 적용 (0) | 2023.05.14 |
[Elasticsearch] Disk-based shard allocation (1) | 2023.03.02 |
[Elasticsearch] text, keyword 타입 (0) | 2023.03.02 |