카카오페이지 검색 개발 후기

2018-04-29

elasticsearch 의 검색을 사용하기 까지

사내에서 elasticsearch 를 처음 사용하면서 로그 수집정도에만 사용하고 있었다. 워낙에 elk 가 서로 잘 작동하기도 했고 데이터 수집부터 분석까지 너무나도 유려한 기능들을 갖추고 있었다. 서비스에서의 검색을 처음 도입해본 경험은 사내 정산 시스템을 만들면서 였는데, 작품이름 검색이나 작가등의 이름으로 검색을 하게 해달라는 요청이 있어서 였다. 데이터가 db 에 있어 단순하게 like 검색으로 구현할까 했었는데, 생각보다 결과에 대한 품질이 너무 낮았다. 결과에 대한 점수 부여 방법도 딱히 없고 애초에 조건이 하나만 걸리니 내가 원하는 결과를 얻을 수 없었다. 그래서 안되겠다 싶어 elasticsearch 와 은전한닢을 이용해 구현했고, 거기서 자신감이 생겨 서비스의 검색도 과감하게 elasticsearch 로 구현하자고 주장했다. 처음에 회사에서는 반기는 분위기는 아니였다. 기존에 (나름 잘) 동작하는 검색이 구현되어 있었고 한번도 도전해보지 않은 검색이라는 서비스를 잘 구현할 수 있을까에 대한 걱정이 있었다. 그리고 이미 카카오에서 제공하는 검색엔진이 있고 이를 이용하면 우리쪽에서 굳이 만들 이유가 없어서 였다. 카카오에서 제공하는 기능들을 이용하기만 해도 되었지만 앞으로 우리가 우리만의 기술을 갖게 된다면 그 자체로 의미가 있다 생각했고, 결과로 보여드려야겠다고 생각해서 나는 카카오페이지의 검색을 만들어 보기로 했다.

기존 구성

나는 일단 기존의 검색이 어떻게 동작하고 있는지 부터 분석을 해보기로 했다. 지정된 시간마다 단어들을 수집해서 잘게 쪼갠 뒤 테이블에 데이터를 저장해놓고, 실제 서비스에서는 나름의 공식을 이용해 점수를 부여해서 검색 결과를 내어주는 방식이었다. mysql 의 fts 를 이용한 방식이었는데 나름 속도도 괜찮고 완벽하진 않았지만 검색의 결과도 그리 나쁘지는 않았다. 처음에는 이 검색 서비스부터 이겨보자는 생각으로 다른 검색 서비스들이 검색을 어떻게 서비스 하고 있는지 부터 관찰했다. 개발 초기에는 리디북스레진코믹스 같은 사이트들의 검색을 많이 참고했다.

은전한닢

한글 형태소 분석기는 크게 3가지가 있다. elasticsearch 에서 제공하는 형태소분석기 분석에서 보면 arirang, 은전한닢 그리고 open-korean-text(구 twitter-korean-text) 등이 있는데 각 장단점들이 명확해서 아직까지는 형태소 분석기의 절대적인 강자는 없는듯 하다. 각자 자신에게 맞는 형태소 분석기를 사용하면 되는듯 한데, 리디북스에서 은전한닢을 사용하다는 기술 블로그를 보고 생각의 결과가 많이 굳기도 했지만 음절 분석결과가 가장 자세하고 속도도 괜찮은 은전한닢을 사용하기로 했다.

클러스터 구성

2대의 노드로 클러스터를 구성해서 검색 색인을 하고 있고, 주기적으로 추가/변경되는 정보들을 인덱스에 반영하고 있다. 추가적으로 실제 서버 데이터와 개발에서의 구성도 elasticsearch 로 구성했는데, 테스트 서버군은 하나의 노드로 구성했고 각 테스트군별로 색인을 따로 구성했다.

데이터 수집및 정제

검색 쿼리만큼이나 중요한 부분이 데이터 수집이다. 어찌보면 색인이 얼마나 잘 되어 있느냐에 따라 검색 결과의 품질이 좌우된다. 색인과 검색의 비율을 따져 생각해본다면 6:4 정도라고 생각한다. 아무리 많은 데이터를 사용한다 하더라도 데이터가 잘 저장되어 있지 않으면 아무런 소용이 없기 때문이다. 일단 데이터를 주기적으로 수집하기로 했고, 카카오 검색팀의 조언을 받아 단어에 대한 점수에 대한 결과도 함께 수집을 한다. logstash 로 처음에는 데이터를 수집하려고 했는데 점수부여에 대한 로직이나 데이터 정제의 편의성을 위해 배치작업을 데이터를 수집했다. 간략하게 수집되는 정보들을 보면 다음과 같다.

  • 열람수, 구매자수, 인기도, 기다리면 무료의 대한 점수 계산
  • 랭킹데이터 수집
  • (정산 시스템 구축의 경험으로) 계약 주체에 따른 점수 부여
  • 운영자가 등록한 태그 수집
  • 작품명, 작가명, 발행자명, 출연진등의 정보 수집

가능한 모든 정보를 수집하고자 했고, 은전한닢으로 기분석된 단어와 순수한 단어를 따로 인덱싱을 했다. 이유는 나중에 검색부분에도 언급하겠지만 한글 형태소 분석기로 분석된 단어와 그렇지 않은 단어를 함께 검색 쿼리에 넣어 검색의 결과를 높이기 위함이다. 형태소 분석기를 통해 토크나이징을 하게 되면 자체적으로 단어를 쪼개게 되는데, 이 때 원치 않게 단어가 나뉠수도 있기 때문이다. 예를 들어 김비서 이라는 단어를 형태소 분석기에 넣고 돌리면 엉뚱한 결과가 나온다.

김비서
김비	NNP,인명,F,김비,*,*,*,*
서	JKB,*,F,서,*,*,*,*
EOS

일반적으로 생각했을 때 , 비서 로 나올거라 예상했지만 결과는 이와는 달리 김비, 로 나뉘게 되버린다. 원인은 제공되는 정보가 부족해서인데, 모든 문장을 써주면 정상적으로 표시 된다.

김비서가왜그럴까
김	NNP,인명,T,김,*,*,*,*
비서	NNG,*,F,비서,*,*,*,*
가	JKS,*,F,가,*,*,*,*
왜	MAG,문장부사/양상부사,F,왜,*,*,*,*
그럴까	VA+EC,*,F,그럴까,Inflect,VA,EC,그렇/VA/*+ᆯ까/EC/*

이런식의 토크나이징은 검색에서도 문제가 된다. elastisearch 에서 검색을 태우면 색인되어 있는 타입에 따라 기분석해서 나온 토큰들로 검색을 하게 되는데 사용자가 김비서 라고 검색을 하게 되면 라는 토큰이 나오게 되기 때문에 엉뚱한 검색 결과가 나오게 된다. 추가적으로 난감했던 단어는 하라간 과 같이 사전에 등록되지 않은 고유명사들의 경우였다. 은전한닢은 세종 말뭉치를 사용하고 있는데, 비교적 최근에 추가되는 신조어들이나 작품에서 의도적으로 사용되는 고유명사들에 대한 데이터는 존재 하지 않는다. 하라간 의 경우엔 형태소 분석기에서는 아예 알아들을 수 없는 단어가 되어버린다.

하라간
하	VV,*,F,하,*,*,*,*
라	EC,*,F,라,*,*,*,*
간	NNG,*,T,간,*,*,*,*
EOS

(...여기서 멘붕이 왔었다.) 임시방편으로 사용했던 방법은 mecab 에서 제공하는 사용자 사전이었는데, 인명이나 지명등의 단어를 넣으면 형태소 분석기에서 의도적으로 단어를 쪼개지 않고 단어 그자체로서 인식을 가능하게 한다. 하지만 4만개가 넘는 모든 작품들을 모니터링하고 일일히 결과를 들여다보면서 사용자 사전에 필요한 단어들을 넣는건 나 혼자 작업으로는 불가능했었고, 처음에는 사용자 cs 나 내부적으로 결과가 이상한 단어들에 대해서만 사전에 등록했었다. (사용자 사전은 나중에 좀 더 자세하게 이야기 해보자.)

데이터 색인

데이터는 항상 추가된다. 하지만 판매중이었다가 여러 이유로 판매가 중지되는 작품도 있고 운영자에 의해 작품의 정보가 변경되는 일도 꽤나 잦았다. 이미 존재하는 필드에 값을 갱신하려고 했더니 경우의 수가 너무 많았다. 모든 상황에 대해 신시간으로 대응하기 위해서 다른 곳에서 아이디어를 얻었는데 elasticsearch 에서 제공하는 alias 와 일반적인 blue-green deploy 방법을 이용해서 모든 데이터를 주기적으로 새로 갱신하는 방법을 이용했다. index-blue, index-green 를 하나씩 생성하고 index 라는 alias 를 걸어 실제 검색 쿼리는 index alias 를 바라보게 했다. 데이터 수집 배치는 독립적으로 동작하고 있기에 실시간으로 필드를 추가하는 장점도 함께 가져갈 수 있다.

검색 쿼리

초기의 버전에서는 사용자 사전을 최대한 사용하지 않는 방향으로 진행했었다. 위에서 언급한 바와 같이 모든 고유명사들을 수동으로 쪼개는 작업이 물리적으로 불가능했었기 때문인데, 이런 제한을 극복하고자 최대한 검색 쿼리를 세부적으로 쪼개서 검색의 결과를 높이고자 했다. mapping 에서 title 에 관련된 정보는 다음과 같이 구성했다.

"title": {
    "type": "text",
    "analyzer": "seunjeon_analyzer"
},
"titleBigram": {
    "type": "text",
    "analyzer": "han_bigrams"
}
"titleStandard": {
    "type": "text",
    "analyzer": "exact"
}

검색 쿼리를 수행시 3개의 필드에 대해 각기 다른 boost 를 줘서 가장 근접한 결과에 대해 점수를 높게 부여해서 결과를 만들었다. bigram 을 사용한 이유는 앞서 언급한 형태소 분석기의 한계를 대체하기 위해 색인되는 단어들을 ngram 으로 조개어 저장해서 엉뚱한 결과가 나오는 것에 대한 대처였다. (나중에 다시 언급하겠지만 결과적으로는 bigram 을 사용하지 않게 되었다.) MatchQuery 로 검색을 하는데 위에서 언급한 대로 기분석된 색인이 엉뚱하게 저장되는 경우들이 꽤나 빈번했기 때문에 초기 검색결과는 가히 충격적이었다. 왜 이 단어가 검색이 되었지? 하는 결과들이 포함되어 있었기 때문이었다. 이후에 카카오 검색셀의 조언을 받아 쿼리는 대대적으로 튜닝하게 되었다.

elasticsearch 버전 관리

개발 초기에는 elasticsearch 최신 버전이 5.1.1 이었다. 검색 서비스를 라이브에 반영하게 되고 보니 버전은 6.x 까지 올라가버렸다. 요즘 추세인지는 모르겠지만 어째 버전업이 너무나도 빠르게 진행되고 있었다. 이전 포스트에서도 얘기 했었던 elasticsearch 기술 지원 때문에라도 elasticsearch 버전을 주기적으로 (꽤나 빈번하게) 올리기도 했다. rolling upgrade 에 대한 내용은 여기에서 자세하게 다루었다.

쿼리 튜닝

사용자 사전 없이 title, titleBigram, titleStandard 만들어 검색쿼리를 날리면 정말 검색 결과가 엉뚱하게 나오는 경우가 있다. 위에서도 언급한 토크나이징 때문인데 이는 사용자 사전 없이는 사실상 불가능 하다. 사용자 사전을 추가하고 난 후에는 bigram 은 사용하지 않고 title, titleStandard 두 필드를 사용해서 MatchQuery 를 날린다. bigram 도 결국에는 단어를 ngram으로 추출하는 방식인데 근본적인 해결책이 되진 못하기 때문이다. 그리고 FunctionScoreQuery, FilterFunction 를 이용해서 결과에 대한 점수 부여를 추가했다. 점수 부여에 관한 로직은 카카오 검색셀(hao) 의 도움을 받아 구현했는데, 위에서 데이터 수집당시에 점수를 부여할 수 있는 판매량이나 열람수와 같은 정보를 이용해서 내부적인 공식을 적용해서 점수를 부여했다. 쿼리 튜닝이라고는 했지만 결과적으로는 인덱싱할 때부터 데이터를 잘 만드는게 큰 도움이 되었다. 잘 만들어진 데이터가 있어야 좋은 검색이 나올 수 있기 때문이다.

사용자 사전 관리

내가 의도한대로 토크나이징이 되기 위해 결국엔 사용자 사전을 추가하게 되었다. 또한 신조어들의 대한 대응을 하기 위함이었는데, 예를 들어 레벨업 이라는 단어가 포함된 작품을을 검색할 때 기존에는 레벨 으로 쪼개버린다.

레벨업
레벨	NNG,*,T,레벨,*,*,*,*
업	NNG,*,T,업,*,*,*,*
EOS

이 경우에는 이라는 단어가 포함된 작품들도 함께 결과에 포함되기 때문에 전혀 엉뚱한 작품들이 포함되기도 한다. 레벨업 이라는 단어를 사전에 고유명사로 등록해서 최대한 엉뚱한 결과가 없게끔 했다. 그렇다면 전체 작품에 대해 어떻게 원하는 고유명사를 추출할 수 있을까? 이 고민에 대한 해결책은 기존의 검색용 수집 데이터를 사용했다. 운영자가 작품을 등록할 때 태그 를 함께 등록하는데 이는 기존에 검색에서 사용되었던 검색 태그들이다. 김비서가 왜그럴까 같은 경우엔 김비서김명미 등의 태그들을 걸어두어서 기존의 검색에서 사용할 수 있는 데이터를 제공했었다. 이 데이터를 약간 손봐서 사용가능한 단어들에 대해 사용자 사전에 등록해서 완벽하진 않지만 신조어나 고유명사들에 대한 대응을 했다. 데이터가 없는 경우에 뉴스기사나 다른 데이터를 학습해서 토크나이징을 할 수 있는 soynlp 같은 훌륭한 오픈소스도 참고할까 싶었지만 개발 시간에 대한 한계도 있었고(핑계지만), 검색 결과에 대해 스스로 보장하기 어렵다는 이유로 도입을 하진 않았다. 나중에 여유가 되면 학습을 통한 토크나이져를 추가해볼까도 고민중인 부분이다. 추가적으로 구글에서는 자연어분석 서비스를 내놓았다. 어쩌면 가까운 미래에는 한글 형태소 분석기가 없어도 한글 검색이 가능해질지도 모르겠다.

결론

기술적인 이야기를 거의 하지 않았더니 개발일기 처럼 되어 버렸다. 이제 곧 웹에서도 검색기능이 들어갈 예정이니 앞으로는 검색을 좀 더 편하게 할 수 있을것이다. 아직은 좀 더 가다듬고 완성도를 높히는데 목표를 두어야 할 것이다. 약 1년동안 개발했던 내용을 몇문장으로 압축해서 쓰려고 하니 하고 싶은 말은 많은데 모두 다 담지 못한 부분도 있다. 나중에 더 생각 나는 부분들에 대해서 좀 더 내용을 써봐야 겠다.