2015년 11월 26일 목요일

4. Classic ASP + Solr 연동

1. 요까지 하고 import 해보자. 다시한번 얘기하는데 solr에는 reindex 기능 없다. 설정 완료 됐다~~ 싶으면 해라.

2. import 가 끝나면 Schema Brower로 가보자.

 

2.1. 컬럼을 선택하면 입력된 용어들을 볼 수 있다. 저게 검색의 기초 자료가 되는 거다.

2.2. 구글링 하다 보니까.. 저 용어들을 nGram 필터로 한단어 단위로 쪼개줘야 한다는 걸 봤는데.. 테스트 해보니까 오히려 결과가 더 안 좋았다.

2.3 solr의 검색방식이 저장되어 있는 용어들에 대해서 점수를 매기는 것으로 알고 있다. 점수가 높으면 결과로 출력한다고 한다.

2.4. "개역개정4판" 이란 단어를 읽으면 "개역","역개","개정","정4","4판", "개역개정4판", 뭐 이런식으로 2단어씩 짤라서 단어 사전을 만드는 것 같다.

2.5. nGram필터로 1단어씩 짤라서 넣으면 당연히 한 단어씩 데이터를 구축하지만, 의외로 질의어에 대한 결과가 너무 많이 나오거나 특수문자가 결합된 데이터에 대해서는 결과가 엉망으로 나오드라. 그래서 그냥 2단어로 자르는 기본 형식으로 사용하기로 했다.

3. Classic ASP + Solr 연동

1. schema.xml

1.1. 이 파일에서 필드 유형 정의하는거야 대강 찾아보면 알테고.. 상품명에 사용한 설정은 다음과 같다.

schema.xml 일부 발췌

  name="text" class="solr.TextField" positionIncrementGap="100">
       type="index">
         class="solr.CJKTokenizerFactory"/>
         class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
         class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
         class="solr.LowerCaseFilterFactory"/>
         class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
         class="solr.PorterStemFilterFactory"/>
         class="solr.RemoveDuplicatesTokenFilterFactory"/>
      
type="query">
         class="solr.CJKTokenizerFactory"/>
         class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
         class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
         class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
         class="solr.LowerCaseFilterFactory"/>
         class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
         class="solr.PorterStemFilterFactory"/>
         class="solr.RemoveDuplicatesTokenFilterFactory"/>


만만한 설정은 다 때려 넣음. 형태소 이런게 제대로 동작하는지는 실제로 의문임...

1.2. tokenizer 필터를 arirang을 사용하지 않고 기본으로 지원하는 CJKTokenizerFactory를 사용했다. 중국어, 일본어, 한국어를 지원한다고 하는데 경험해 본 결과 양놈들은 한중일 3개 언어의 형식이 같아 보이는가보다.. 빙신들..

1.3. WhiteSpace~~~ 어쩌구는 사용안함. 이거 사용하면 공백으로 단어를 구분하게 되서 정확한 상품 검색을 오히려 방해한다.(내가 못하는 걸 수도 있고..ㅡㅡ;;)

2. solrconfig.xml은 거의 수정한게 없다. solr 4 cookbook을 보고 몇개 테스트 해봤으나.. 저~~~~연~~~혀~~~~ 도움이 안됨.. 그냥 기본 기능 그대로 사용하기로 함.

2.1. 분석기를 사용하기 위해서..  요거 하나만 추가 했다. 요거 추가하면 solr의 단어 형태소 분석기 같은걸 사용할 수 있다.. 왜 기본 기능을 막아 놨는지는 미스테리~~

2. Classic ASP + Solr 연동



1. solr 구축은 알아서. 서비스로 올리는 것도 재주껏(이미 블로그에 써 놨음)

2. 데이터 import 시에 초기 데이터가 매우 중요하다. solr가 웃긴게 중간에 데이터 형이나 설정을 변경하면 이미 import 한 데이터는 소용이 없다. 오죽하면 구글링을 해보니 재 인덱싱에 대한 질문글이 수두룩 하다. 물론 보통의 글들이 "님아 재 인덱싱 따윈 없어요.. 요런 얍삽이도 있긴 함.."으로 정리됨.

3. solr는 절대 한글과 친하지 않기에 내 경우에는 별의별 상황 대비를 위해서 띄어쓰기를 제거한 컬럼을 별도로 준비해 놨다. (그리고 그게 주효했다!!!)

4. solr 4 cook book에 나온 내용은 별로 도움이 안되더라. 그냥 기본 개념 정리는 좀 되긴 했지만, 실전이랑은 좀 동 떨어져 있음. 더군다나 한글은.........RIP..

5. 잔소리 그만하고 DB에서 import해서 인덱싱 했던거 부터 소개.

6. db-data-config.xml

 >
  name="lifebook" driver="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:서버JDBC형식 주소" user="사용자" password="비번" encoding="UTF-8"/>
    
         name="item" pk="pcode" 
        query="select * FROM VIEW A WHERE 1=1 "         
        deltaImportQuery="SELECT * FROM VIEW A WHERE prma_pcode= '${dih.delta.pcode}'"
        deltaQuery="SELECT pcode FROM VIEW A 
                    WHERE LEFT(upt_date,10)
                    BETWEEN CONVERT(CHAR(8),DATEADD(hh,-1,GETDATE()),112)+LEFT(REPLACE(CONVERT(CHAR(6),DATEADD(hh,-1,GETDATE()),108),':',''),2)
                    AND CONVERT(CHAR(8),GETDATE(),112)+LEFT(REPLACE(CONVERT(CHAR(6),GETDATE(),108),':',''),2)"

        >
             column="pcode" name="pcode" />
            column="pname" name="pname" />  
             column="search_pname" name="search_pname" />​
        
        
    
>

6.1. 미리 DB에 뷰를 만들어서 가져올 데이터를 준비해 놨다. pcode는 유니크키, pname은 상품명, search_pname은 띄어쓰기 제거한 상품명

6.2. 증분쿼리로는 수정 시간이 1시간 이내인 것들을 가져오게 설정해 놓음, deltaImportQuery와 deltaQuery를 잘 보면 어떻게 적용하는지 알 수 있을 것이다.

6.3. 다시한번 강조하지만, 중간에 설정이 변경되거나 필드가 추가된다고 해도 기존 받아놓은 데이터랑은 상관없는 얘기다. 일일이 업데이트 하거나 새로 데이터 받아야 함(전체 인덱싱을 통해..). 검색 서비스 시작전에 충분히 테스트 후 적용하시길.. 물론 난 이미 서비스 중이어서 어쩔 수 없는 상황이 많다.

7. filed는 실제 운영에는 당근 저거보다 많다. 검색 내 검색 구현시에 사용함.

8. 2015년 4월 13일 추가 : 증분 데이터로 100건 이상 넘어간다면.. 그냥 새로 받는걸 추천한다. 회사 DB의 상품이 9만건 정도 라이브로 운영하는데.. 100건 업데이트 하는데 1시간 이상 걸림.. 즉시 반영 옵션 같은거 다 써 봤으나.. 결국.. 좀 대량이다 생각되면.. 개발 환경에 같은 검색 엔진 환경 세팅 해서 데이터 새로 받고 기존 검색서버 잠시 중지한 다음, 데이터(인덱스 파일)만 새로 올림.. 이렇게 하면 20분안에 끗~~~ 무료 엔진이라 그런지.. 좀.. 아쉬운 점이 많다..

1. Classic ASP + Solr 연동

겪은 상황 일단 정리

1. 설치한 버전은 solr 4.3.1 : 작년에 설치해서 버전이 좀 낮음. 현재 최신 버전은 solr 4.9.0 인듯..

2. 한글 처리를 위해서 koreananalyzer.4x-20130430 이나 arirang-1.1.2 를 사용해 보았으나, 결과적으로 별 효과가 없다.

3. arirang을 사용하려면 solr 4.5 사용해야 함. 권장하는 설정을 맞춰서 해봤으나 효과 없음.

4. koreananalyzer​도 4.3이 가능하다고 하는거 같으나, 결과적으로 사용 못했다. 끊임없는 에러(배포본에 문제가 있는 듯 하다. 아마 arirang으로 추후 배포하는 것 같아서 더 이상 사용 안함).​

5. solr 데이터 import시에 한글에 대해서는 한글자씩 넣으라는 글도 해 봤으나, 검색 결과가 말도 안되게 나와서 중단.

6. 결과적으로는 검색어를 가지고 컨트롤 하는 것으로 결론.

7. 한글이래서 그런지 exact matching이 잘 안되는거 같다. 게다가 한글과 특수기호가 들어있으면 결과가 더 안좋아지는 마법도 발동했음.

8. 특수기호에 한글이 두단어 이상 붙어 있으면 결과가 나오는데 한글자만 붙어 있으면 안 나오는 묘기도 구경.

이제 시작해 보자.. 기초적인건 정리 안함.

0. Classic ASP + Solr 연동

1. 목적 : 회사에서 검색 서비스 도입을 하면서 느꼈던 점 및 자료 정리 차원에서 포스트로 정리하려고 함.

2. 환경 : 재직 중인 회사가 IIS7.5(Windows 2008 R2), MSSQL2005 사용중임

3. Spec : Classic ASP + MsSQL2005 + Ansi 환경(Unicode 아님!)

4. 그외 : 이거 하는데 3달 걸림.. 개고생 + 맘고생. solr 생각보다 안 좋다.. 쓰벌..

Apache Solr 연관성 FAQ



Solr Relevancy FAQ

Relevancy is the quality of results returned from a query, encompassing both what documents are found, and their relative ranking (the order that they are returned to the user.)


Should I use the standard or dismax Query Parser

The standard Query Parser uses SolrQuerySyntax to specify the query via the q parameter, and it must be well formed or an error will be returned. It's good for specifying exact, arbitrarily complex queries.
The DisMax Query Parser has a more forgiving query parser for the q parameter, useful for directly passing in a user-supplied query string. The other parameters make it easy to search across multiple fields using disjunctions and sloppy phrase queries to return highly relevant results.
For servicing user-entered queries, start by using dismax.
" height="16" src="https://wiki.apache.org/wiki/modernized/img/attention.png" title="<!>" width="16" style="border: 0px;"> Solr3.1 From Solr 3.1 we recommend starting with the new Extended Dismax parser enabled by defType=edismax

How can I search for "superman" in both the title and subject fields

The standard request handler uses SolrQuerySyntax for q:
q=title:superman subject:superman
Using the dismax request handler, specify the query fields using the qf param.
q=superman&qf=title subject

How can I make "superman" in the title field score higher than in the subject field

For the standard request handler, "boost" the clause on the title field:
q=title:superman^2 subject:superman
Using the dismax request handler, one can specify boosts on fields in parameters such as qf:
q=superman&qf=title^2 subject

Why are search results returned in the order they are?

If no other sort order is specified, the default is by relevancy score.

How can I see the relevancy scores for search results

Request that the pseudo-field named "score" be returned by adding it to the fl (field list) parameter. The "score" will then appear along with the stored fields in returned documents.q=Justice League&fl=*,score

Why doesn't my query of "flash" match a field containing "Flash" (with a capital "F")

The fieldType for the field containing "Flash" must have an analyzer that lowercases terms. This will cause all searches on that field to be case insensitive.

How can I make exact-case matches score higher

Example: a query of "Penguin" should score documents containing "Penguin" higher than docs containing "penguin".
The general strategy is to index the content twice, using different fields with different fieldTypes (and different analyzers associated with those fieldTypes). One analyzer will contain a lowercase filter for case-insensitive matches, and one will preserve case for exact-case matches.
Use copyField commands in the schema to index a single input field multiple times.
Once the content is indexed into multiple fields that are analyzed differently, query across both fields.

I'm getting query parse exceptions when making queries

For the standard request handler, the q parameter must be correctly formatted SolrQuerySyntax, with any special characters escaped. If this is a user-entered query, consider using the dismax handler.
Many other parameters such as fq and facet.query must also conform to SolrQuerySyntax regardless of which handler is used.

How can I make queries of "spiderman" and "spider man" match "Spider-Man"

WordDelimiterFilter can be used in the analyzer for the field being queried to match words with intra-word delimiters such as dashes or case changes.

How can I search for one term near another term (say, "batman" and "movie")

A proximity search can be done with a sloppy phrase query. The closer together the two terms appear in the document, the higher the score will be. A sloppy phrase query specifies a maximum "slop", or the number of positions tokens need to be moved to get a match.
This example for the standard request handler will find all documents where "batman" occurs within 100 words of "movie":
q=text:"batman movie"~100
The dismax handler can easily create sloppy phrase queries with the pf (phrase fields) and ps (phrase slop) parameters:
q=batman movie&pf=text&ps=100
The dismax handler also allows users to explicitly specify a phrase query with double quotes, and the qs(query slop) parameter can be used to add slop to any explicit phrase queries:
q="batman movie"&qs=100

How can I increase the score for specific documents


Query Elevation Component

To raise certain documents to the top of the result list based on a certain query, one can use the QueryElevationComponent.

index-time boosts

To increase the scores for certain documents that match a query, regardless of what that query may be, one can use index-time boosts.
Index-time boosts can be specified per-field also, so only queries matching on that specific field will get the extra boost. An Index-time boost on a value of a multiValued field applies to all values for that field.
Under the covers, solr uses this numeric boost value as a factor that contributes to the "norm" for the field (along with teh length of the field in terms) so it is only valid if 'omitNorms="false"' for the fields you use it on. It will also be encoded according to the rules of the Similarity class used, which means it may loose precision. (ie: in theDefaultSimilarity, the numeric norm value is encoded as a single byte using a 3-bit mantissa, so differences in boost values of less than 25% may end up being rounded out)
Index-time boosts may also be assigned with the optional attribute "boost" in the section of the XML updating messages - in which case it is equivilent to specifying the boost param on all of the individual fiels. See UpdateXmlMessages for more information.
Using Field and/or Document boosts has been supported since the very early days of Lucene, but is some what limiting and antiquated at this point, and instead people should strongly consider indexing their boost values as numeric fields instead (see next section)

Field Based Boosting

You can structure your queries to include "boosts" based on specific attributes of documents. This might be a simple matter of adding an optional query clause that "boosts" documents for matching an "important:true" query, or by using a function on the value of a numeric field (see the next section)

How can I change the score of a document based on the *value* of a field (say, "popularity")

Use a FunctionQuery as part of your query.
Solr can parse function queries in the following syntax.
Some examples...
  # simple boosts by popularity
  defType=lucene&df=text&q=%2Bsupervillians+_val_:"popularity"
  defType=dismax&qf=text&q=supervillians&bf=popularity
  q={!boost b=popularity}text:supervillians

  # boosts based on complex functions of the popularity field
  defType=lucene&q=%2Bsupervillians+_val_:"sqrt(popularity)"
  defType=dismax&qf=text&q=supervillians&bf=sqrt(popularity)
  q={!boost b=sqrt(popularity)}text:supervillians
These functions can also operate on an ExternalFileField

How are documents scored

By default, a "TF-IDF" based Scoring Model is used. The basic scoring factors:
  • tf stands for term frequency - the more times a search term appears in a document, the higher the score
  • idf stands for inverse document frequency - matches on rarer terms count more than matches on common terms
  • coord is the coordination factor - if there are multiple terms in a query, the more terms that match, the higher the score
  • lengthNorm - matches on a smaller field score higher than matches on a larger field
  • index-time boost - if a boost was specified for a document at index time, scores for searches that match that document will be boosted.
  • query clause boost - a user may explicitly boost the contribution of one part of a query over another.
See the Lucene scoring documentation for more info.

Why does id:archangel come before id:hawkgirl when querying for "wings"

Add debugQuery=on to your request, and you will get (fairly dense) detailed scoring information for each document returned.
q=wings&indent=on&debugQuery=on
This extra information will appear in the "explain" section of the "debug" section in the response.

[...]

 wings
 wings
 text:wings
 text:wings
 
  
0.46632254 = (MATCH) fieldWeight(text:wings in 4), product of:
  1.7320508 = tf(termFreq(text:wings)=3)
  2.871802 = idf(docFreq=2)
  0.09375 = fieldNorm(field=text, doc=4)

  
0.35897526 = (MATCH) fieldWeight(text:wings in 24), product of:
  1.0 = tf(termFreq(text:wings)=1)
  2.871802 = idf(docFreq=2)
  0.125 = fieldNorm(field=text, doc=24)

[...]
In this specific example, we see that the main scoring difference between the two documents is the tf or (term frequency) factor. The text field for the id:archangel document contains the term wings 3 times (termFreq(text:wings)=3) while the id:hawkgirl document only contains it once.
Debug info is expensive to generate, and should only be used for debugging problems with specific queries.
Debug info can also be selected from the admin query page, http://localhost:8983/solr/admin/form.jsp

Why doesn't document id:juggernaut appear in the top 10 results for my query

Since debugQuery=on only gives you scoring "explain" info for the documents returned, the explainOther parameter can be used to specify other documents you want detailed scoring info for.
q=supervillians&debugQuery=on&explainOther=id:juggernaut
Now you should be able to examine the scoring explain info of the top matching documents, compare it to the explain info for documents matching id:juggernaut, and determine why the rankings are not as you expect.

How can I boost the score of newer documents

A full example of a query for "ipod" with the score boosted higher the newer the product is:
http://localhost:8983/solr/select?q={!boost b=recip(ms(NOW,manufacturedate_dt),3.16e-11,1,1)}ipod
One can simplify the implementation by decomposing the query into multiple arguments:
http://localhost:8983/solr/select?q={!boost b=$dateboost v=$qq}&dateboost=recip(ms(NOW,manufacturedate_dt),3.16e-11,1,1)&qq=ipod
Now the main "q" argument as well as the "dateboost" argument may be specified as defaults in a search handler in solrconfig.xml, and clients would only need to pass "qq", the user query.
To boost another query parser such as a dismax query, the value of the boost query is a full sub-query and hence can use the {!queryParser} syntax. Alternately, the defType param can be used in the boost local params to set the default type to dismax. The other dismax parameters may be set as top level parameters.
http://localhost:8983/solr/select?q={!boost b=$dateboost v=$qq defType=dismax}&dateboost=recip(ms(NOW,manufacturedate_dt),3.16e-11,1,1)&qf=text&pf=text&qq=ipod
Consider using reduced precision to prevent excessive memory consumption. You would instead use recip(ms(NOW/HOUR,mydatefield),3.16e-11,1,1). See this thread for more information.

How do I give a negative (or very low) boost to documents that match a query?

True negative boosts are not supported, but you can use a very "low" numeric boost value on query clauses. In general the problem that confuses people is that a "low" boost is still a boost, it can only improve the score of documents that match. For example, if you want to find all docs matching "foo" or "bar" but penalize the scores of documents matching "xxx" you might be tempted to try...
    q = foo^100 bar^100 xxx^0.00001    # NOT WHAT YOU WANT
...but this will still help a document matching all three clauses score higher then a document matching only the first two. One way to fake a "negative boost" is to give a large boost to everything that does *not* match. For example...
    q =  foo^100 bar^100 (*:* -xxx)^999  
NOTE: When using (e)dismax, people sometimes expect that specifying a pure negative query with a large boost in the "bq" param will work (since Solr automatically makes top level purely negative positive queries by adding an implicit "*:*" -- but this doesn't work with "bq", because of how queries specified via "bq" are added directly to the main query. You need to be explicit...

    ? defType = dismax 
    & q = foo bar 
    & bq = (*:* -xxx)^999  

Apache SOLR 중첩쿼리 관련 번역



다른 쿼리 형식 안에 임의의 다른 쿼리 형식을 중첩하는 유용한 기능은, Solr가 ​다른 쿼리 형식들을 지원하는 쿼리 분석기를 지원하면서 이전에 조용히 추가되었다. 드디어 함수 쿼리 분석기를 위한 고정 중첩 쿼리들이 출시되고, 쿼리 파라메터로 메타데이터 하나를 추가하는 LocalParam들이 생성되거나, 하나의 쿼리(즉 쿼리 문장을 분석하는데 사용하는 쿼리 파서)​의 형식을 바꿀 수 있는, 중첩 쿼리들에 대한 문서를 작성하는데 오래 걸렸다.

LUCENE 구문안의 중첩 쿼리들

Lucene/Solr 쿼리 문에서 다른 형식의 쿼리를 넣는 것은, 마법의 필드 명 _query_ 를 사용하면 간단하다. 다음은 다른 lucene 쿼리 안에 lucene 쿼리 type:poems를 넣는 예제이다:
text:"roses are red" AND _query_:"type:poems"
이번 과정은 자체로는 유용하지 않지만, 쿼리의 형식을 변경할 수 있는 쿼리 프레임웍과 지역 param들과 함께 사용하면 매우 강력해 진다. 다음은 일반적인 lucene 쿼리 안에 하나의 Dismax 쿼리를 넣는 예제이다:
text:hi  AND  _query_:"{!dismax qf=title pf=title}how now brown cow"
그리고 앞-단의 요청을 구성하는 것을 쉽게 할 수 있는 지역 params 안에 역 참조 파라메터를 사용할 수 있다:
&q=text:hi  AND  _query_:"{!dismax qf=title pf=title v=$qq}
&qq=how now brown cow

함수 쿼리 구문 안의 중첩 쿼리들

이 기능은 이전에 동작이 안되던 부분이고, Solr 1.4에서 수정되어 가능해 졌다. 함수 쿼리 안의 어떤 다른 쿼리 형식에 query() 함수를 사용하여 ​넣어서 사용 할 수 있고, 그 쿼리로 관련 점수를 계산하여 반환한다. 몇가지 예제들이 Solr wiki에 있다.

PURE NESTED QUERY(완전 중첩 쿼리)

또한 완전 중첩 쿼리들을 생성할 수 있게 하는 중첩 쿼리 분석기 플러그인들이 있다. 어떤 쿼리들도 포함하지 않은 중첩 쿼리가 더 유용할까? 놀랍게도 그렇다. 쿼리 요청의 분해가 더 나아진다. 예를들면, 다음은 클라이언트가 원하는 Solr 서버 설정(solrconfig.xml 안의 default 검색 제어자 handler를 사용하는)에 따르는 정확한 쿼리 형식을 남기는, 연관 점수가 추가되는 최신 date boost의 몇가지 정렬들을 나열하는 쉬운 방법을 제공한다. 클라이언트 쿼리는 boost 쿼리로 $datefunc를 지정하면 된다:
q=how now brown cow&bq={!query v=$datefunc}
그리고 solrconfig.xml 안의 default 제어자 handler 는 함수 쿼리로서 datefunc의 실제 정의를 포함하게 된다:

   {!func}recip(rord(date),1,1000,1000)
   [...]
동일한 생각은 클라이언트에게 이런 필터들의 나열 없이도, 복잡한 필터들간의 전환이 가능하게 해준다. 중첩 쿼리 분석기 형식 없이도, 별도의 값(v=$param 같은 지역 param들로)으로 형식뿐만 아니라 쿼리 값의 나열도 가능하게 해준다.

[낯선] 2026.06.20 이태원 MZK MuzikBar 공연

간만의 공연 이태원 비틀즈가 이전하여 MZK MuzikBar 로 새 단장 오픈을 한 뒤로 2번째 공연 새 베이시스트가 합류한 뒤로 첫 공연이기도 함.. 이래저래 틀린 부분도 많지만.. 그래도 간만에 그럭저럭 잘했다.. https://www.youtub...