Oct 10 2011
Sunspot과 Lucene을 이용한 풀 텍스트 검색엔진 구축하기
안녕하세요? Thinkreals의 @belhyun입니다. 이번 포스팅에서는 ruby on rails 환경에서 sql의 like 검색을 대체할 새로운 검색방법을 소개하겠습니다. 소개하기 전, like 검색의 단점을 알아보겠습니다. like 검색은 시간소모가 큰 문제를 가지고 있습니다. 데이터의 양이 많지 않다면 like 검색을 사용해도 아무런 문제가 없겠지만, 만약 데이터가 몇 만, 몇 십만이 될 정도로 커진다면 like 검색은 빠른 속도를 보장해 줄 수 없습니다. 그렇기 때문에 대량의 데이터를 가지고 서비스를 하고 있다면 반드시 다른 검색방법이 필요하게 됩니다. 이런 문제점을 해결하기 위해서 다양한 방법이 사용되고 있지만, 그 중 가장 접근하기 쉽고 사용 가능한 것은 lucene을 활용하는 것입니다. lucene은 자바를 이용하여 검색을 위한 인덱스를 작성하게 해주고 검색을 가능하게 해주는 풀 텍스트 검색엔진입니다. 여기서 생소한 용어가 보입니다. 바로 ‘인덱스를 작성한다’라는 표현입니다. 먼저 lucene을 이해하기 전에 lucene같은 검색엔진을 사용했을 때 검색이 어떤 식으로 이루어지는지부터 알아보겠습니다. 검색은 크게 인덱싱 작업과 검색 작업으로 나누어 집니다. 인덱싱 작업은 책 가장 앞에 있는 인덱스 페이지를 생각하시면 됩니다.

위의 그림은 기본적인 인덱스 구조를 나타낸 그림입니다. 예를 들어, ‘오늘 날씨 좋다’라는 문장이 있을 때, 이 문장에서 ‘오늘’이라는 키워드는 1, 3, 5번 문서에 있고 ‘날씨’라는 키워드는 1, 2, 3번 문서에 있습니다. 만약 ‘날씨’라는 키워드로 검색을 할 경우 일반적인 like 검색을 사용할 때에는 전체 1번부터 5번까지의 문서 전체에 대해서 ‘날씨’라는 키워드가 들어있는지 그렇지 않은지 비교해야 하지만, 이렇게 전체 문서에 대해서 인덱싱 파일을 이용한다면 단지 ‘날씨’라는 키워드에 대해서 1, 2, 3번 문서만 찾으면 원하는 검색결과를 얻을 수 있게 됩니다. 이런 식으로 검색이 이루어지기 때문에 일반적인 like 검색에 비해 훨씬 빠를 수 있는 것입니다. 다음으로 생길 수 있는 궁금증은 어떤 식으로 문장에서 색인어를 추출하는지에 대한 것입니다. 예를 들어 검색의 대상이 되는 문서들이 굉장히 많다고 가정해 보겠습니다. 이렇게 많은 문서 안에 있는 텍스트 중 의미가 있는 색인어(위 예에서는 ‘오늘’, ‘날씨’, ‘좋다’)를 어떻게 추출할건지에 대한 것이 중요한 이슈로 부각될 수 있습니다. 그렇기 때문에 lucene에서는 여러 색인어 추출법을 사용하고 있습니다. 대표적으로 2가지만 알아보겠습니다. 첫 번째 방법은 띄어쓰기로 색인어를 추출하는 방법입니다. 예를 들어 ‘오늘은 날씨가 시원합니다.’라는 문장이 있을 때 위와 같은 방법을 사용하게 된다면 추출되는 색인어는 ‘오늘은’, ‘날씨가’, ‘시원합니다’가 되게 됩니다. 이런 색인어를 바탕으로 lucene은 문서의 텍스트를 검색해 인덱스 파일을 만들게 될 것입니다. 여기서는 다음과 같은 문제가 생길 수 있습니다. 만약 사용자가 ‘오늘은’이라는 키워드로 검색을 하지 않고 ‘오늘’이라는 키워드로 검색을 하게 된다면 lucene은 정확한 검색결과를 줄 수 없게 됩니다. 왜냐하면 띄어쓰기 기반의 색인어 추출에서는 ‘오늘은’과 ‘오늘’은 전혀 다른 의미가 되기 때문입니다. 그렇기 때문에 이러한 띄어쓰기 기반의 색인어 추출은 많이 사용되지 않고 있습니다. 이러한 단점을 극복하기 위해서 사용되는 것이 형태소 기반의 색인어 추출입니다. 형태소 기반의 색인어 추출은 자연어처리 기법을 도입하고 있습니다. 위의 예를 통해서 알아보겠습니다. ‘오늘은 날씨가 시원합니다.’라는 문장에서 ‘은’, ‘가’와 같은 조사들은 전혀 의미가 없는 것들입니다. 그렇기 때문에 이런 조사들이나 ‘니다’와 같은 서술어는 색인어로 추출하기에는 전혀 의미가 없는 것들입니다. 이렇게 자연어 처리에 기반해서 색인어를 추출해야 하기 때문에 일반적인 분석기와는 다른 한글에 특화된 형태소 분석 기반의 추출기가 필요하게 됩니다. 이번 과제를 수행하기 위해서 다양한 형태소 분석 기반의 추출기를 찾아봤고, 본 과제에서 사용한 것은 이수명씨가 개발한 ‘한글 형태소 분석기‘입니다. 한글의 문법과 단어에 맞게 분석기가 구성되어 있기 때문에 한글에 대한 색인어 추출에서 큰 이점을 얻을 수 있었습니다. 다만, 최신의 동의어, 단어 등은 2010년 5월 이후로 업데이트가 되어있지 않기 때문에 반드시 재 수정이 필요하다고 생각됩니다. 이러한 한글 형태소 분석기를 사용한다면 ‘오늘’, ‘날씨’와 같은 단어들이 색인어로 추출될 것입니다. 지금까지 인덱스 파일의 구조와 색인어 추출에 대해서 알아보았습니다. 여기서 하나 짚고 넘어가야 할 것이 있습니다. 위 그림에서 색인어에 연결된 문서들은 어떤 식으로 연결되는지에 대한 것입니다. 일반적으로 생각하기에, 색인어가 문서에서 많이 등장한 문서가 가장 먼저 나와야 할 것이라고 예상할 수 있습니다. 맞습니다. 하지만 이러한 방법 이외에도 다른 방법이 하나 더 있습니다. 이 두 가지 방법을 정리해 보도록 하겠습니다.

TF는 문서 당 색인어 빈도 수를 의미합니다. 즉 문서 당 색인어가 얼만큼 많이 나타나는지에 따라 문서를 정렬하는 것입니다. 두 번째 방법은 DF, 즉 색인어 당 문서 빈도입니다. 이 방법에서는 색인어 당 문서의 빈도를 나타내는 척도입니다. 일반적으로 생각해 봤을 때, TF가 높으면 그 문서는 색인어를 대표하는 문서일 확률이 높을 것이고, DF가 낮으면 그 문서는 색인어를 대표하는 문서일 확률이 높을 것입니다. 지금까지 인덱스에 대해서 봤습니다. 그럼 실제로 검색은 어떤 식으로 이루어지는지 보도록 하겠습니다. 검색은 실제로 인덱스 파일을 참조하여 이루어지게 됩니다. 이러한 검색에서 다양한 옵션을 줄 수 있습니다. 검색결과의 제한, 랭킹부여, 검색 키워드에 대한 하이라이팅 처리와 같은 다양한 작업을 할 수 있습니다. 위에서 언급한 대표적인 세 가지 옵션만 간략히 알아보도록 하겠습니다. 검색결과를 제한하는 것은 검색결과를 필터링 하는 것입니다. 예를 들어, ‘오늘’이라는 검색어로 1, 3, 5번 문서가 검색결과로 나왔다면 이 중 3번 문서가 오늘 작성된 것이고 필터링 옵션이 작성된 날짜가 오늘인 문서는 제외한다라는 옵션을 주게 된다면 3번 문서를 제외한 검색결과를 얻게 되는 것입니다. 두 번째는 랭킹을 부여하는 것입니다. 예를 들어 똑같은 검색결과에 대해서도 제목에서 검색어가 나타날 수도 있고 문서의 내용에서 검색어가 나타날 수도 있습니다. 만약 제목에서 나타난 것에 대해서 더 높은 점수를 주어 그 검색결과를 앞에 나오게 하고 싶다면 제목에 더 높은 점수를 부여해서 검색결과 중 제목에 검색 키워드를 포함한 것이 더 높은 우선순위를 갖게 되고 앞에 나오게 됩니다. 마지막은 하이라이트입니다. 하이라이트는 검색결과로 나온 문서에서 검색 키워드에 대해서 하이라이팅 효과를 주는 것입니다. 이렇게 검색에 있어서도 다양한 옵션을 사용해서 검색을 다양화 할 수 있습니다.
그럼 본격적으로 ruby on rails와 lucene, 한글 형태소 분석기를 사용해서 어떤 식으로 검색엔진을 연동할 수 있는지 알아보도록 하겠습니다. 먼저 SOLR에 대해서 알아보도록 하겠습니다. SOLR는 lucene의 http 요청을 가능케 하는 웹 서버입니다. SOLR는 lucene을 내장하고 있기 때문에 SOLR에 대해서 http 요청을 하게 되면 lucene과 똑 같은 결과 값을 얻을 수 있습니다. 단 결과값들은 모두 xml 포맷에 맞춰 전송됩니다. 이렇게 검색서버인 SOLR를 갖추었다면 다음으로 필요한 것은 한글 형태소 분석기를 추가하는 것입니다. 이 과정은 루씬 한글분석기 오픈소스 프로젝트 카페(http://cafe.naver.com/korlucene)에 자세히 명시되어 있습니다. 이렇게 SOLR를 구축한 뒤에 해야 할 것은 실제로 rails app과 연동하는 것입니다. 이를 위해서 사용한 것이 sunspot이라는 gem입니다. sunspot은 SOLR 검색엔진과 rails app을 연동해주는 gem입니다. 간단한 사용만으로도 쉽게 SOLR와 rails app을 연동할 수 있습니다.
위의 그림은 sunspot의 인덱싱 과정을 나타내주는 그림입니다. 먼저 sunspot은 사용자가 model에 정의한 searchable 메소드 안에 있는 인덱싱할 대상들을 인지하게 됩니다. 그리고 그것을 xml 형태로 SOLR에 전달하게 됩니다. SOLR는 내장되어 있는 lucene을 통해서 그것을 인덱싱하고 인덱싱한 결과 값을 SOLR의 data 디렉토리에 저장합니다. 이런 일련의 과정들을 sunspot이 처리해 주기 때문에, 단지 searchable 메소드 안에 자신이 인덱싱할 정보들만 기재하면 됩니다.
다음은 검색하는 과정을 나타냅니다. rails의 컨트롤러에서 검색 키워드를 받아 그 키워드를 SOLR에 전달합니다. SOLR에서는 인덱싱 한 값들을 바탕으로 검색된 문서(튜플)에 관한 정보들을 rails에 xml형태로 전달하고 rails에서는 그것을 파싱하여 검색결과를 출력해 주게 됩니다. 이런 일련의 과정을 모두 sunspot에서 처리해 주기 때문에 해야할 것은 단지 컨트롤러에서 검색 키워드를 처리해주는 작업입니다.
sunspot을 사용하기 위해서는 모델에 인덱싱 할 대상을 설정해 주어야 합니다. 다음의 예를 통해서 설명드리겠습니다.
만약 article이라는 모델이 있고, 그 모델의 attribute가 name과 content가 있다고 가정해 보겠습니다. sunspot을 사용하기 위해서는 모델에 위와 같이 명시해 주어야 합니다. text 로 명시된 것은 인덱싱할 대상입니다. 일반적으로 테이블의 attribute의 이름과 똑같이 쓰게되면 그것에 대해서 인덱싱 하게 됩니다. 하지만 그렇다고 해서 반드시 searchable 메소드 안에 테이블의 attribute와 똑같은 이름을 명시할 필요는 없습니다. 예를 들어, text :myname이라고 지정하고 myname에 대한 정의부만 있게 되면, 그것 역시 인덱싱의 대상이 될 수 있습니다. 또한 text라고 명시된 것만이 인덱싱의 대상이 되며, integer, string과 같은 것은 검색결과를 제한하기 위해서 사용하게 됩니다. 예를 들어, 만약 검색 결과 중 오늘 작성된 글만 보고 싶다고 하면 제한을 하기위한 것을 searchable안에 정의하면 됩니다. 이외에도 searchable안에 다양한 방법으로 인덱싱할 대상을 지정할 수 있습니다. 구체적인 내용은 https://github.com/sunspot/sunspot에 기술되어 있습니다.
위의 코드는 컨트롤로에서 명시해 주는 내용입니다. 그저 단순히, searchable 메소드가 정의된 모델에 search메소드를 호출하고, 검색할 키워드를 fulltext 메소드의 파라미터 값으로 넘겨주는 것만으로 @search에 검색된 결과 값을 얻을 수 있습니다. 또한 search 메소드 안에서 검색결과의 제한, 하이라이팅 추가와 같은 다양한 옵션을 활용해서 검색결과를 새롭게 구성할 수 있습니다. 이와 관련된 내용은 역시 https://github.com/sunspot/sunspot에 자세히 명시되어 있습니다.
또한 sunspot을 이용하여 has_many, has_one과 같은 관계를 맺고 있는 릴레이션에 대한 처리도 가능합니다. 이 또한 https://github.com/sunspot/sunspot에 자세히 명시되어 있습니다.
그럼 실제로 sunspot을 이용하여 검색을 개선한 것을 보여드리도록 하겠습니다. 먼저 보여드릴 화면은 기존의 like 검색을 사용하고, ‘오향족발만두’라는 검색어로 검색을 했을 때의 결과 화면입니다.
결과 값을 보았을 때, 아무런 결과 값도 나오지 않았음을 확인할 수 있습니다. 다음은 sunspot을 이용하여 검색 방법을 개선한 후의 결과화면입니다.
똑같은 키워드에 대해서 검색을 했을 때, 더 많은 검색결과를 제공해 주고 있습니다.
Leave a Reply






Recent Comments