참고 사이트
Elasticsearch_dsl 공식홈페이지
Stackoverflow
Elasticsearch : Single-node로 구현
1. 개요
세번째 프로젝트 과정에서 채용중인 기업 중 인기있는 기업을 순서대로 나열해서 보여주기 위한 실시간 검색 순위를 구현할 필요가 생겼다.
장고로 부터 로그데이터를 수집하여 엘라스틱서치에 넣고, 이를 python 환경에서 elasticsearch_dsl 모듈을 활용하여 다시 집계 후, 실시간 검색 순위를 구현하도록 했다.
나는 실시간 검색 순위를 최근 인기있는 채용기업을 표시해주는 용도로 사용할 것이기 때문에 어떤 검색어를 입력했는지를 집계하는 대신에, 검색후 어떤 기업을 선택하여 상세정보를 확인했는가에 대한 로그 정보를 활용해서 실시간 인기 기업 순위를 만들었다.
로그는 유저가 특정 기업정보를 클릭하기위해 누를 때, 그 사용자의 정보뿐만아니라 기업의 이름과 주소를 로그로 수집하도록 했고 이를 통해 실시간 인기 기업 순위를 표시할 때, 기업의 이름과 간략한 주소를 표기하도록 했다.
시간범위는 서비스에서는 안정성을 위해 1주일 간격의 집계를 보여주는 방식으로 사용했지만, 현재 시간을 기준으로 7일전의 내용을 집계하는 방식이기 때문에 실시간으로 순위가 변화하는 모습을 확인할 수 있었다.
2. 구현 과정
우선 엘라스틱서치와 내 로컬 파이썬 환경에서 connection을 만들어줘야한다. 그리고 유저가 직접 클릭해서 기업 상세 정보를 확인한 로그를 가지고와서 기업명 기준으로 grouping 한다.
시간 범위는 1주일로 설정하고 로그 데이터가 저장되어 있는 인덱스를 지정해 준다. 필요하다면 doc_type도 별도로 지정해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
from elasticsearch_dsl import connections
from elasticsearch_dsl import Search, Q
client = connections.create_connection(hosts=['http://{host-ip}:9200'], http_auth=('es-id', 'es-password'))
s = Search(using=client)
body = {"size": 0, "aggs": {"by_corp": {"terms": {"field": "result_corp_nm.keyword", "size": 5},
"aggs": {"addrs": {"top_hits": {"_source": ['result_corp_addr']}}}
}}}
s = Search.from_dict(body).filter("range", **{"@timestamp": {"gte": "now-7d", "lte": "now"}})
s = s.index("targetlog-*")
s = s.doc_type("target_log")
elasticsearch_dsl을 사용하여 elasticsearch 와 연결할때는 기본적인 설정을 다하고 난후, execute()를 반드시 해줘야 한다. 이 자체로 해당 조건에 맞는 내용을 검색해달라고 요청을 보내는 일이기 때문이다.
execute를 실행할때 변수를 지정해주면, 객체형태로 그 응답이 전달되는데 아래의 코드에서는 t라는 변수에 그 응답을 담았다. 응답된 객체는 일련의 리스트 형태로 도큐먼트를 담아오는데 각 도큐먼트들은 반복문을 사용하면 하나씩 빼낼 수 있다.
최종 랭킹 정보는 json형태로 장고 템플릿에 렌더링하기 위해 배열안에 딕셔너리를 담는 형태로 구성했다.
1
2
3
4
5
6
7
8
t = s.execute()
corp_rank_list = []
for item in t.aggregations.by_corp.buckets:
dic = {}
dic['corp_nm'] = item.key
addr_split = item.addrs.hits.hits[0]._source.result_corp_addr.split(' ')
dic['addr'] = addr_split[0] + " " + addr_split[1]
corp_rank_list.append(dic)
3. 느낀점 및 보완점
내가 ELK파트를 전담하면서 검색기능과 실시간 순위를 구현했지만 검색엔진의 성능에 쉽게 만족하기 어렵다는 것을 절실히 느꼈다.
nori tokenizer와 synonym filter를 사용해서 어느정도 성능을 끌여올렸음에도 불구하고 한글 검색 성능을 높히는 건 한계가 보였기 때문이다. (물론 처음 ELK를 다뤄본 입장에서, 그리고 해당 프로젝트를 진행하는 기간 안에서 성능을 끌어올리기 어렵다는 의미다.)
- 영어로 된 기업명이 한글로 기업명이 적혀있다던지 : LG -> 엘지
- 사실 이 부분은 동의어 처리로 대부분 해결하였다.
- 검색의 대상이 되던 기업의 수가 너무 과하게 많아 굳이 필요없는 검색어 까지 나온다던지 : 삼전과 삼성전자를 동의어로 설정을 해놨더니 삼전이라는 이름이 들어가는 수많은 기업들이 나온다던지
- 실제로는 대다수가 원하고 검색하였을 기업들만 놔두고 나머지를 모두 잘라내는 것이 생각보다 훨씬 곤란했다.
- 형태소 분석이 불완전 한다던지 : 받침을 자꾸 별개의 형태소로 구분하는 경우들이 종종 있었음
- 이건 끝까지 해결하지 못했다.
또한 실시간 검색어라는 것은 단순히 검색창에 어떤 검색어가 많이 입력됐는지만을 보여주는 것이라 생각하지 않는다. 사람들이 어떠한 검색어를 통해, 어떠한 정보를 실제로 얻고 싶었는지를 종합적으로 보여줘야한다고 생각하기 때문이다. 그러한 점에서 이번 프로젝트 안에서 구현했던 기능은 반쪽짜리라고 평하고 싶다. 만약 추후에 이 파트를 다시 건들 일이 있다면, 실제적으로 어떤 검색어가 최종적으로 무엇과 연결되어 있는지를 파악하고 이를 연관지어 실시간 검색어로 보여줄 수 있도록 구현해보고 싶다는 생각이든다.
하지만 일단 다음포스팅의 주제는 Elastic에서 제공해주는 search app이라는 기능을 통해 검색기능을 구현하는 것일 듯하다. 자체적으로 로그를 찍어주고, 검색어 자동완성 기능, 검색기능이 구현된 기본 UI등을 제공해주기 때문이다. 이를 위해 elasticsearch를 cluster로 구축하고 search app에서 나오는 로그들을 logstash, kafka등을 이용해 수집하고 분석까지 해보는 경험을 해보려한다.(구체적인 아키텍쳐는 다음 포스팅에서 쓰겠다.)