-
Notifications
You must be signed in to change notification settings - Fork 28
프롤로그 검색 기능, 근데 이제 Elasticsearch를 곁들인
- Elasticsearch란 무엇인가?
- Elasticsearch 도입 이유
- 프롤로그에서의 Elasticsearch 구조
- 프롤로그에서의 Elasticsearch 쿼리
- Elasticsearch 관련 문제 발생 시 해결 방법
- 참고 자료
Elasticsearch란 무엇인가에 대해 이야기하기에 앞서, 검색엔진과 검색시스템에 대해서 먼저 알아보자.
검색 엔진이라는 것은, 웹과 같은 방대한 곳에서 정보를 수집해 검색 결과를 제공하는 프로그램이다. 이는 검색 결과로 제공되는 데이터의 특성에 따라 구현 형태가 달라진다.
ex. 카테고리 검색, 디렉터리 기반 검색 등
검색 시스템이라는 것은, 대용량 데이터를 기반으로 신뢰성 있는 검색 결과를 제공하기 위해 검색 엔진 기반 구축된 시스템을 통칭하는 용어이다. 그리고 이 검색 시스템을 활용해서 우리는 검색 서비스를 만들 수 있다.
검색 서비스 > 검색 시스템 > 검색 엔진
그럼 엘라스틱 서치는 무엇일까?
Elasticsearch는 텍스트, 숫자, 위치 기반 정보, 정형 및 비정형 데이터 등 모든 유형의 데이터를 위한 무료 검색 및 분석 엔진으로 분산형 및 개방형을 특징으로 합니다.
공식 홈페이지에서 가져온 정의이다. 즉, 엘라스틱 서치 또한 검색 엔진으로서 검색 서비스를 만들 때 사용할 수 있는 하나의 프로그램이다.
그렇다면 우리는 왜 Elasticsearch를 이용해서 검색 엔진을 구현하려고 하는 것일까? RDBMS로 질의해서 결과를 얻어낼 수 있지 않을까?
그 이유에 대해 알아보기 위해 RDBMS와 Elasticsearch(이하 ES)의 차이와, ES의 장점에 대해 알아보자.
RDBMS | Elasticsearch |
---|---|
정형 데이터, 구조화 | 비정형 데이터, 색인/ 검색 |
텍스트 매칭을 통한 단순 검색 | 역색인 구조를 통한 빠른 검색 |
유의어, 동의어 X | 유의어, 동의어 O |
미리 정의된 스키마 | 스키마리스 |
- 전문 검색(Fulltext search)이 가능하다.
- 전문 검색이란 내용 전체를 색인해서 특정 단어가 포함된 문서를 검색하는 것을 말한다.
- 스키마리스
- 미리 정의된 스키마가 없어도 데이터를 스스로 분석하여 필드를 생성하고 저장한다.
- RESTful API를 제공한다.
- 멀티테넌시
- 멀티테넌시란 단일 인스턴스로 서로 다른 사용자에게 서비스를 제공할 수 있는 것을 말한다.
- 서로 상이한 인덱스라도 검색 필드명만 같으면 여러개를 한번에 조회할 수 있다. 이를 통해 멀티테넌시를 지원할 수 있다.
- 역색인 구조
- 역색인이란 책의 뒷편 단어 색인 페이지와 같이 제공되는 특수 데이터구조이다.
- 간단히 생각해보면, 500쪽으로 구성된 책에서 "역색인"이라는 단어를 찾으려면 오랜 시간이 걸린다. 하지만 맨 뒷장 제공되는 단어 색인 페이지에서 역색인을 찾아 해당 페이지로 이동하는 것은 훨씬 빠르게 페이지를 찾을 수 있다.
역색인에 대해서 좀 더 자세히 알아보자. (왜냐면 내가 생각했을 때 역색인이 가장 큰 장점인 것 같기 때문.) 예를 들어, 다음과 같은 세 문장을 저장한다고 가정하자.
- 서민정 안녕
- 서민정 조앤 인사
- 조앤 안녕
위 세 문장은 역색인되어 다음과 같이 저장된다.
- 다음은 예시이며 실제는 더 많은 정보가 역색인을 통해 저장된다.
단어 | 문서 번호 | 빈도 |
---|---|---|
서민정 | 1, 2 | 2 |
조앤 | 2, 3 | 2 |
안녕 | 1, 3 | 2 |
인사 | 2 | 1 |
위와 같은 구조에서, 안녕이 검색어로 들어온다면 바로 1,3번 문서를 결과로 응답할 수 있다.
따라서 위에서 언급한 것과 같은 장점으로 인해 검색 엔진
에서는 RDBMS보다 ES에서 더 효율적인 결과를 얻을 수 있다.
그렇다면, ES의 단점은 없을까? 사실 글이 너무 길어지긴 하지만 그래도 써보겠다.
- 실시간 X
- 색인된 데이터는 약 1초 뒤부터 검색이 가능하다. 색인되는 과정에서 commit, flush 등의 복잡한 과정을 필요로하기 때문이다. 준실시간으로 볼 수 있다.
- 트랜잭션과 롤백을 지원하지 않는다.
- 클러스터 환경과 같은 분산 시스템에 적합한 구조로 설계된 ES는 트랜잭션과 롤백을 지원하지 않는다. 이는 spring에서 제공되는 spring-data-elasticsearch에서도 마찬가지이다.
- 이는 그래프 데이터베이스를 제외한 NoSQL의 특징과도 유사하다.
- NoSQL에 대한 자세한 정보는 이 책을 참고하길..
- 데이터의 수정을 지원하지 않는다.
- ES는 update시 수정이 아닌, 삭제 + 삽입으로 수정이 이루어진다. 이러한 이유로 수정 시 단순 업데이트에 비해 상대적으로 많은 비용이 발생한다.
- 이는 단점일 수도 있지만, 불변이라는 이점을 얻을 수도 있다.
아무쪼록, ES의 장단점과 RDBMS와의 비교까지 해 보았다.
앞으로 누군가 프롤로그는 왜 검색에서 ES를 사용했나요?
라고 묻는다면 위 장점을 곁들여 설명할 수 있을 것이다. (아마도..)
이제, 프롤로그에서 도입한 인덱스 매핑 구조 및 인프라 구조에 대해서 알아보자.
ES는 RDBMS와는 조금 다른 용어를 사용한다. 용어 비교는 다음과 같다.
RDBMS | ES |
---|---|
데이터베이스 | 인덱스 |
파티션 | 샤드 |
테이블 | 타입 |
행 | 도큐먼트 |
컬럼 | 필드 |
용어도 알아보았으니, 인덱스 매핑 구조 먼저 알아보자.
인덱스 매핑에 대한 자세한 설명을 주석으로 달기 보다는, 큰 개념에 대해서 설명하고, 각자 json 형태의 매핑 코드를 보고 어떤 식으로 매핑되었는지 이해하는 것이 더 좋을 것 같다.
인덱스를 매핑한다는 것은, RDBMS에서 CREATE DATABASE
를 하는 것과 같다. "엥? 스키마리스인데 왜 스키마 생성해!"라고 할 수 있다. 사실 ES는 스키마리스를 지원하는 것이 맞다. 하지만 스키마리스는 되도록이면 사용하지 않는 것이 좋다. 예를 들어보자.
쪼애니: "문이 뭐게?"
웨지: '문? 열고 닫는 그 문이겠지..'
외국인 잭슨씨: '문? Moon?'
즉, 입력을 했을 때 받아먹긴 하지만 예상치 못한 결과를 얻을 수 있다. 따라서 스키마리스를 지양하는 것이 좋다.
아무튼 여담은 걷어치우고 다시 인덱스 매핑으로 돌아와보자. 우리는 인덱스를 등록하고, 해당 인덱스에 문서를 저장하기 위해서는 몇가지 설정들이 필요하다. 그 설정을 인덱스를 매핑할 때 미리 작성해둘 수 있다.
특히, 분석기를 설정할 수 있는데, 분석기라는 것은 문서를 색인하기 전에 해당 문서의 필드 타입이 무엇인지 확인하고 텍스트 타입이면 분석기를 활용해 이를 분석한다.
예를 들어, "조앤은 똑똑하다"를 Standard Analyzer(Default 분석기)에 넣었을 때, 이는 "조앤은", "똑똑하다"로 분석된다. (Standard Analyzer는 whitespace 기준으로 토큰을 자른다.) 따라서 누군가 검색창에 "조앤"이라고 검색하면, 분석기에는 "조앤은"으로 분석되어있기 때문에 원하는 검색 결과를 얻을 수 없다. 이렇듯, 적절한 분석기를 설정해주는 것은 매우 중요하다.
정리. 색인할 때 특정한 규칙과 흐름에 의해 텍스트를 변경(토큰화)하는 과정을 분석(Analyze)이라고 하며, 해당 처리는 분석기(Analyzer)를 통해서 이루어진다.
아래 json에서도 확인할 수 있겠지만, 프롤로그는 한글 검색 위주이기 때문에 nori라는 토크나이저를 분석기로서 활용하고 있다. 노리는 루씬(ES는 루씬 기반 검색 엔진이다.)에서 공식적으로 제공해주는 한국어 지원 토크나이저이다. 자세한 내용은 여기에서 확인할 수 있다.
위에서는 분석기가 문장을 토큰화하고, 분석하는 것에 대해서 알아보았다. 분석기의 분석이 이루어지기 이전에, 필터를 거쳐 문장에 대해 우리가 원하는 특정 필터링을 할 수 있다. 예를 들어 <p>HTML</p>
와 같은 문장이 들어왔을 때, html 태그를 제거하고 싶은 경우 char_filter
에서 제공하는 html_strip
을 활용해 <p>HTML</p> => HTML
로 필터링할 수 있다.
또한 토큰 필터로서 분리된 토큰에서 제거할 특정 형태소를 지정할 수 있다. 우리가 사용하는 nori 플러그인을 예시로 들자면, filter의 stoptags에 제거할 형태소를 지정해주면 해당 형태소들이 문장에서 제거된다. 예를 들어, 아래 stoptags의 예시에서 “J”는 마침표를 제거해준다. 따라서 안녕.
과 같이 마침표가 포함된 문장은 해당 필터를 거쳐 안녕
으로 필터링된다.
stoptags에 대한 자세한 정보는 여기에서 확인할 수 있다.
{
"settings":{
"index":{
"analysis":{
"tokenizer":{
"nori_tokenizer_mixed_dict":{
"type":"nori_tokenizer",
"decompound_mode":"mixed"
}
},
"analyzer": {
"korean":{
"type":"custom",
"tokenizer":"nori_tokenizer_mixed_dict",
"filter":[
"nori_readingform","lowercase",
"nori_part_of_speech_basic"]
}
},
"filter":{
"nori_part_of_speech_basic":{
"type":"nori_part_of_speech",
"stoptags":["E","IC","J","MAG","MAJ","MM","SP","SSC","SSO","SC","SE","XPN","XSA","XSN","XSV","UNA","NA","VSV"]
}
}
}
}
}
}
이제, 인프라 구조에 대해서 알아보자. 현재 프롤로그에서는 EC2의 Docker로 Elasticsearch를 띄워 이용하고 있다. 프롤로그에 적용된 도커 컴포즈 파일의 자세한 내용은 여기에서 docker-compose 파일을 확인하면 된다.
docker를 활용한 elasticsearch의 실행과 관련해 자세한 내용은 Install Elasticsearch with Docker | Elasticsearch Guide 7.15 | Elastic 에서 확인할 수 있다.
참고로 Elasticsearch는 영문 문서가 매우 잘 되어 있다. (영문..)
이제 끝이 보인다. 우리는 어떤 검색 쿼리를 이용해서 검색 로직을 수행하고 있는지 알아보자.
그 전에, 이 글을 읽다보면 그럼 RDBMS와 Elasticsearch는 따로 구축되어있는데, 어떻게 동기화하지?
라는 의문이 들 수 있다. 이에 대해 먼저 답변하고, 쿼리에 대해 알아보자.
현재 프롤로그에서는 학습로그의 CUD 작업이 일어날 때마다 ES에도 함께 CUD를 수행하고 있다. 그리고 가끔 (..) ES 인스턴스의 상태가 안좋아서 다시 reload 하거나, 재부팅해야하는 경우에는 sync API를 사용하고 있다. sync API는 StudylogDocumentController에 정의되어있다. 이는 단순히 RDBMS에 저장된 모든 데이터를 조회한 뒤, ES에 전부 삽입하는 구조로 이루어져있다.
아무튼 여담은 끝내고, 다시 쿼리로 돌아와보자. 우리는 검색 쿼리로서 bool 쿼리와 filter를 사용하고 있다. 간단한 filter를 먼저 알아보자면 날짜 범위 쿼리는 필터로 질의하고 있다. 즉, start와 end 날짜 범위가 들어오면 전체 데이터에서 해당 날짜 범위에 속하는 데이터들로 필터링을 걸어준다. bool 쿼리는 복합 쿼리이다. 즉, 여러 조건을 조합하기 위해 bool 복합 쿼리를 사용했다. 특히 bool 쿼리에서 사용할 수 있는 must 인자를 사용했다.
- must: 쿼리가 참인 도큐먼트들을 검색한다.
- 복합쿼리의 must 인자 내부에 query_string 쿼리를 이용해 실제 검색을 수행한다.
query_string
쿼리는 query에 적힌 내용을 검색하며, 검색 대상이 될 필드를 명시해줄 수 있다.
검색 쿼리에 대한 자세한 이론은 여기에서 확인할 수 있다. 프롤로그에 적용된 쿼리는 아래와 같으며, 변경 가능성은 무한대이다.
{
"query": {
"bool" : {
"must" : [
{
"query_string" : {
"query" : "*\\?*",
"fields" : [
"content^1.0",
"title^1.0"
]
}
},
{
"query_string" : {
"query" : "*seovalue*",
"default_field" : "username"
}
},
{
"query_string" : {
"query" : "*",
"default_field" : "levelId"
}
},
{
"query_string" : {
"query" : "*",
"default_field" : "missionId",
"default_operator" : "or"
}
}
],
"filter" : [
{
"range" : {
"dateTime" : {
"from" : "19000101",
"to" : "99991231"
}
}
}
],
}
}
}
이제, 프롤로그에서 주로 발생하는 ES 관련 문제를 어떻게 해결하는지 알아보자.
- 엘라스틱 서치와 커넥션이 이루어지지 않아 학습로그 검색, 삽입, 수정 등의 작업이 모두 실패하는 경우
- 해결 방법
- AWS EC2 대시보드에 접속해 환경에 알맞은 prolog-es-stack 인스턴스의 상태를 확인한다.
- 인스턴스 연결성 검사에 실패한 경우라면 해당 인스턴스를 재부팅한다.
- 브라운께 pem key를 전달받은 뒤 해당 인스턴스에 접속한다.
- prolog/search 디렉토리로 이동해
docker-compose -f docker-compose-{profile}.yml up -d
을 수행한다. - prolog/search 디렉토리로 이동해 다음 HELP.md에 적힌대로 쉘 스크립트를 실행한다.
- 끗
Elastic 가이드 북 - Elastic 가이드북
Elasticsearch Guide 7.15 | Elastic