이번에 회사 솔루션을 업데이트 하면서 Solr작업을 할 일이 생겼다.
사실 솔라를 제대로 해 본적이 별로 없어서 이번에 고생을 좀 했는데 그 내용을 기록해본다.
보통 많이들 하는 자동완성 구현은 프론트에서 ajax로 검색어를 서버쪽으로 날리면 서버에서 솔라의 /select를 이용해서 대상 필드에 쿼리를 날려 응답을 얻어오는 방식으로 많이들 구현하는데 솔라에서는 이미 자동완성을 위한 suggester라는 기능이 있다.
또한 자바카페에서 올려놓은 훌륭한 초성검색 및 한영오타 라이브러리가 이미 존재하기 때문에 해당 라이브러리를 사용할 경우 손쉽게 구현이 가능하다. 우선 아래 링크에서 자신의 Solr버전에 맞는 라이브러리를 다운받는다. (내 경우 7.0라이브러리를 받아 Solr 8에 사용이 가능했다)
기본 솔라 구동 및 사용에는 문제가 없다고 가정하고 핵심만 짧게 서술해본다. 공식 레퍼런스는 아래 링크를 참고바람
It is designed to drive powerful document retrieval applications – wherever you need to serve data to users based on their queries, Solr can work for you.
schema.xml에 필드 및 애널라이저 설정
1. 다운받은 javacafe-analyzer jar라이브러리를 <솔라 설치 디렉토리>/server/solr-webapp/webapp/WEB-INF/lib/에 넣는다.
2. schema.xml에 다음 설정을 추가한다.
초성검색용 필드(text_auto_complete) 및 한영오타검색검색용(text_eng2kor_auto_complete) 필드 유형을 추가하고 애널라이저, 토크나이저, 필터 설정을 한다. 특정 필드유형을 정의하고 어떤 분석기를 사용할 것인지 지정하는 과정이다.
(원래 넣은 내용은 기존 설정을 보고 적당히 넣었었는데 알고보니 팩토리 클래스의 소스에 주석으로 정확한 설정이 들어 있어서 그 내용으로 테스트 해보고 수정하였다)
<fieldType name="text_auto_complete" class="solr.TextField">
<analyzer type="index">
<tokenizer class="solr.KoreanTokenizerFactory" decompoundMode="discard" outputUnknownUnigrams="false"/>
<filter class="solr.KoreanPartOfSpeechStopFilterFactory" />
<filter class="solr.KoreanReadingFormFilterFactory" />
<filter class="org.apache.solr.index.analysis.chosung.JavacafeChosungTokenFilterFactory"/>
<filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="50"/>
<filter class="solr.LowerCaseFilterFactory" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="org.apache.solr.index.analysis.chosung.JavacafeChosungTokenFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="text_eng2kor_auto_complete" class="solr.TextField">
<analyzer type="index">
<tokenizer class="solr.KoreanTokenizerFactory" decompoundMode="discard" outputUnknownUnigrams="false"/>
<filter class="solr.KoreanPartOfSpeechStopFilterFactory" />
<filter class="solr.KoreanReadingFormFilterFactory" />
<filter class="solr.LowerCaseFilterFactory" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="org.apache.solr.index.analysis.eng2kor.JavacafeEng2KorConvertFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
필드 유형을 추가했으니 필드도 추가한다.
<field name="searchChosungAuto" type="text_auto_complete" indexed="true" stored="true" /><!-- 초성검색 -->
<field name="searchEng2KorAuto" type="text_eng2kor_auto_complete" indexed="true" stored="true" /><!-- 한영오타검색검색 -->
추가한 필드에 원래 검색하려는 필드의 값을 복사해준다. (주로 제품명 등)
<copyField source="productName" dest="searchChosungAuto" />
<copyField source="productName" dest="searchEng2KorAuto" />
기존 productName필드에 “최고급 한우 살치살”이 들어있었다면 그 값이 그대로 searchChosungAuto, searchEng2KorAuto에 복제된다.
이후 애널라이저에 의해 “ㅊㄱㄱ ㅎㅇ ㅅㅊㅅ” “chlrhrmq gksdn tkfcltkf” 입력시 검색이 되게 된다.
3. solrconfig.xml에 Suggester 설정
SuggestComponent하위에 아래와 같이 suggester설정을 추가해준다. 혹시 기존에 쓰던 suggester설정이 있었다면 그 밑에 list인자로 추가해준다.
여기서 name은 나중에 /suggest쿼리시 suggest.dictionary에 인자로 사용하게 된다.
(자동완성에 쓰일 딕셔너리 명이 되는 것이며 여러개를 줄 수도 있음)
<searchComponent name="suggest" class="solr.SuggestComponent">
<lst name="suggester">
<str name="name">searchChosungAuto</str>
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<str name="lookupImpl">BlendedInfixLookupFactory</str>
<str name="suggestAnalyzerFieldType">text_auto_complete</str>
<str name="suggestFreeTextAnalyzerFieldType">text_auto_complete</str>
<str name="field">searchChosungAuto</str>
<str name="contextField">suggest_string</str>
<str name="buildOnCommit">true</str>
<str name="buildOnOptimize">true</str>
<str name="exactMatchFirst">true</str>
<str name="separator"><![CDATA[ ]]></str>
</lst>
<lst name="suggester">
<str name="name">searchEng2KorAuto</str>
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<str name="lookupImpl">BlendedInfixLookupFactory</str>
<str name="suggestAnalyzerFieldType">text_eng2kor_auto_complete</str>
<str name="suggestFreeTextAnalyzerFieldType">text_eng2kor_auto_complete</str>
<str name="field">searchEng2KorAuto</str>
<str name="contextField">suggest_string</str>
<str name="buildOnCommit">true</str>
<str name="buildOnOptimize">true</str>
<str name="exactMatchFirst">true</str>
<str name="separator"><![CDATA[ ]]></str>
</lst>
</searchComponent>
solr설정은 이게 끝이다. 이제 아래와 같이 테스트를 하고 이상없으면 어플리케이션단 작업을 하면 된다.
/select를 이용한 테스트
솔라 어드민 화면에 접속 및 로그인한다. 콜렉션 선택 후 query창에서 다음과 같이 입력 후 Execute Query를 눌러 테스트한다.
초성검색 입력
searchChosungAuto:ㅊㄱㄱ ㅎㅇ ㅅㅊㅅ
한영오타검색 입력
searchEng2KorAuto:chlrhrmq gksdn tkfcltkf
결과에 “최고급 한우 살치살”을 포함한 다큐먼트가 출력되면 성공이다. 이를 통해 애널라이저와 토크나이저가 정상동작하는 것을 알 수 있다.
이 케이스의 초성검색 FULL URL은 다음과 같다.
솔라 서비스 URL/solr/콜렉션명/select?indent=true&q.op=OR&q=searchChosungAuto%3A%E3%85%8A%E3%84%B1%E3%84%B1%20%E3%85%8E%E3%85%87%20%E3%85%85%E3%85%8A%E3%85%85
/suggest를 이용한 테스트
아래 주소를 호출했을 때
솔라 서비스 URL/solr/콜렉션명/suggest?suggest.dictionary=searchChosungAuto&suggest.q=%3A%E3%85%8A%E3%84%B1%E3%84%B1%20%E3%85%8E%E3%85%87%20%E3%85%85%E3%85%8A%E3%85%85
결과가 다음과 같이 나오면 잘 작동된다는 것을 알 수 있다.
{
"responseHeader": {
"status": 0,
"QTime": 9
},
"suggest": {
"searchAutoKeyword": {
"ㅊㄱㄱ ㅎㅇ ㅅㅊㅅ": {
"numFound": 10,
"suggestions": [{
"term": "최고급 한우 살치살 500g"
},
{
"term": "최고급 한우 살치살 선물세트"
}
]
}
}
}
}
JAVA에서의 사용
쿼리 실행 후 리턴된 response에서 SuggesterResponse를 꺼내고 거기서 다시 SuggestedTerms를 꺼내면 된다.
SolrQuery query = new SolrQuery();
query.setQuery("ㅊㄱㄱ ㅎㅇ ㅅㅊㅅ");
query.setRequestHandler("/suggest");
query.set("suggest.dictionary", new String[] { "searchChosungAuto", "searchEng2KorAuto" });
query.set("suggest.q", new String[] { "ㅊㄱㄱ ㅎㅇ ㅅㅊㅅ" });
QueryResponse response = solrClient.query(collectionName, query);
SuggesterResponse suggesterResponse = response.getSuggesterResponse();
이해가 좀 어려운 경우 stackoverflow의 코드를 참고 바란다.
query.setRequestHandler(”/suggest”);
query.setParam(“suggest”, ”…
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.