루씬 정렬(Sort) 그리고 CustomScoreQuery

검색엔진을 활용하면서 항상 기술적으로 대두되는 이슈가 정렬처리, 실시간검색 단어인듯하다. 다양한 방법으로 Sort처리하고 있지만 루씬메일링리스트에서 찾아본자료에서는 크게 2가지 방법으로 접근하는듯하다. 2가지 방법을 알아보고, 조금 실험적인 방법인 CustomScoreQuery 에 대해서 알아보고자 한다.

  • 루씬내부 Sort 클래스를 이용하여 정렬
  • 검색한결과를 재정렬하는 방법, 검색범위를 줄여서 정렬하는 방법
루씬내부 Sort 클래스를 이용하여 정렬
SortField sortFeild = new SortField("DATE",SortField.STRING,true);
Sort sort = new Sort(sortFeild);
TopFieldDocs docs = searcher.search(query,null,200,sort);

위코드와 같이 Sort클래스를 활용하여 루씬내부에서 정렬을 하게끔 처리하도록 한다. 하지만, 루씬내부 Javadoc문서를 살펴보면,

Memory Usage
Sorting uses of caches of term values maintained by the internal HitQueue(s). The cache is static and contains an integer or float array of length IndexReader.maxDoc() for each field name for which a sort is performed. In other words, the size of the cache in bytes is:
4 * IndexReader.maxDoc() * (# of different fields actually used to sort)

내부에서 Sort를 위해 term value값을 캐시하기때문에, 최대검색결과 * 필드형의사이즈 만큼의 메모리를 사용하게 된다. 따라서, 인덱스사이즈가 큰경우 OutOfMemoryException 이 발생하게되고 또한, 요청수가 많게되면 GC로 인한 Hang현상을 경험하게 된다. 즉, 성능상이슈가 발생하게 된다는 것이다.

검색한결과를 재정렬하는 방법, 검색범위를 줄여서 정렬하는 방법

루씬을 사용하여 기본적인 검색을 진행하면 랭킹알고리즘을 적용하여 검색결과를 나타나게 된다. 내부적으로 문서빈도수, 색인어빈도수를 측정하여 랭킹을 계산하게 된다. 아래의 코드는 특정쿼리로 최대 1000개의 문서를 반환하게끔하는 코드이다.

TopScoreDocCollector collector = TopScoreDocCollector.create(1000,false);
searcher.search(query,collector);
TopDocs docs = collector.topDocs();

이 결과를 바탕으로 다시 내가 원하는 필드로 정렬하는 방식이다. 즉, TopN개에 대한 결과를 미리 처리한 후 정렬하겠다는 것. 요약하면, 검색품질에 대한 어느정도의 필터링을 거친후 정렬하겠다는 방식이다.

첫번째 설명한 루씬내부에서의 정렬은 모든문서가 정렬대상이 되는 반면, 지금 정렬방식은 TopN개만이 검색정렬대상이 된다는 것이다. 특별하게 검색결과에 포함되어야 하는 것은 누락이 될 수 있다는 위험성을 내포한다. TopN개의 수위조절을 어느선에서 정할지에 대한것은 검색결과,성능과의 Trade Off니 운영을하면서 맞추어주는 방법밖에는 없는듯하다. 또한, TopN개에 대한 정렬을 Java단에서 처리하게되면 메모리부족이슈가 발생할 수 있으므로 JNI를 통해서 Native쪽으로 우회하는 방법도 존재하는듯하다. 첫번째,두번째 방법의 경우 모두 정렬에 대한 명쾌한 해답을 제시해주지 못하고 있다.

정말 인덱스사이즈가 클수록 정렬이슈가 커진다. 또한, 검색엔진에서 최신순 정렬은 필수가 아니던가!!! T.T 정렬이슈로 고민하다 “루씬에서 계산하는 Score를 바꾸어 줄 수 있다면…” org.apache.lucene.search.function.* 패키지에서 해답을 찾았다!

CustomScoreQuery

org.apache.lucene.search.function.* : Programmatic control over documents scores.

org.apache.lucene.search.function
Class CustomScoreQuery

java.lang.Object
  extended by org.apache.lucene.search.Query
      extended by org.apache.lucene.search.function.CustomScoreQuery


WARNING: The status of the search.function package is experimental. The APIs introduced here might change in the future and will not be supported anymore in such a case.

루씬내부에서 문서점수계산에 대한 API를 제공하고 있다!
사용법은 다음과 같다.

FeildCustomScoreQuery scoreQuery = new FeildCustomScoreQuery("필드명", FieldScoreQuery.Type.타입);
query = new [CustomScoreQuery를 상속한클래스](query, scoreQuery);

CustomScoreQuery – 오버라이드하여 점수계산

public class FeildCustomScoreQuery extends CustomScoreQuery {
....
@Override
public float customScore(int doc, float subQueryScore, float valSrcScore){
return valSrcScore;
}
}

해당 CustomScoreQuery,FeildCustomScoreQuery를 사용하여 날짜가 최신순이면 스코어를 높이는 방식으로 최신순정렬을 해결하였다. 루씬내부에서 실험적인 시도이므로 다음릴리즈때 어떤변화가 있을지 모르겠지만, 개인적으로 정렬에 대한 근본적인접근이라 생각한다. org.apache.lucene.search.function.* 패키지 내부의 클래스를 활용하면 다음과 같은 멋진것도 가능하다.

  • 구글의 PageRank 개념으로 문서랭킹을 만들 수 있다.
  • 지도검색에서 어떤 상점을 검색시, 현재 위치한 곳에서 가까운 가게가 랭킹이 더 높다.
  • 블로그검색에서 RSS구독자가 많은 블로그가 랭킹이 더 높다.
  • 등등…

참고 URL

루씬 정렬(Sort) 그리고 CustomScoreQuery”에 대한 5개의 생각

  1. 좋은 글 감사합니다. 저는 요새 다른 업무에 치여서 루씬을 손을 놓고 있었는데
    이렇게 좋은 자료를 올려주셔서 잘 보았습니다 ^^
     인덱스의 사이즈가 클 수록 정렬에 대한 이슈가 발생하는 것은
    시중에 라이센스로 판매되고 있는 검색 엔진들도 마찬가지 인 것 같습니다.
     
    정렬이 필요한 필드를 미리 캐시를 걸어 놓는다던지 하는 방법을 사용하기도 하더라구요.. ㅎㅎ
     
     

  2. 좋은 글 감사합니다. 현재 대용량 데이타 정렬 메모리 문제때문에 걱정이 있었는데. 소개해주신 자료를 바탕으로 구현하고 있습니다.하나 궁금한 사항이 있는데요. 짐승님은 날짜를 위의 방식으로 처리하셨다고 하시는데 저는 그게 잘 안되는데요 ㅠ.ㅠ년월일시분초까지 저장하고 있는데 최신순으로 뽑아내야 하는데 제가 원하는데로 안되네요..어떤식으로 해야 할지 조언부탁드려도 될까요?

    1. 안녕하세요.루씬을 잘활용하는 일이 참 어렵습니다.제가 설명을 드리는것보다, 이미 잘정리하신 용식님 글을 소개해드리는게 좋을듯하네요.아래 3개 포스팅글이 워낙 잘 정리되어있으니 참고 많이 되실듯하구요.혹시나 보시고 조금 어렵다면, “루씬인액션 1판” 책을 완독 후 보시면 도움이 되실듯합니다.그럼 건승하시길 바랍니다~ ^^http://devyongsik.tistory.com/281http://devyongsik.tistory.com/282http://devyongsik.tistory.com/283

  3. 좋은 글들 감사드립니다~ 사내에서 실험적으로 루씬을 사용해보고 있습니다.
    필드별 순위매김을 따로 할 일이 있었던 차에 윗 글로 많은 참조 하고 갑니다.
    아직 많이 부족하여 어려움이 많지만, 무척 감사드립니다~

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중