Hanbit ebook Realtime 93 실무 예제로 배우는 Elasticsearch 검색엔진 활용편 정호욱 지음
실무예제로배우는 Elasticsearch 검색엔진활용편 정호욱지음
실무예제로배우는 Elasticsearch 검색엔진활용편 초판발행 2015 년 3 월 9 일 지은이정호욱 / 펴낸이김태헌펴낸곳한빛미디어 ( 주 ) / 주소서울시마포구양화로 7길 83 한빛미디어 ( 주 ) IT출판부전화 02-325-5544 / 팩스 02-336-7124 등록 1999년 6월 24일제10-1779호 ISBN 978-89-6848-742-2 15000 / 정가 15,000원 총괄배용석 / 책임편집김창수 / 기획 편집정지연디자인표지 / 내지여동일, 조판최송실마케팅박상용 / 영업김형진, 김진불, 조유미 이책에대한의견이나오탈자및잘못된내용에대한수정정보는한빛미디어 ( 주 ) 의홈페이지나아래이메일로알려주십시오. 한빛미디어홈페이지 www.hanbit.co.kr / 이메일 ask@hanbit.co.kr Published by HANBIT Media, Inc. Printed in Korea Copyright c 2015 정호욱 & HANBIT Media, Inc. 이책의저작권은정호욱과한빛미디어 ( 주 ) 에있습니다. 저작권법에의해보호를받는저작물이므로무단복제및무단전재를금합니다. 지금하지않으면할수없는일이있습니다. 책으로펴내고싶은아이디어나원고를메일 (ebookwriter@hanbit.co.kr) 로보내주세요. 한빛미디어 ( 주 ) 는여러분의소중한경험과지식을기다리고있습니다.
저자소개 지은이 _ 정호욱 지난 13년동안야후코리아, NHN Technology, 삼성전자에서커뮤니티, 소셜검색, 광고검색관련서비스를개발해오면서검색엔진을활용한다양한프로젝트를수행하였다. 현재빅데이터전문기업인그루터 Gruter 에서오픈소스기반검색엔진개발자로근무하고있다. Elasticsearch 기술에대한정보와경험을현재개인블로그 (http://jjeong.tistory.com) 를통해공유하고있다.
저자서문 검색엔진은모든서비스의기본이되는핵심요소입니다. 우리가사용하는모든서비스에는검색기능이포함되어있습니다. 하지만검색엔진관련기술은일반사용자가접근하기에는너무어려운기술로남아있습니다. 루씬 Lucene 이라는오픈소스검색라이브러리가진입장벽을많이낮추기는했지만, 서비스에적용하기에는개발자가직접구현해야하는기능이너무많고관리와유지보수가어렵다는문제가있었습니다. 하지만이런문제점은 Elasticsearch라는오픈소스검색엔진이나오면서사라졌고전문적인검색엔진및서비스개발자가아니더라도누구나쉽게검색서비스를만들수있게되었습니다. 비싼라이선스비용을내고검색품질과기능을커스터마이징하기어려운벤더중심의검색엔진을사용하고있다면 Elasticsearch로꼭바꾸길추천합니다. 아직국내에는 Elasticsearch 사용자층이넓지않습니다. 이책은 Elasticsearch에관심은있으나어디서부터시작해야할지모르는사용자와검색을모르는사용자가쉽게서비스를만들수있도록도움을주고자집필하였습니다. 끝으로이책을집필하는데많은도움을주신그루터권영길대표님그리고이책이세상에빛을볼수있도록많은도움을주신한빛미디어김창수님, 정지연님, 이중민님께감사의말을전합니다. 집필을마치며 정호욱
대상독자및참고사항 초급초중급중급중고급고급 이책은검색엔진을이용한다양한기술과의접목과활용, 사용자정의기능을구현해서적용할수있는플러그인구현방법까지 Elasticsearch를적극적으로활용할수있는방법을보여줍니다. 또한, 기본적인성능최적화방법과가이드를제공하여대용량트래픽의처리와안정성을확보하는데도움을줄수있도록구성되어있습니다. 이책을읽으시려면루씬에대한기본지식이필요합니다. 또한, 설치와구성등기본적인내용은이책에서다루지않으므로이책의전작인 실무예제로배우는 Elasticsearch 검색엔진 ( 기본편 ) ( 한빛미디어, 2014) 을읽어보시길권합니다. 사용하는용어나기술에대한기본지식이없을경우이해하는데어려움이있을수있습니다. 이도서의예제소스코드는다음에서내려받을수있습니다. https://github.com/howookjeong?tab=repositories
한빛 ebook 리얼타임 한빛 ebook 리얼타임은 IT 개발자를위한 ebook 입니다. 요즘 IT 업계에는하루가멀다하고수많은기술이나타나고사라져갑니다. 인 터넷을아무리뒤져도조금이나마정리된정보를찾기도쉽지않습니다. 또한, 잘정리되어책으로나오기까지는오랜시간이걸립니다. 어떻게하면조금이라 도더유용한정보를빠르게얻을수있을까요? 어떻게하면남보다조금더빨 리경험하고습득한지식을공유하고발전시켜나갈수있을까요? 세상에는수 많은종이책이있습니다. 그리고그종이책을그대로옮긴전자책도많습니다. 전자책에는전자책에적합한콘텐츠와전자책의특성을살린형식이있다고생 각합니다. 한빛이지금생각하고추구하는, 개발자를위한리얼타임전자책은이렇습니다. 1 ebook Only - 빠르게변화하는 IT 기술에대해핵심적인정보를신속하게제공합니다 500페이지가까운분량의잘정리된도서 ( 종이책 ) 가아니라, 핵심적인내용을빠르게전달하기위해조금은거칠지만 100페이지내외의전자책전용으로개발한서비스입니다. 독자에게는새로운정보를빨리얻을기회가되고, 자신이먼저경험한지식과정보를책으로펴내고싶지만너무바빠서엄두를못내는선배, 전문가, 고수분에게는좀더쉽게집필할수있는기회가될수있으리라생각합니다. 또한, 새로운정보와지식을빠르게전달하기위해 O'Reilly의전자책번역서비스도하고있습니다. 2 무료로업데이트되는전자책전용서비스입니다 종이책으로는기술의변화속도를따라잡기가쉽지않습니다. 책이일정분량이상으 로집필되고정리되어나오는동안기술은이미변해있습니다. 전자책으로출간된이 후에도버전업을통해중요한기술적변화가있거나저자 ( 역자 ) 와독자가소통하면서보완하여발전된노하우가정리되면구매하신분께무료로업데이트해드립니다.
3 4 독자의편의를위해 DRM-Free 로제공합니다 구매한전자책을다양한 IT 기기에서자유롭게활용할수있도록 DRM-Free PDF 포맷으로제공합니다. 이는독자여러분과한빛이생각하고추구하는전자책을만들어나가기위해독자여러분이언제어디서어떤기기를사용하더라도편리하게전자책을볼수있도록하기위함입니다. 전자책환경을고려한최적의형태와디자인에담고자노력했습니다종이책을그대로옮겨놓아가독성이떨어지고읽기어려운전자책이아니라, 전자책의환경에가능한한최적화하여쾌적한경험을드리고자합니다. 링크등의기능을적극적으로이용할수있음은물론이고글자크기나행간, 여백등을전자책에가장최적화된형태로새롭게디자인하였습니다. 앞으로도독자여러분의충고에귀기울이며지속해서발전시켜나가도록하겠 습니다. 지금보시는전자책에소유권한을표시한문구가없거나타인의소유권한을표시한문구가있다면위법하게사용하고있을가능성이큽니다. 이경우저작권법에따라불이익을받으실수있습니다. 다양한기기에사용할수있습니다. 또한, 한빛미디어사이트에서구매하신후에는횟수에관계없이내려받으실수있습니다. 한빛미디어전자책은인쇄, 검색, 복사하여붙이기가가능합니다. 전자책은오탈자교정이나내용의수정 보완이이뤄지면업데이트관련공지를이메일로알려드리며, 구매하신전자책의수정본은무료로내려받으실수있습니다. 이런특별한권한은한빛미디어사이트에서구매하신독자에게만제공되며, 다른사람에게양도나이전은허락되지않습니다.
차례 chapter 1 검색기능확장 001 1.1 자동완성 001 1.1.1 자동완성 Analyzer 002 1.1.2 자동완성예제 005 1.2 Percolator 013 1.2.1 Percolator 생성 014 1.2.2 Percolator Query 등록 017 1.2.3 Percolator 요청 019 1.3 Join 021 1.3.1 Parent-Child 022 1.3.2 Nested 029 1.4 River 035 1.4.1 JDBC River 동작의이해 035 1.4.2 JDBC River 설치전준비작업 037 1.4.3 JDBC River 설치 039 1.4.4 JDBC River indice 구성 040 1.4.5 JDBC River 등록 041 1.4.6 JDBC River 실행 047 1.4.7 JDBC River에서 REST API 이용하기 048 1.5 정리 049
chapter 2 검색데이터분석 051 2.1 Bucket Aggregation 052 2.1.1 Global Aggregation 053 2.1.2 Filter Aggregation 055 2.1.3 Missing Aggregation 056 2.1.4 Nested Aggregation 057 2.1.5 Reverse Nested Aggregation 058 2.1.6 Terms Aggregation 060 2.1.7 Significant Terms Aggregation 063 2.1.8 Range Aggregation 065 2.1.9 Date Range Aggregation 067 2.1.10 Histogram Aggregation 069 2.1.11 Date Histogram Aggregation 071 2.1.12 Geo Distance Aggregation 072 2.2 Metric Aggregation 074 2.2.1 Min Aggregation 074 2.2.2 Max Aggregation 075 2.2.3 Sum Aggregation 076 2.2.4 Avg Aggregation 077 2.2.5 Stats Aggregation 078 2.2.6 Extended Stats Aggregation 079 2.2.7 Value Count Aggregation 080 2.2.8 Percentiles Aggregation 081 2.2.9 Cardinality Aggregation 082 2.3 정리 083
chapter 3 Plugin 085 3.1 Plugin 제작 085 3.1.1 Plugin 프로젝트생성 085 3.1.2 Plugin 프로젝트구성 086 3.2 REST Plugin 만들기 088 3.2.1 REST Plugin 프로젝트생성과등록 088 3.2.2 REST Plugin 기능구현 090 3.2.3 REST Plugin 등록설정 090 3.2.4 REST Plugin 빌드와설치 091 3.2.5 REST Plugin 구현요약 093 3.3 Analyzer Plugin 만들기 094 3.3.1 Analyzer Plugin 프로젝트생성과등록 094 3.3.2 Analyzer Plugin 기능구현 095 3.3.3 Analyzer Plugin 등록설정 098 3.3.4. Analyzer Plugin 빌드와설치 100 3.4 정리 103 chapter 4 Hadoop 연동 105 4.1 MapReduce 연동 107 4.1.1 준비항목 108 4.1.2 색인 MapReduce 구현 108 4.1.3 검색 MapReduce 구현 111 4.2 Hive 연동 114 4.2.1 준비항목 114 4.2.2 색인구현 115 4.2.3 검색구현 118 4.3 정리 120
chapter 5 ELK 연동 121 5.1 Logstash 121 5.1.1 다운로드와설치 122 5.1.2 실행과테스트 123 5.1.3 Command-line Flag 알아보기 124 5.1.4 Config 알아보기 126 5.1.5 Input 알아보기 129 5.1.6 Filter 알아보기 130 5.1.7 Output 알아보기 132 5.1.8 Codec알아보기 134 5.2 Elasticsearch 135 5.2.1 다운로드와설치 135 5.2.2 실행과테스트 136 5.2.3 기본플러그인설치 136 5.3 Kibana 137 5.3.1 다운로드와설치 137 5.3.2 실행 138 5.3.3 대시보드만들기 140 5.4 정리 148 chapter 6 SQL 활용하기 149 6.1 RDB 관점의 Elasticsearch 150 6.1.1 Index vs. Database 150 6.1.2 Type vs. Table 150 6.1.3 Document vs. Row 151 6.1.4 Field vs. Column 151 6.1.5 Analyzer vs. Index 151 6.1.6 _id vs Primary Key 151
6.1.7 Mapping vs. Schema 152 6.1.8 Shard/Route vs. Partition 152 6.1.9 Parent-Child/Nested vs. Relation 152 6.1.10 Query DSL vs. SQL 153 6.2 SQL 정의하기 153 6.2.1 SQL 정의 153 6.2.2 인덱스생성 / 삭제 / 선택 157 6.2.3 테이블생성 / 삭제 158 6.2.4 절선택 / 삽입 / 업데이트 / 삭제 159 6.3 SQL 변환하기 160 6.3.1 Match_all Query 160 6.3.2 Match Query 161 6.3.3 Bool Query 161 6.3.4 Ids Query 165 6.3.5 Range Query 165 6.3.6 Term Query 167 6.3.7 Terms Query 167 6.4 JDBC Driver 만들기 168 6.4.1 Elasticsearch Driver 169 6.4.2 Elasticsearch Connection 169 6.4.3 Elasticsearch Statement 170 6.4.4 Elasticsearch ResultSet 171 6.4.5 Elasticsearch ResultSetMetaData 172 6.4.6 Elasticsearch JDBC Driver 예제 173 6.5 정리 174
chapter 7 Elasticsearch 성능최적화 175 7.1 하드웨어관점 175 7.1.1 CPU 176 7.1.2 RAM 178 7.1.3 DISK 179 7.1.4 NETWORK 179 7.2 Document 관점 180 7.2.1 Index와 Shard 튜닝 180 7.2.2 Modeling 182 7.3 Operation 관점 186 7.3.1 설정튜닝 186 7.3.2 검색튜닝 192 7.3.3 색인튜닝 195 7.4 정리 197
실무예제로배우는 Elasticsearch 검색엔진 활용편 실무예제로배우는 Elasticsearch 검색엔진 < 기본편 > 에서는 Elasticsearch의기본개념과설치방법, 검색서비스구성까지살펴봤습니다. 이번 < 활용편 > 에서는 < 기본편 > 에서다루지못한확장기능과다양한서비스의활용방법, Elasticsearch의성능최적화방법을알아보겠습니다.
chapter 1 검색기능확장 1.1 자동완성 자동완성 Auto Completion 기능은검색서비스에서가장많이사용하는기능의하나로, 사용자가입력하는검색쿼리를실시간으로입력받아문장을완성합니다. 이기능은키워드추천이나오타교정등에활용할수있습니다. 검색서비스에서자동완성기능은전방일치, 부분일치, 후방일치기능을제공합니다. 전방일치는입력한질의가문장의앞부분에서매칭이이루어지는것을의미하고, 부분일치는입력한질의가문장의중간에서매칭되는것을의미합니다. 그리고후방일치는전방일치와반대로문장의뒷부분에서매칭됩니다. 전방 / 부분 / 후방일치로매칭된다는것은형태소분석으로추출된토큰 Token 단위의색인어 (Term) 가일치되는것을의미합니다. Elasticsearch에서는이런자동완성기능을구현하기위해 Prefix 쿼리, Suggester, Analyzer(ngram, edge ngram) 를이용합니다. Prefix 쿼리는전방일치기능을구현할때손쉽게적용할수있는데, 적용을위해서는필드 Field01 의인덱스 Index02 속성이 not_analyzed가되어야합니다. Suggester는 Term, Phrase, Completion, Context Suggester의 4가지기능이있으며아직개 01 RDBMS에서테이블의 column에해당한다. 02 데이터를저장하기위한장소로, RDBMS의데이터베이스와유사하다. 1 검색기능확장 - 001
발중입니다. 이기능들은유사한색인어를찾아추천해줍니다. analyzer 중 ngram과 edge ngram을이용하는것은자동완성용필드의텍스트를미리분석하여색인하는방법입니다. 다음그림은 가, 나, 다, 라 를각각의색인어라고가정했을때전방 / 부분 / 후방일치에대한예입니다. 각그림의앞항목은입력한질의어고, 뒤항목은분석되어저장된단어가매칭된모습입니다. 그림 1-1 전방일치 가가나다라 그림 1-2 부분일치 나가나다라 그림 1-3 후방일치 라가나다라 1.1.1 자동완성 Analyzer 자동완성용 indice 03 를생성할때 analyzer의설정을살펴보겠습니다. 자동완성기능을사용하려면자동완성질의필드에용도에맞춰 tokenizer를구성해야합니다. NOTE Tokenizer 색인과정에서루씬에전달된일반텍스트는 Tokenization이라는과정을거치게됩니다. 이는 Token이라는인덱스의작은요소로입력텍스트를검색할수있도록처리하는것을말합니다. 이러한작업은단순히일반텍스트를분리하는것뿐만아니라텍스트를제거, 변형, 임의의패턴매칭, 필터링, 텍스트정규화그리고동의어확장까지다양한처리를하게되는데, 이를담당하는요소가 Tokenizer입니다. 03 Index는포괄적인의미의색인또는색인파일이고, Indice는 Elasticsearch 내에서물리적으로사용되는색인또는색인파일이라고보면된다. Indice는기존검색엔진의 collection과같다. 002 -
[ Analyzer 설정 ] "analyzer" : { "ngram_analyzer" : { "type" : "custom", "tokenizer" : "ngram_tokenizer", "filter" : ["lowercase", "trim"], "edge_ngram_analyzer" : { "type" : "custom", "tokenizer" : "edge_ngram_tokenizer", "filter" : ["lowercase", "trim"], "edge_ngram_analyzer_back" : { "type" : "custom", "tokenizer" : "edge_ngram_tokenizer", "filter" : ["lowercase", "trim", "edge_ngram_filter_back"] ngram 은음절단위로색인어를생성하는방식으로재현율은높으나정확도는떨 어집니다. ngram_tokenizer, lowercase 필터, trim 필터로구성하고, 첫음절 을기준으로 max_gram 에서지정한최대길이만큼색인어를생성합니다. [ ngram 색인결과 ] min_gram: 1 max_gram: 5 text: 실무예제로배우는검색엔진 terms: [" 실 ", " 실무 ", " 무 ", " 예 ", " 예제 ", " 예제로 ", " 제 ", " 제로 ", " 로 ", " 배 ", " 배우 ", " 배우는 ", " 우 ", " 우는 ", " 는 ", " 검 ", " 검색 ", " 검색엔 ", " 검색엔진 ", " 색 ", " 색엔 ", " 색엔진 ", " 엔 ", " 엔진 ", " 진 "] edge ngram은 ngram과매우유사한데, min_gram 크기부터 max_gram 크기까지지정한 tokenizer의특성에맞춰각색인어에대한 ngram 색인어를생성하는방식입니다. edge_ngram_tokenizer, lowercase 필터, trim 필터로구성합니다. 1 검색기능확장 - 003
[ edge ngram 색인결과 ] min_gram: 1 max_gram: 5 text: 실무예제로배우는검색엔진 terms: [" 실 ", " 실무 ", " 예 ", " 예제 ", " 예제로 ", " 배 ", " 배우 ", " 배우는 ", " 검 ", " 검색 ", " 검색엔 ", " 검색엔진 "] edge ngram back은 edge ngram과같은방식으로동작하나색인어의순서가역순이됩니다. 이 analyzer를후방일치에사용하려면 edge ngram 필터옵션중 side:back을반드시설정해야하고, edge_ngram_tokenizer, lowercase 필터, trim 필터, edgengram 필터로구성합니다. [ edge ngram back 색인결과 ] min_gram: 1 max_gram: 5 text: 실무예제로배우는검색엔진 terms: [" 실 ", " 무 ", " 실무 ", " 예 ", " 제 ", " 예제 ", " 로 ", " 제로 ", " 예제로 ", " 배 ", " 우 ", " 배우 ", " 는 ", " 우는 ", " 배우는 ", " 검 ", " 색 ", " 검색 ", " 엔 ", " 색엔 ", " 검색엔 ", " 진 ", " 엔진 ", " 색엔진 ", " 검색엔진 "] ngram 과 edge ngram back 의결과를보면추출방식에서차이가있는것을 알수있습니다. 이해를돕기위해 검색엔진활용 으로 ngram 과 edge ngram back 의결과를비교하면추출된색인어의차이를확인할수있습니다. 표 1-1 ngram 과 edge ngram back 결과비교 ngram min_gram: 1 max_gram: 5 text: 검색엔진활용 terms: [ 검, 검색, 검색엔, 검색엔진, 검색엔진활, 색, 색엔, 색엔진, 색엔진활, 색엔진활용, 엔, 엔진, 엔진활, 엔진활용, 진, 진활, 진활용, 활, 활용, 용 ] edge ngram back min_gram: 1 max_gram: 5 text: 검색엔진활용 terms: [ 검, 색, 검색, 엔, 색엔, 검색엔, 진, 엔진, 색엔진, 검색엔진, 활, 진활, 엔진활, 색엔진활, 검색엔진활 ] 004 -
1.1.2 자동완성예제 자동완성기능을테스트하려면 analyzer 구성과함께 term 쿼리를이용하는방 법과 prefix 쿼리를이용하는방법이있습니다. 자동완성설정먼저테스트를위한 indice 생성에필요한 settings와 mappings 설정을살펴보겠습니다. 설정관련자세한내용은제공되는소스코드 04 를참고하기바라며여기서는일부코드만다루겠습니다. [ Analyzer 필드구성 - schema/autocompletion.json ] "keyword" : { "type" : "string", "store" : "no", "index" : "analyzed", "omit_norms" : true, "index_ options" : "offsets", "term_vector" : "with_positions_offsets", "include_in_all" : false, "fields" : { "keyword_prefix" : {"type" : "string", "store" : "no", "index" : "not_analyzed", "omit_norms" : true, "index_options" : "offsets", "term_vector" : "with_positions_offsets", "include_in_all" : false, "keyword_edge" : {"index_analyzer" : "edge_ngram_analyzer", "type" : "string", "store" : "no", "index" : "analyzed", "omit_norms" : true, "index_options" : "offsets", "term_vector" : "with_positions_offsets", "include_in_all" : false, "keyword_edge_back" : {"index_analyzer" : "edge_ngram_analyzer_back", "type" : "string", "store" : "no", "index" : "analyzed", "omit_norms" : true, "index_options" : "offsets", "term_vector" : "with_positions_offsets", "include_in_all" : false, 다양한검색옵션을적용하기위해 keyword 필드를멀티필드로구성하고, 각필드의검색옵션은유형별로구성합니다. keyword_prefix : Prefix 쿼리에서사용하기위한필드 keyword_edge : Term 쿼리를이용한전방일치용필드 keyword_edge_back : Term 쿼리를이용한후방일치용필드 04 http://bit.ly/1w0ka01 1 검색기능확장 - 005
edge ngram analyzer 와비교하기위해구성한설정입니다. [ ngram_analyzer settings ] "ngram_analyzer" : { "type" : "custom", "tokenizer" : "ngram_tokenizer", "filter" : ["lowercase", "trim"] term 쿼리를이용하여전방일치를구현하는설정입니다. [ edge_ngram_analyzer settings ] "edge_ngram_analyzer" : { "type" : "custom", "tokenizer" : "edge_ngram_tokenizer", "filter" : ["lowercase", "trim"] term 쿼리를이용하여후방일치를구현하는설정입니다. [ edge_ngram_analyzer_back settings ] "edge_ngram_analyzer_back" : { "type" : "custom", "tokenizer" : "edge_ngram_tokenizer", "filter" : ["lowercase", "trim", "edge_ngram_filter_back"] tokenizer 의타입은 ngram, token 의최소길이는 1, 최대길이는 5 로설정합니다. [ ngram_tokenizer settings ] "ngram_tokenizer" : { "type" : "ngram", "min_gram" : "1", "max_gram" : "5", "token_chars": [ "letter", "digit", "punctuation", "symbol" ] 006 -
ngram tokenizer 와설정은거의동일하며타입만 edgengram 으로설정합니다. [ edge_ngram_tokenizer setting ] "edge_ngram_tokenizer" : { "type" : "edgengram", 중략 전방일치기능을구현하기위해 side 값을 front로설정합니다. [ edge_ngram_filter_front setting ] "edge_ngram_filter_front" : { "type" : "edgengram", "min_gram" : "1", "max_gram" : "5", "side" : "front" 후방일치기능을구현하기위해 side 값을 back 으로설정합니다. [ edge_ngram_filter_back setting ] "edge_ngram_filter_back" : { "type" : "edgengram", "min_gram" : "1", "max_gram" : "5", "side" : "back" REST API 를이용하여생성합니다. [ 자동완성 indice 생성 ] $ curl -XPUT http://localhost:9200/autocompletion -d @autocompletion.json 전체데이터는소스코드를참고하기바랍니다. 1 검색기능확장 - 007
[ 자동완성데이터등록 data/autocompletion.json ] { "index" : { "_index" : "autocompletion", "_type" : "search_keyword" { "keyword_id" : 1, "keyword" : "open source search engine", "keyword_ranking" : 20 { "index" : { "_index" : "autocompletion", "_type" : "search_keyword" { "keyword_id" : 2, "keyword" : "elasticsearch", "keyword_ranking" : 30 중략 REST API 를이용하여등록합니다. $ curl -s -XPOST http://localhost:9200/autocompletion/_bulk --data-binary @autocompletion. data.json 자동완성테스트코드여기서는 prefix 쿼리, ngram, edge ngram을이용한전방일치와 edge ngram back을이용한후방일치예제를살펴보겠습니다. prefix 쿼리는전방일치에사용할수있지만일치된색인어에대한강조를적용할수없습니다. 이는해당필드에대한인덱스설정을 not_analyzed로구성하여전체값을강조처리하기때문입니다. [ Prefix 쿼리예제코드 ] Settings settings = Connector.buildSettings("elasticsearch"); Client client = Connector.buildClient(settings, new String[] {"localhost:9300"); PrefixQueryBuilder querybuilder = new PrefixQueryBuilder("keyword_prefix", "elastic"); String searchresult = Operators.executeQuery(settings, client, querybuilder, "autocompletion"); 결과를보면 keyword 필드에 elastic 으로시작하는문서가매칭된것을확인할 수있습니다. Prefix 쿼리예제결과 "hits" : { 008 -
"total" : 4, "max_score" : 1.0, "hits" : [ { "_index" : "autocompletion", "_type" : "search_keyword", "_id" : "3", "_score" : 1.0, "_source":{ "keyword_id" : 3, "keyword" : "elasticsearch vs solr", "keyword_ ranking" : 10, 중략 ] 다음예제는매칭된색인어를정확히구분하기위해강조기능을추가하였습니다. analyzed 속성을갖는 keyword 필드에 term 쿼리를실행한예제로, 추출된색인어를 ngram 분석으로매칭합니다. 즉, ucene 으로는매칭되지만 lucene 으로는매칭되지않습니다. 이는 max_gram을 5로설정하였기때문입니다. 여기서검색색인어를 lucen 대신 ucene 로한이유는 edge ngram과구분하기위해서입니다. [ ngram을이용한질의예제코드 ] Settings settings = Connector.buildSettings("elasticsearch"); Client client = Connector.buildClient(settings, new String[] {"localhost:9300"); TermQueryBuilder querybuilder = new TermQueryBuilder("keyword", "ucene"); String searchresult = Operators.executeQueryHighlight(settings, client, querybuilder, "autocompletion", "keyword", "strong"); highlight 영역에 strong 태그로강조된것을확인할수있습니다. ngram을이용한질의예제결과 "hits" : { "total" : 2, "max_score" : 1.4054651, "hits" : [ { 1 검색기능확장 - 009
"_index" : "autocompletion", "_type" : "search_keyword", "_id" : "4", "_score" : 1.4054651, "_source":{ "keyword_id" : 4, "keyword" : "lucene based search engine", "keyword_ ranking" : 10, "highlight" : { "keyword" : [ "l<strong>ucene</strong> based search engine" ], { "_index" : "autocompletion", "_type" : "search_keyword", "_id" : "5", "_score" : 1.4054651, "_source":{ "keyword_id" : 5, "keyword" : "elasticsearch based on lucene", "keyword_ ranking" : 10, "highlight" : { "keyword" : [ "elasticsearch based on l<strong>ucene</strong>" ] ] 다음예제는 ngram과비교하기위해작성하였습니다. ngram에서는 ucene 이라는키워드로질의하였고 edge ngram에서는 lucen 이라는키워드로질의합니다. 이둘의차이는앞에서 ngram_analyzer와 edge_ngram_analyzer 부분에서설명한내용에서참고하기바랍니다. [ edge ngram을이용한질의예제코드 ] Settings settings = Connector.buildSettings("elasticsearch"); Client client = Connector.buildClient(settings, new String[] {"localhost:9300"); TermQueryBuilder querybuilder = new TermQueryBuilder("keyword_edge", "lucen"); String searchresult = Operators.executeQueryHighlight(settings, client, querybuilder, "autocompletion", "keyword_edge", "strong"); edge ngram 을이용한질의예제결과 "hits" : { 010 -
"total" : 2, "max_score" : 1.4054651, "hits" : [ { "_index" : "autocompletion", "_type" : "search_keyword", "_id" : "4", "_score" : 1.4054651, "_source":{ "keyword_id" : 4, "keyword" : "lucene based search engine", "keyword_ ranking" : 10, "highlight" : { "keyword_edge" : [ "<strong>lucen</strong>e based search engine" ], { "_index" : "autocompletion", "_type" : "search_keyword", "_id" : "5", "_score" : 1.4054651, "_source":{ "keyword_id" : 5, "keyword" : "elasticsearch based on lucene", "keyword_ ranking" : 10, "highlight" : { "keyword_edge" : [ "elasticsearch based on <strong>lucen</strong>e" ] ] 다음은후방일치에대한예제코드입니다. 질의어를 e 한글자로설정하였으므 로결과에서강조된색인어의제일뒤에위치한문자는 e 가됩니다. [ edge ngram을이용한후방일치예제코드 ] Settings settings = Connector.buildSettings("elasticsearch"); Client client = Connector.buildClient(settings, new String[] {"localhost:9300"); TermQueryBuilder querybuilder = new TermQueryBuilder("keyword_edge_back", "e"); String searchresult = Operators.executeQueryHighlight(settings, client, querybuilder, "autocompletion", "keyword_edge_back", "strong"); 앞에서설명한것과같이강조된글자의마지막문자가 e 로끝난것을확인할수 있습니다. 1 검색기능확장 - 011
edge ngram을이용한후방일치예제결과 "hits" : { "total" : 10, "max_score" : 1.4246359, "hits" : [ { "_index" : "autocompletion", "_type" : "search_keyword", "_id" : "4", "_score" : 1.4246359, "_source":{ "keyword_id" : 4, "keyword" : "lucene based search engine", "keyword_ ranking" : 10, "highlight" : { "keyword_edge_back" : [ "<strong>luce</strong>ne <strong>base</strong>d <strong>se</strong>arch <strong>e</strong>ngine" ], 중략 ] 한글후방일치는네이버메인검색창또는지식쇼핑검색창에 청바지 라는검색 어를넣으면간단히확인할수있습니다. 다음그림의블록지정된부분이후방일 치부분입니다. 그림 1-4 한글후방일치예 012 -
1.2 Percolator Percolator는문서색인보다는문서모니터링에주로사용하며역검색 (Reverse Search) 이라고도합니다. 이기능은증권, 경매, 광고등의다양한서비스에서활용할수있고, 특정로그에대한감시용으로도사용할수있습니다. Elasticsearch에서 percolator는인덱스에쿼리를하나의타입 Type05 으로지정하여저장합니다. 즉, percolator라는인덱스에.percolator라는타입으로쿼리를저장합니다. 도큐먼트 Document06 를색인할때먼저 percolator 요청을수행하고결과에따른처리를하는데, 이 percolator로요청하는과정이역검색입니다. 다음그림은 percolator의논리적인개념을보여줍니다. 그림 1-5 Percolator 생성과쿼리등록 Query({"query": ) 등록 percolator/.percolator 구현방법은요구사항에따라달라집니다. 첫번째방법은 [ 그림 1-6] 처럼발생한문서가등록한 percolator 쿼리에매치되는지질의한후결과에따라 Alert 처리를하거나색인 Indexing 을수행하도록구현합니다. 목적에따라서는둘다수행할수도있습니다. 두번째방법은 [ 그림 1-7] 처럼발생한문서를먼저색인한후 percolator 쿼리에매치되는지질의해서결과에따라 Alert를수행합니다. 05 도큐먼트타입은물리적인인덱스나저장소를가지고있지않다. 다만논리적으로단일인덱스에대한서로다른목적의데이터를구분하여저장하는방법으로사용된다. 데이터베이스관점에서보면테이블과유사하며, 내장필드인 _type에따라저장된다. 06 검색에서가장기본이되는데이터단위로, Elasticsearch에저장되는하나의아이템또는아티클 (article) 을말한다. 도큐먼트는 RDBMS에서테이블내하나의 row에해당한다. 1 검색기능확장 - 013
그림 1-6 Percolator 구현방법 1 Alert 문서 percolator/.percolator Indexing 그림 1-7 Percolator 구현방법 2 percolator/.percolator Alert 문서 Indexing 1.2.1 Percolator 생성 이번예제는온라인경매서비스에서입찰금액을실시간으로모니터링하기위한구성으로작성되었습니다. Percolator 쿼리를등록하기위한인덱스는 auction_bidding_percolate로생성하고 percolator 쿼리에매칭된도큐먼트는 auction_bidding_log로생성된인덱스에등록합니다. percolator 쿼리는총 4개고, 각쿼리에매칭된도큐먼트는 auction_bidding _log의타입에맞춰서유형별로등록합니다. 그림 1-8 Percolator index 1 auction_bidding_percolate auction_bidding_log 2.percolator 3 Query1 Query2 special _order over_price Query3 Query4 reserved _order auction_1 014 -
1 번은인덱스를의미합니다. 2 번은타입을의미합니다. Percolator 타입은.percolator 로생성됩니다. 3 번은도큐먼트를의미합니다. Percolator 는쿼리자체가하나의도큐먼트가됩니다. 다음두예제는경매서비스에서특정키워드와범위 (range) 조건을지정하고이조건과일치할때모니터링을수행합니다. 이쿼리는경매입찰시 special 과 order 라는두개의키워드가포함되어있을때해당입찰건을모니터링합니다. inner query key 영역 ("query" : "special order") 의값을 over price 와 reserved order 로변경하여키워드모니터링용 percolator를생성할수있게합니다. [ 예제 1. 키워드모니터링 ] { "query" : { "match" : { "bidding_keyword" : { "query" : "special order", "operator" : "and" 이쿼리는범위 (range) 모니터링예제로경매에 1 번참여할때입찰금액이 1 천만 원을초과한입찰건을모니터링합니다. [ 예제 2. 범위모니터링 ] { "query": { "bool": { "must": [ { "term": { "auction_id": 1 1 검색기능확장 - 015
], { "range": { "bidding_price": { "gt": 10000000 auction_bidding_log 인덱스에생성한타입들은특정조건과일치하는경매 입찰이들어왔을때해당이벤트를유형별로기록합니다. [ Percolator 인덱스와타입생성예제코드 ] Settings settings = Connector.buildSettings("elasticsearch"); Client client = Connector.buildClient(settings, new String[] {"localhost:9300"); String setting = ""; String[] mapping = new String[4]; setting = Operators.readFile("schema/percolate_settings.json"); mapping[0] = Operators.readFile("schema/percolate_mappings_special_order.json"); mapping[1] = Operators.readFile("schema/percolate_mappings_over_price.json"); mapping[2] = Operators.readFile("schema/percolate_mappings_reserved_order.json"); mapping[3] = Operators.readFile("schema/percolate_mappings_auction_1.json"); try { client.admin().indices().delete(new DeleteIndexRequest("auction_bidding_log")). actionget(); client.admin().indices().delete(new DeleteIndexRequest("auction_bidding_percolate")). actionget(); catch (Exception e) { finally { CreateIndexResponse createindexresponse = client.admin().indices().preparecreate("auction_bidding_log").setsettings(setting).addmapping("special_order", mapping[0]) 016 -
.addmapping("over_price", mapping[1]).addmapping("reserved_order", mapping[2]).addmapping("auction_1", mapping[3]).execute().actionget(); createindexresponse = client.admin().indices().preparecreate("auction_bidding_percolate").setsettings(setting).execute().actionget(); client.close(); 이벤트유형에따라등록되는타입은다음표와같습니다. 표 1-2 유형별등록타입 유형 special order over price reserved order 경매 1번의입찰가격이 1천만원을초과할때 타입 special_order over_price reserved_order auction_1 1.2.2 Percolator Query 등록 색인문서를필터링또는모니터링하려면검색쿼리를등록해야합니다. 여기서는경매입찰건의모니터링조건을 auction_bidding_percolate 인덱스의.percolator 타입에등록합니다. Percolator 쿼리는 XContentBuilder 와 JSON string의두가지방법으로등록할수있는데, XContentBuilder는 setsource(source), JSON string은 setsource(json) 으로등록합니다. Percolator 요청 (request) 조건이일치할때 auction_bidding_log의타입과일치시키기위해 percolator 쿼리등록시도큐먼트 id에 auction_bidding_ log 타입을등록합니다. 1 검색기능확장 - 017
다음코드는경매입찰시등록한키워드에 special 과 order 라는키워드가 모두포함되어있으면해당입찰에대해정의한액션을수행합니다. special order 는 over price 와 reserved order 로수정하여등록할수있게합니다. [ 예제 1. 등록코드 ] // XContentBuilder 이용. QueryBuilder querybuilder = QueryBuilders.matchQuery("bidding_keyword", "special order"). operator(operator.and); XContentBuilder json = jsonbuilder().startobject(); json.field("query", querybuilder); json.endobject(); // JSON string 이용 MatchQueryBuilder matchquerybuilder = new MatchQueryBuilder("bidding_keyword", "special order"); matchquerybuilder.operator(operator.and); String source = "{\"query\" : " + matchquerybuilder.tostring() + ""; client.prepareindex("auction_bidding_percolate", ".percolator", "special_order") //.setsource(source).setsource(json).execute().actionget(); 다음코드는범위에해당하는값을모니터링하여경매 1 번에입찰한금액이 1 천 만원을초과할때정의된액션을수행합니다. 예제코드는제공된소스코드를참 고하여생성하기바랍니다. [ 예제 2. 등록코드 ] TermQueryBuilder termquerybuilder = new TermQueryBuilder("auction_id", 1); RangeQueryBuilder rangequerybuilder = new RangeQueryBuilder("bidding_price"); rangequerybuilder.gt(10000000); querybuilder = QueryBuilders.boolQuery().must(rangeQueryBuilder).must(termQueryBuilder); json = jsonbuilder().startobject(); json.field("query", querybuilder); json.endobject(); client.prepareindex("auction_bidding_percolate", ".percolator", "auction_1").setsource(json) 018 -
.execute().actionget(); 1.2.3 Percolator 요청 Percolate 인덱스를생성하고쿼리등록까지끝났으니이제 percolator 요청를생성하여요청이어떻게동작하는지알아보겠습니다. 먼저경매입찰요청이입력되면등록된 percolator 쿼리로이요청을보내일치여부에따른액션을수행합니다. 다음은발생한이벤트에대한 percolator 요청을수행한후결과를획득하는예로, special order 와 auction_1 두가지경우를포함하고있습니다. 이예제의결과값에따른처리는 [ 응답후처리코드 ] 를참고하기바랍니다. [ 요청코드 ] PercolateRequestBuilder precolaterequestbuilder = new PercolateRequestBuilder(client); // 가상의경매입찰문서를생성합니다. DocBuilder docbuilder = new DocBuilder(); XContentBuilder jsondoc = jsonbuilder().startobject().field("auction_id", 1).field("bidding_id", 1).field("bidding_keyword", "special order").field("bidding_price", 200000000).endObject(); docbuilder.setdoc(jsondoc); // percolator request를보낸다. PercolateResponse percolateresponse = precolaterequestbuilder.setindices("auction_ bidding_percolate").setdocumenttype(".percolator").setpercolatedoc(docbuilder).execute().actionget(); 다음은두가지조건에대한 percolator 요청을수행한후결과에따른색인방 1 검색기능확장 - 019
법을보여주기위한예로, 색인결과는 [ 요청코드 ] 의 special order 조건에따 라 special_order 타입에, auction_id:1, bidding_price:200000000 조건 에따라 auction_1 타입에등록됩니다. [ 응답후처리코드 ] Match[] matches = percolateresponse.getmatches(); int size = matches.length; for ( int i=0; i<size; i++ ) { String doctype = matches[i].getid().string(); if ( "auction_1".equalsignorecase(doctype) ) { IndexRequestBuilder requestbuilder; IndexResponse response; requestbuilder = client.prepareindex("auction_bidding_log", doctype); response = requestbuilder.setsource(jsondoc).execute().actionget(); if ( "special_order".equalsignorecase(doctype) ) { IndexRequestBuilder requestbuilder; IndexResponse response; requestbuilder = client.prepareindex("auction_bidding_log", doctype); response = requestbuilder.setsource(jsondoc).execute().actionget(); 키워드모니터링을하려면 reserved order 와 over price 는 special order 와같은방법으로타입을매칭시켜처리하면됩니다. 020 -
1.3 Join Elasticsearch는정규화된데이터 Normalized data 를다루는 RDBMS와다르게비정규화된데이터 Denormalized data 로문서가구성되어서 RDBMS와같은조인기능을제공하기가쉽지않습니다. 여기서는조인기능을 Elasticsearch에서어떻게처리하는지알아보겠습니다. Elasticsearch에서는크게두가지방법으로조인기능을구현합니다. 애플리케이션단에서의조인 (Application-side Joins) 이방법은두개의테이블에서외래키 Foreign Key 정보를 N 관계에있는테이블에함께저장하여관계모델을구성합니다. Elasticsearch에서는 parent-child 관계를이용하는데, 한인덱스에 parent 타입과 child 타입을등록하여관계모델을구현합니다. 예를들어, 1의관계를정의하기위한 parent 타입으로 buyer 를생성하고구매자정보를저장하며, N의관계를정의하기위한 child 타입으로 buyer_item을생성하고구매자의구매이력을저장합니다. 그림 1-9 parent-child 관계모델 1 join_parent_child1n 2 3 buyer 1 _parent:buyer buyer_item N 1 번은 1:N 관계를포함하는인덱스입니다. 2 번은 parent 타입으로 1 의관계를가지며, child 타입의외래키값을포함합니다. 3 번은 child 타입으로 N 의관계를가지며, _parent.type 필드에 parent 타입을지정해야합니다. 비정규화데이터 (Denormalizing Data) 이방법은관계데이터를중복으로구성하여하나의테이블에모든데이터를등록 1 검색기능확장 - 021
하는것인데, 색인크기가증가한다는점을유의해야합니다. Elasticsearch에서는비정규화데이터를구성하기위해 inner objects와 nested 타입을제공하므로인덱스의스키마를구성하기위한매핑정보설정시관계데이터에대한필드를 object 타입또는 nested 타입으로정의하여색인시모든데이터를등록합니다. 그림 1-10 비정규화모델 Table : buyer Table : buyer_item Table : purchase_history Column Column Column buyer_id buyer_id (FK) buyer_id buyer_login_id item_id buyer_login_id buyer_name item_name buyer_name buyer_sex purchase_date buyer_sex buyer_phone item_price buyer_phone order_price item_id item_name purchase_date item_price order_price NOTE Field Collapsing 앞의두가지방법외에추가로두방법을기반으로 aggregation을적용하여조인기능을구현하는방법이있습니다. child 타입에 aggregation하기위한최소정보를 parent 타입에서가져와색인시함께등록하고, 질의시 aggregation 질의를통해결과를가져오는방법인데, 일반적인화면구성을위한결과로사용할수없으므로여기서는간단히소개만했습니다. 1.3.1 Parent-Child parent-child 타입은반드시같은인덱스에생성해야하고, 서로다른인덱스 생성해서사용할수없습니다. 또한, 인덱스단위로선언하는것도불가능합니다. parent 타입은반드시기본키 primary key 역할을하는 _id 값을지정해야하는데, child 타입에서문서를등록할때이값을 _parent 필드의외래키로반드시설 022 -
정해야합니다. child 타입을정의할때매핑설정에서 _parent.type 값은앞에서생성한 parent 타입명으로지정해야만정상적으로 parent-child 타입을사용할수있습니다. parent type : buyer 구매자의기본정보또는메타데이터를저장하며, 구매자한명의정보는고유합니다. child type : buyer_item 구매상품에구매정보를저장하며, 구매자별로복수개의구매정보가있습니다. Elasticsearch에서는 parent-child 기능구현을위해다음두종류의 API를제공합니다. has_parent 쿼리 / 필터이 API는 parent 도큐먼트에질의하고 child 도큐먼트를반환합니다. has_child 쿼리 / 필터 이 API 는 child 도큐먼트에질의하고 parent 도큐먼트를반환합니다. 다음예제로 parent-child 타입생성과구성을확인해보겠습니다 ( 전체코드는제 공된소스코드 07 를참고하기바랍니다 ). [ Parent-Child 인덱스생성 - settings ] "settings" : { "number_of_shards" : 3, "number_of_replicas" : 0, "index" : { 중략 07 http://bit.ly/1zhvfob 1 검색기능확장 - 023