Programmer/Lucene - 해당되는 글 4건

참 제목도 힘들다 -_-;;;

기존에 모두 인덱싱하고 서비스하고 있던 index에 컬럼을 추가해야 되는 일이 생겼다.

ㅜㅜ 그러고 싶지 않았지만 서비스를 해야하기에 어쩔수 없이 일이 생겼다

예를들어 no, name, phone만 사용하던 index에 address라는 컬럼을 추가하게 된것이다.

추가 하는데 무슨일이 생길까? 라는 의문이 생기겠지만 나는 이 새로 추가된 컬럼에

값이 있는것들만 뽑고 싶었다.

Lucene에서 지원하는 ' *:* '라는 검색값을 넣으면 모든 컬럼을 반환해주는것을 내가

사용해야 했기 때문에 의문이 생겨서 테스트를 해보았다.

결과는 어떠할까....

값이
no      name      phone
1         kim         010
2         lee         011
3         park       016

이렇게 있다가 address라는 컬럼을 추가하면서 4  choi   019   pusan 을 넣었다.

no      name      phone    address
1         kim         010
2         lee         011
3         park       016
4         choi       019         pusan

이라고 될것이다. 그럼 검색시에 필드를 address를 설정하고 *:* 라고 하면 어떻게 나올까

정말 나는 뛰어난 Lucene의 힘으로 4에 대한 값만 나왔으면했지만

luke로 테스트 해본결과 1,2,3,4 모두 값이 나왔다 ㅜㅜ

알아서 null이나 인식이 안되는 값을 넣어서 4번만 나오면 얼마나 좋을까....

다들 inexer나 검색서버 운영시에 참고하길 바란다.... 아흑~
|

What is Lucene

Jakarta Lucene은 고성능의 확장 가능한 검색 엔진 기술로서 완전히 자바로 작성되었다. Lucene API는 인덱스 구성에 관한 기능과 검색 기능으로 구성되어 있다. Jakarta Lucene은 텍스트 검색 엔진을 필요로 하는 모든 어플리케이션에 적합한 기술이다.

Lucene의 기본적인 용어와 설명

용어 설명
indexing 인덱싱이란 인덱스를 생성하는 처리 과정이다. 인덱스는 컴파일된 문서들의 버전을 포함하고 있는 특별한 데이터베이스이며, 특정 단어들(term)들을 포함하고 있는 문서 목록을 빠르게 찾을 수 있도록 최적화 되어 있다. 대개 이러한 인덱스들은 선택된 한 디렉토리에 Lucene이 생성한 일련의 파일들에 저장된다.
analyzer Analyzer는 인덱싱 동안 문서의 내용을 어떻게 term(단어들)들로 쪼개야 할지에 대해 제어하는 클래스이다. 예를 들면, 어떤 한 analyzer는 "The ill dogs"라는 텍스트를 소문자로 바꾸어 "the", "ill, dogs"의 형태로 쪼개는 반면, 다른 한 analyzer는 소문자로 변환하고 복수형태는 단수 형태로 정규화 하며 일반적인 단어인 "the"는 제거한 형태인 "ill", "dog"라고 쪼갤 수도 있다. 어떤 analyzer를 사용할지 선택하는 것이 중요하다.
searching 이는 원하는 내용을 포함하고 있는 문서의 일부나 몇몇 지정한 사항들과 일치하는 문서의 속성들을 찾는 과정(operation)이다. search 연산은 미리 컴파일된 문서들을 포함하고 있는 특수한 데이터인 '인덱스'에 대해 수행된다. 인덱스 데이터베이스는 "인덱싱" 과정 동안 생성된다.
query 일반적으로 query는 문서를 선택하기 위한 criteria를 지정하며, searching은 문서 인덱스와 query를 비교하고 query와 일치하는 것들을 찾음으로써 이루어진다. query의 결과로 해당 criteria와 일치하는 문서들의 목록(hits)을 얻게 된다.
document Lucene에서 사용하는 문서를 나타낸다. 문서를 나타내기 위해 Document라는 클래스가 제공된다. 이 클래스의 인스턴스는 인덱싱 과정 동안은 인덱스에 추가될 문서들의 정보를 나타내며 검색 동안은 검색의 명중율(hits)을 나타낸다.
field 문서와 hit 정보는 Document 클래스에 필드들의 목록으로 표현된다. 각각의 필드는 이름(String)과 값(문자열이 추출될 수 있는 String이나 Reader)를 포함하고 있다. 또한 각각의 필드는 세 가지 boolean 속성을 갖는데, 이들의 조합은 인덱싱 동안 필드가 사용되는 방법을 지정한다. 이들 속성은 isIndexed, isStored, isTokenized이다.
hit 이것은 query의 결과이며, 지정된 query와 일치하는 문서들의 목록을 말한다. hit 목록은 일반적으로 ranking이나 scoring라 불리는 몇몇 관련 정도의 단위로 정렬된다. hit 목록에는 질의와 일치하는 문서들(매우 높은 score를 가진 문서들)로 이루어진 것들 중의 일부분만을 포함할 수도 한다.
terms 일반적으로 terms는 단어(words)라고 생각할 수 있다. 예를 들면, 문자열 "yadda yadda yadda"는 세 개의 terms를 포함하고 있으며, 이들 각각의 terms는 "yadda"라는 값을 가지고 있다. Lucene이나 대부분의 검색 엔진에서 terms는 인덱싱과 검색을 위한 기본적인 단위이다. terms 값들은 대소문자를 구별한다. 영어나 라틴 언어가 아닌 다른 언어의 텍스트를 terms들로 쪼갤 필요가 있는 경우 규칙들을 커스터마이징 할 수 있다.
tokenizing 문자열을 terms들로 쪼개는 과정. tokenizing은 Analyzer 객체를 사용해 생성되는 TokenStream의 인스턴스들에 의해 수행된다. 만약 한 문서의 필드가 인덱싱 과정 동안 "tokenize하지 않도록" 지정되었다면, 필드의 전체 내용이 단일 term으로 간주된다(URL인 경우 유용).
term query Lucene에서의 매우 단순한 질의이며 한 단어와 비교하는 경우에 사용된다. TermQuery 클래스로 표현되며 term과 필드 이름을 포함하고 있다.
boost factor query 구조와 일치하는 문서의 랭킹을 증가시키거나 감소시키는데 사용될 수 있는 factor.
phrase query 필드에서 연속된 일련의 terms들에 대해 일치하는지를 비교하는 query를 나타낸다. 예를 들면, "winding road"는 "winding road"와는 일치해야 하지만 "road winding"와는 일치하지 않는다. PhraseQuery 클래스 인스턴스로 표현된다. 이 인스턴스는 일치하는 terms들을 표현하는 정렬된 Term 객체들로 이루어진 리스트를 포함한다.
boolean query "AND", "OR", "NOT"과 같은 규칙들을 사용한 복합 질의를 나타낸다. BooleanQuery 클래스의 인스턴스로 표현된다. 각각의 객체는 BooleanClause라는 어댑터 클래스의 인스턴스를 사용해 연결된 하위 질의들의 목록을 포함하고 있다.
filtering hit 목록에 대해 추가적인 제한을 가해 검색 결과에 영향을 주는 것을 말한다.
 
 
 
 

Lucene의 설치

다운로드 받은 Lucene의 압축을 적절한 디렉토리에 푼 후, lucene-1.4-final.jar를 클래스패스에 추가하면 Lucene의 설치는 끝난다.

주요 클래스 설명

  • 인덱싱 관련 클래스
클래스 이름 설명
IndexWriter 인덱스를 만들고 유지한다. 생성자의 인자들은 위에서 설명한 것과 동일하다. Document들을 추가하는 작업이 종료된 후에는 반드시 이 객체의 close 메소드를 통해 닫아주어야 한다. 만약 잠시 동안 추가될 Document들이 더 이상 없고 검색 성능을 높이기 위한 최적화를 수행하고자 할 경우, close 메소드 호출 전에 optimize 메소드를 호출할 수도 있다.
Analyzer 텍스트를 분석하는 TokenStream들을 만들어준다. 즉, 문서를 인덱싱 하거나 검새갈 때 핵심이 되는 요소로서, 텍스트를 파싱 할 때 사용된다.
StandardAnalyzer 추상 클래스 Analyzer를 구현한 클래스이다. 아스키 코드, 라틴권 문자와 언어 문법을 기반으로 한 analyzer이다. stop word에 대해서는 인덱싱 처리를 하지 않는다.
WhitespaceAnalyzer 추상 클래스 Analyzer를 구현한 클래스이다. 공백 문자를 기준으로 인덱싱 작업을 한다.
SimpleAnalyzer 공백과 하이픈을 사용해 단어를 구분하여 인덱싱 작업을 한다.
Document Document는 한 문서에 대한 정보를 담고 있다. Document는 인덱싱과 검색의 단위이며, 필드들로 이루어져 있다. 각각의 필드는 이름과 텍스트 값을 가지고 있다. 대개 Document는 하나 이상의 stored 필드들을 갖는다.
Field 필드는 Document의 일부를 구성한다. 각각의 필드는 이름과 값이라는 두 가지 부분을 갖는다. 값들은 String이나 Reader로 제공되는 형태의 텍스트나 atomic한 keyword들일 수 있다. 필드들은 선택적으로 인덱스에 저장되어 검색된 문서와 함께 반환될 수 있다. Field에는 Keyword, UnIndexed, UnStored, Text라는 네 가지 타입이 있다.
Field.Text 텍스트 데이터를 토큰으로 구분하여 인덱싱 작업을 수행하며, 그 값을 인덱스에 저장한다.
Field.Keyword 텍스트 데이터를 토큰으로 나누지 않는다. 인덱싱 작업을 처리하며 텍스트 데이터 그대로 인덱스에 저장된다.
Field.UnIndexed 텍스트 데이터를 토큰으로 분리하지 않고, 인덱싱 작업도 처리하지 않는다. 텍스트 데이터는 그대로 저장한다.
Field.UnStored 텍스트 데이터를 토큰으로 분리하고 인덱싱 작업도 처리하지만, 텍스트 데이터는 저장하지 않는다.

  • 검색 관련 클래스
클래스 이름 설명
Searcher 검색 구현을 위한 abstract base 클래스이다. 몇몇 일반적인 유틸리티 메소드들을 구현하고 있다.
IndexSearcher 단일 IndexReader에 대한 검색을 구현한 클래스이다. 어플리케이션에서는 상속된 Searcher.search(Query)나 Searcher.search(Query, Filter) 메소드를 호출한다.
QueryParser 이 클래스는 JavaCC에 의해 생성된다. 클라이언트에서는 parse() 메소드를 호출하게 된다. 이 메소드는 질의 문자열을 파싱 하여 Query를 반환한다.
Query 질의를 위한 abstract base 클래스이다.
Hits 순위가 매겨진 Document들의 목록이며, 검색 결과를 저장하고 유지하는데 사용된다.






Query Syntax

Terms
질의는 term과 연산자(operator)로 나뉜다. term에는 Single terms라는 것과 Phrases라는 두 가지 타입이 term이 존재한다. Single Term은 "test"나 "hello"와 같이 한 단어로 이루어진다. Phrase는 "hello dolly"와 같이 인용 부호로 감싸여진 여러 단어로 이루어진다. 여러 term들은 보다 복잡한 질의를 나타내기 위해 Boolean 연산자와 함께 조합될 수 있다.

Fields
Lucene은 필드화 된 데이터를 지원한다. 검색을 수행할 때, 필드를 지정하거나 디폴트 필드를 사용할 수 있다. 필드 이름들과 디폴트 필드는 구현에 종속적이다. 필드 이름 다음에 콜론(:)을 적고 찾으려는 term을 적어 넣음으로써 어떠한 필드라도 검색할 수 있다.

예를 들면, Lucene 인덱스가 title과 text라는 두 개의 필드를 포함하고 있다고 가정해보자. 이 때 text는 디폴트 필드라고 가정한다. 만약 "don't go this way"라는 text를 포함하며 "The Right Way"라는 title을 갖는 문서를 검색하고자 한다면, 다음 둘 중 한 방식으로 입력하면 된다.

title:"The Right Way" AND text:go 또는 title:"Do it right" AND right 

text는 디폴트 필드이기 때문에 필드 지시어(indicator)는 반드시 입력될 필요가 없다.

필드는 바로 뒤에 따르는 term에 대해서만 유효하기 때문에, title:Do it right라고 질의를 입력하는 경우, 이는 title 필드에서 "Do"만을 찾게 된다. 나머지 "it"과 "right"는 디폴트 필드에서 찾게 된다.

Term Modifiers
Lucene은 보다 광범위한 검색 옵션을 제공하고 있다.

와일드카드 검색

Lucene은 단일 문자 와일드카드 검색과 다수 문자 와일드카드 검색을 지원하고 있다. 단일 문자 와일드카드 검색에서는 "?" 기호를 사용하며, 다수 문자 와일드카드 검색에서는 "*" 기호를 사용한다.

단일 문자 와일드카드 검색의 예: "text"나 "test"를 검색하려면, te?t의 형태로 검색할 수 있다. 다수 문자 와일드카드 검색의 예: "test", "tests", "tester"를 검색하려 한다면, test* 형태로 검색한다. 

와일드카드는 te*t의 형태와 같이 term의 중간에 사용할 수도 있다. 그러나, *나 ? 기호는 검색 첫 글자의 위치에 사용할 수 없다.


Fuzzy 검색

Lucene은 Levenshtein Distance 알고리즘이나 Edit Distance 알고리즘에 기반하여 fuzzy 검색을 지원한다. fuzzy 검색을 수행하려면, Single word Term의 뒤에 틸드 기호인 "~"를 사용한다. 예를 들어, "roam"과 유사한 스펠링을 가진 term을 검색하려는 경우, roam~ 과 같은 형태를 사용한다. 이렇게 검색하게 되면 foam, roams와 같은 term들을 검색하게 된다.


Proximity 검색

Lucene supports finding words are a within a specific distance away. proximity 검색을 수행하려면 Phrase의 뒤에 "~" 기호를 사용한다. 예를 들어, 한 문서 안에서 각각 10 단어 내에서 "apache"와 "jakarta"를 검색하려 한다면 "jakarta apache"~10과 같은 형태로 검색을 수행하면 된다.

Range 검색

Range 질의를 이용하면 필드의 값이 질의에 지정된 범위 내에 있는 값들과 일치하는 문서들을 찾아낼 수 있다. Range 질의에서는 최소값과 최대값을 포함하거나 제외한 검색 결과를 얻을 수 있다. 정렬은 사전 순서로 이루어진다.

 mod_date:[20020101 TO 20030101] 

이것은 20020101과 20030101을 포함하여, 이 범위 안에 있는 값들을 가진 mod_date 필드들을 포함하고 있는 문서를 검색하게 된다. Range 질의는 날짜 필드 검색에만 국한되지 않는다. 다음과 같이 날짜가 아닌 필드들에 대해서도 가능하다.

title:{Aida TO Carmen} 

이것은 Aida와 Carmen 범위 내에 있는 title을 갖는 문서들을 찾는다. 이 때, Aida와 Carmen은 포함되지 않는다.

최소값과 최대값을 포함한 Range 검색은 [ ] 기호를 사용하며, 포함하지 않는 Range 검색은  { } 기호를 사용한다.


Boosting a Term

Lucene은 발견된 term들을 기반으로 문서가 일치하는 정도를 판단하는 기능을 제공한다. term을 boost하려면, 검색하려는 term의 끝에 boost factor (숫자)와 함께 캐럿 기호 "^"를 사용한다. boost factor가 높을수록, term과의 관련성이 더 높아진다.

Boosting을 사용하여 문서의 term을 boosting함으로써, 문서의 관련성을 제어할 수 있다. 예를 들어, jakarta apache를 검색하려 하는데, "jakarta"라는 term이 보다 더 중점을 두어 검색하려 한다면, jakarta4 apache와 같은 형태로 term 다음에 기호와 함께 boost factor를 사용하면 된다.

이는 jakarta라는 term을 가진 문서가 보다 더 연관성이 높은 것으로 만들어준다. "jakarta apache"^4 "jakarta lucene"과 같이 Phrase term에 대해서도 boost할 수 있다.

디폴트로 boost factor는 1이다. boost factor는 반드시 양수여야 하지만, 0.2와 같이 1보다 작을 수는 있다.

Boolean 연산자
Boolean 연산자를 사용하면 논리 연산자를 통해 term들을 조합할 수 있다. Lucene은 AND, "+", OR, NOT, "-"을 Boolean 연산자로 지원한다. Boolean 연산자들은 반드시 대문자여야 한다.

OR
OR 연산자는 디폴트 결합 연산자이다. 즉, 두 term 사이에 어떠한 Boolean 연산자도 존재하지 않는다면, OR 연산자가 사용된다. OR 연산자는 두 term들을 연결하여, 이 term들 중 하나라도 문서에 존재하는지에 대해 검색한다. 이는 합집합 연산과 동일하다. OR라는 단어 대신 기호 ||를 사용할 수도 있다.

"jakarta apache"나 "jakarta"만을 포함하고 있는 문서들을 검색하려면, "jakarta apache" jakarta나 "jakarta apache" OR jakarta와 같은 형태의 질의를 사용하면 된다.

AND
AND 연산자는 두 term을 모두 갖는 문서들을 검색한다. 이는 교집합 연산과 동일하다. AND라는 단어 대신 기호 &&를 사용할 수도 있다.

"jakarta apache"와 "jakarta lucene"을 포함하고 있는 문서들을 검색하려면, "jakarta apache" AND "jakarta lucene"과 같은 형태의 질의를 사용하면 된다.

+
"+"가 표시된 요소는 반드시 포함되어 있어야 한다는 것을 의미한다. "jakarta"는 반드시 포함해야 하며 "lucene"은 포함될 수도 있는 문서들을 검색하려면, +jakarta apache와 같은 형태의 질의를 사용하면 된다.

NOT
NOT 연산자는 NOT 다음에 나온 term을 포함한 문서들은 배제한다. 이는 차집합 연산과 동일하다. NOT 단어 대신 ! 기호를 사용할 수 있다.

"jakarta apache"는 포함하고 있지만 "jakarta lucene"은 포함하지 않는 문서들을 검색하려면, "jakarta apache" NOT "jakarta lucene"과 같은 형태의 질의를 사용하면 된다.

NOT 연산자는 한 term에만 사용될 수 없다. 예를 들면, NOT "jakarta apache"와 같이 사용할 수 없으며, 이는 어떠한 결과도 반환하지 않을 것이다.

-
"-" 연산자는 "-" 기호 다음에 있는 term을 포함하고 있는 문서들은 제외한다. "jakarta apache"는 포함하지만 "jakarta lucene"은 포함하지 않는 문서들을 검색하려면 "jakarta apache" -"jakarta lucene"과 같은 형태의 질의를 사용하면 된다.

Grouping
Lucene에서는 서브 질의를 형성하기 위해 괄호를 사용하여 절들을 묶을 수 있다. 이는 boolean 로직을 제어하려는 경우 유용할 수 있다.

"jakarta" OR "apache" AND "website"를 검색하려면 (jakarta OR apache) AND website라는 형태의 질의를 사용하면 된다.

Field Grouping
Lucene에서는 여러 절들을 하나의 필드로 묶는데 괄호를 사용할 수 있다.

"return"이라는 단어와 "pink panther"이라는 phrase를 모두 포함하고 있는 title을 찾으려면, title:(+return +"pink panther")와 같은 질의를 사용하면 된다.

Escaping Special Characters
Lucene에서는 질의에 특수 문자를 escaping하는 기능을 지원해준다. 이들 특수 문자들은 다음과 같다.

+ - && || ! ( ) { } [ ] ^ " ~ * ? :

이들 특수 문자들을 escape 하려면, 문자 앞에 를 붙인다. 예를 들면, (1+1):2를 검색하려면 (1+1):2와 같은 질의를 사용하면 된다.

관련 아티클 및 자료

Lucene Home http://jakarta.apache.org/lucene/
Lucene FAQ http://lucene.sourceforge.net/cgi-bin/faq/faqmanager.cgi
Lucene Getting Started http://jakarta.apache.org/lucene/docs/gettingstarted.html
고수로 가는 지름길 Jakarta Project 가메출판사 최범균 저
QueryParser Rules http://today.java.net/pub/a/today/2003/11/07/QueryParserRules.html
Give your Web site its own search engine using Lucene http://builder.com.com/5100-6389-5054799.html
Lucene Intro http://today.java.net/pub/a/today/2003/07/30/LuceneIntro.html
Parsing, indexing, and searching XML with Digester and Lucene http://www-106.ibm.com/developerworks/library/j-lucene/
Advanced Text Indexing with Lucene http://www.onjava.com/pub/a/onjava/2003/03/05/lucene.html
Introduction to Text Indexing with Apache Jakarta Lucene http://www.onjava.com/pub/a/onjava/2003/01/15/lucene.html
The Lucene search engine Powerful flexible and free http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene.html

다운로드

Lucene 바이너리 다운로드 http://jakarta.apache.org/site/binindex.cgi
Lucene 소스 다운로드 http://jakarta.apache.org/site/sourceindex.cgi






Lucene 데모 실행

Lucene 배포본을 다운로드 받으면 그 안에 데모가 들어 있다. 여기에서는 이 데모를 실행하는 방법에 대해 살펴보도록 하겠다.

1. 우선 Lucene의 데모를 실행하기 위해서는 압축이 풀린 디렉토리에 있는 lucene-1.4-final.jar 파일과 lucene-demos-1.4-final.jar 파일이 클래스패스에 잡혀 있어야 한다.

2. 인덱스를 생성한다. 데모의 인덱스를 구성하는 어플리케이션의 이름은 IndexFiles로서, 이는 org.apache.lucene.demo 패키지에 들어 있다. 다음 명령을 실행하게 되면 Lucene 배포본의 src라는 하위 디렉토리에 있는 Lucene에 대한 모든 소스 코드를 대상으로 인덱스를 구성하게 된다.
첫 번째 인자는 <Lucene 배포본의 압축을 푼 위치>src의 형태를 취한다.

java org.apache.lucene.demo.IndexFiles C:ApacheJakartalucene-1.4-finalsrc 

다음은 명령 프롬프트에서 클래스패스를 잡고 위 명령어를 실행한 결과 화면이다.




 
 
3. 위 과정이 성공적으로 이루어졌다면 인덱스 생성은 끝나게 된다. 위 예제를 실행 시킨 후 탐색기로 살펴보면 다음 화면과 같은 디렉토리와 파일이 생성된다.
 



 
 
4. 데모에서 생성된 인덱스를 검색하는 클래스는 SearchFiles로, org.apache.lucene.demo 패키지에 들어 있다.

java org.apache.lucene.demo.SearchFiles 

위 명령을 실행하게 되면 질의를 입력하도록 요청 받을 것이다. 여기에서 찾으려는 단어를 입력하고 엔터키를 누르면 검색된 결과를 보여준다. 결과 화면은 10개 단위로 보여주며, 결과를 보여준 후 다음 10개 항목을 볼 것인지 아닌지를 선택할 수 있다. 다음은 java와 vector에 대한 질의 실행 결과를 나타낸 화면이다.

 
 
 
 
 
 
 
 
 

Lucene 데모 소스 코드에 대한 설명

소스 코드의 위치
Lucene의 압축을 풀어 생성된 디렉토리에서 "src"라는 디렉토리에 "demo"라는 디렉토리가 있다. 이 디렉토리는 모든 Lucene 데모에 대한 루트 디렉토리이다. 모든 자바 소스는 이 디렉토리 아래의 org/apache/lucene/demo에 위치해 있다.

Indexing Files
앞에서 말한 디렉토리에 보면 인덱스를 생성하는데 사용된 IndexFiles.java를 볼 수 있을 것이다. 이것이 어떻게 동작하는지 살펴보도록 하자.

이 소스 코드에 있는 main 함수는 가장 먼저 IndexWriter의 인스턴스를 생성한다. 이 인스턴스는 "index"라는 문자열과 "StandardAnalyzer"라는 클래스의 새로운 인스턴스를 넘겨 받는다. "index"는 모든 인덱스 정보가 저장될 디렉토리 이름이다. 이 예제에서는 어떠한 경로 정보도 지정하지 않고 있기 때문에, 현재 디렉토리의 하위 디렉토리에 생성하는 것을 가정하고 있다.

IndexWriter는 인덱스들을 생성하는 역할을 담당하는 클래스이다. 이를 사용하려면, 이 클래스의 인스턴스를 생성할 때, 인덱스를 작성할 수 있는 경로 정보를 넘겨주어야한다. 이 예제의 경우, 이 경로에 인덱스가 존재하지 않는다면, 이를 생성하게 된다. 만약 해당 경로에 인덱스가 존재한다면, 그 인덱스를 갱신하게 된다. 또한, IndexWriter의 인스턴스를 생성할 때 org.apache.analysis.Analyzer의 인스턴스도 넘겨주어야 한다. 다음 코드는 이러한 작업을 수행한다.

IndexWriter writer = new IndexWriter("index", new StandardAnalyzer(), true); 

이 예제에서 사용한 Analyzer인 Stop Analyzer는 모든 문자열들을 소문자로 만들고 인덱스로부터 쓸모 없는(useless) 단어들을 걸러낸다. 여기에서 쓸모 없는 단어란, a, an, the와 같은 관사들과 검색에 필요없는 일반적인 단어들을 의미한다. 각각의 언어마다 서로 다른 규칙들이 존재하고 있다는 사실에 주의하며, 이들 각각의 언어에 적절한 analyzer를 사용해야 한다. Lucene는 현재 영어와 독일어를 위한 Analyzer들을 지원하고 있다.
(한국어, 중국어, 일본어와 같은 아시아권 언어에 대한 analyzer의 경우, Jakarta Lucene의 Sandbox에서 제공되는 analyzer를 사용하면 된다고 한다)

IndexFiles 소스 파일의 아래에 보면 indexDocs()라는 메소드가 있다. 이 메소드는 재귀적인 함수로, 지정한 하위 디렉토리를 돌아다니면서 FileDocument를 사용하여 Document 객체들을 생성한다. Document는 파일의 생성 일자, 위치 및 그 내용을 표현해주는 간단한 데이터 객체이다. 이들 Document의 인스턴스들은 IndexWriter에 추가된다. 다음은 indexDocs() 메소드에 대한 소스 코드이다.

public static void indexDocs(IndexWriter writer, File file) throws IOException { 
if (file.canRead()) {
if (file.isDirectory()) {
String[] files = file.list();
if (files != null) {
for (int i = 0; i < files.length; i++) {
indexDocs(writer, new File(file, files[i]));
}
}
} else {
System.out.println("adding " + file);
try {
writer.addDocument(FileDocument.Document(file));
} catch (FileNotFoundException fnfe) {
}
}
}
}

indexDocs()는 인자로 받은 File을 읽을 수 있는지 판별하고, 이것이 디렉토리인지 파일인지 검사한다. 만약 디렉토리라면 그 하위 디렉토리를 재귀적으로 검사해 나가며, 파일인 경우 IndexWriter의 addDocument() 메소드를 통해 인덱스에 Document를 추가한다. 이 때, addDocument() 메소드는 인자로 Document를 받는데, 이 예제에서는 FileDocument라는 유틸리티 클래스를 사용하고 있다. 이 유틸리티 클래스는 File로부터 Lucene Document 객체를 만들어준다.

FileDocument는 File 객체에 들어 있는 정보를 바탕으로 Document 객체를 만들어준다. Document 객체는 세 개의 필드로 File에 대한 정보를 표현한다. 이들 필드들은 다음과 같다.
  • path: 파일에 대한 경로명. 텍스트 값 저장(Stored), 토큰 처리(Tokenized) 필드이다.
  • modified: 파일의 최종 수정일. org.apache.jakarta.lucene.DateField에 의해 인코딩 된 Keyword 필드이다.
  • contents: 파일의 내용. Reader 필드이다.

Searching Files
SearchFiles는 검색을 수행한다. 이 클래스는 IndexSearcher, StandardAnalyzer, QueryParser와 상호 작용한다.
이 클래스의 main() 메소드에서는 먼저 검색을 수행하는 Searcher의 객체를 생성하고 질의를 분석하는데 사용될 Analyzer 객체를 생성한다. 다음은 이를 수행하는 코드이다.

Searcher searcher = new IndexSearcher("index"); 
Analyzer analyzer = new StandardAnalyzer();

query parser는 Index를 해석할 때 사용했던 analyzer와 동일한 analyzer를 사용한다. 다음 코드는 이러한 작업을 수행한다.

Query query = QueryParser.parse(line, "contents", analyzer); 

Query 객체는 QueryParser로부터의 결과를 포함하고 있는데, 이 Query 객체는 searcher로 넘겨진다. searcher의 결과들은 "Hits"라 불리는 Document들의 컬렉션에 담겨져 반환된다. Query를 지정하기 위한 syntax는 매우 다양하다. 이들에 대한 내용은 아래에서 간단하게 다룰 것이다.

Hits hits = searcher.search(query); 

그런 후, 이들 컬렉션을 순환하면서 그 안의 내용을 사용자에게 보여준다.

for (int start = 0; start < hits.length(); start += HITS_PER_PAGE) { 
int end = Math.min(hits.length(), start + HITS_PER_PAGE);
for (int i = start; i < end; i++) {
Document doc = hits.doc(i);

String path = doc.get("path");
if (path != null) {
System.out.println(i + ". " + path);
} else {
String url = doc.get("url");
if (url != null) {
System.out.println(i + ". " + url);
System.out.println(" - " + doc.get("title"));
} else {
System.out.println(i + ". " + "No path nor URL for this document");
}
}
}

if (hits.length() > end) {
System.out.print("more (y/n) ? ");
line = in.readLine();
if (line.length() == 0 || line.charAt(0) == 'n')
break;
}
}

요약

다음은 일반적으로 Lucene을 standalone 형태의 어플리케이션에서 사용하는 절차를 간단하게 정리한 것이다.

인덱스 만들기
인덱스를 만드는 절차는 다음과 같다.
  • IndexWriter 객체를 생성한다.
이 객체를 생성할 때는 인자로 세 개의 정보를 넘겨준다. 우선 첫 번째 인자는 인덱스를 생성할 경로를 설정한다. 두 번째 인자는 사용할 Analyzer의 인스턴스이다. Analyzer는 Lucene에서 다수 구현하여 제공하고 있다. 세 번째 인자는 기존의 인덱스가 존재한다면 갱신할 것인지, 아니면 다시 새로 만들 것인지에 대해 지정한다. 자세한 내용은 Lucene API 문서를 찾아보길 바란다.
  • 인덱싱 작업의 대상이 되는 경로로부터 디렉토리와 파일 정보를 읽어 들여, 디렉토리인 경우는 재귀적인 호출을 통해 하위 디렉토리까지 검색하도록 하며, 파일인 경우는 필요한 정보를 추출한 후, org.apache.lucene.Document 형태로 구성하고, IndexWriter의 addDocument() 메소드를 통해 이를 추가한다.

검색하기
  • Searcher 객체를 생성한다. 이 객체는 인덱스가 생성되어 있는 경로를 인자로 받아 추후 검색을 수행한다.
  • 분석에 사용할 Analyzer를 생성한다. 이 Analyzer는 인덱스를 생성할 때 사용했던 Analyzer의 종류와 같은 것이어야 한다. 만약 다른 것을 사용한다면 잘못된 검색 결과를 얻을 수 있다.
  • QueryParser의 parse() 메소드를 이용하여 Query 객체를 생성한다. parse() 메소드는 검색어, 인덱스 생성 시 작성했던 필드 명과 위에서 생성한 analyzer 객체를 인자로 받아 인덱스 정보를 검색한다.
  • QueryParser를 통해 얻어진 Query 객체로부터, 검색 결과를 담고 있는 Hits를 얻어온 후, Hits가 담고 있는 내용들을 보여준다.




예제 실행을 위한 절차

Indexing Files
이 단계에서는 웹 어플리케이션 예제에서 필요한 인덱스를 작성하게 된다. 우선은 lucene-1.4-final.jar 파일과 lucene-demos-1.4-final.jar 파일이 클래스패스에 잡혀 있어야 한다. 명령 프롬프트에서 {tomcat-home}/webapps의 하위 디렉토리 중 아무 곳으로나 이동하여 다음 명령어를 실행한다.

java org.apache.lucene.demo.IndexHTML -create -index C:ApacheJakartaTomcat-5.0luceneIndex .. 

이 명령어에서 뒤의 ..을 반드시 입력해야 한다. 그렇지 않으면 예외가 발생한다. 세번째 인자는 Tomcat이 읽고 기록할 수 있지만, 웹을 통해서는 접근이 불가능한 디렉토리이다. 이 디렉토리에는 생성된 인덱스가 저장된다. 데모 웹 어플리케이션에서는 이 위치에서 인덱스를 찾도록 설정하고 있다. 이를 실행하게 되면 톰캣의 webapps 디렉토리의 하위에 있는 모든 디렉토리에 존재하는 문서들에 대한 정보를 담고 있는 인덱스를 생성하여 지정된 인덱스 디렉토리에 작성하게 된다. 여기에서는 luceneIndex가 될 것이다.

다음은 이 명령을 실행한 결과와 앞에서 지정했던 인덱스 디렉토리의 모습을 담은 화면이다.

 




 
Deploying the Demos
Lucene을 다운로드 받아 압축을 푼 디렉토리에 보면 luceneweb.war라는 war 파일을 볼 수 있을 것이다. 이 파일을 {tomcat-home}/webapps 디렉토리로 복사하고 톰캣을 재시작한다. {tomcat-home}은 톰캣이 설치된 디렉토리이다. 이 과정을 마치고 나면 다음처럼 luceneweb이라는 웹 어플리케이션이 생성된 것을 볼 수 있을 것이다.
 


 
 
Configuration
톰캣 디렉토리에서 webapps/luceneweb 하위 디렉토리로 이동한다. 만약 존재하지 않는다면, 브라우저의 주소 창에서 "http://localhost:8080/luceneweb"을 입력하고 이동하면, 이 디렉토리가 생길 것이다.

이동한 디렉토리에 있는 configuration.jsp 파일을 편집한다. 이 파일 안에 보면 indexLocation이라는 String 형 변수가 있다. 이 변수는 인덱스가 생성된 디렉토리의 위치를 저장하게 된다. 이 값이 이전에 지정했던 위치와 동일한지 확인한다.

appTitle과 appFooter String 형 변수도 원하는 값으로 설정할 수 있다. configuration.jsp 설정을 끝마쳤다면 톰캣을 재시작한다.

Running the Demos
브라우저에서 http://localhost:8080/luceneweb 을 입력하고 "test"와 페이지 당 보여질 항목의 개수를 입력한 후 search를 누른다.

이렇게 하면 몇 개의 결과들을 볼 수 있게 될 것이다. 다른 terms들도 검색해 보아라. 설정된 페이지 당 항목의 개수 및 반환된 결과에 따라, 화면 아래 쪽에 "more results>>"라는 링크가 보일 수 있다. 이를 클릭하게 되면 다음 페이지로 이동하게 된다. 만약 인덱스를 열 때 에러가 발생한다면 이는 "configuration.jsp"를 올바르게 설정하지 않았거나, 톰캣에서 접근이 허용되지 않은 디렉토리를 인덱스 디렉토리로 잡았을 가능성이 크다.

다음은 "jsp"라는 단어를 검색한 결과 화면이다.



 

참고 사이트 : http://blog.naver.com/NBlogMain.nhn?blogId=okaydanky&Redirect=Dlog&Qs=%2Fokaydanky%2F5815700&


|


  ( 월간 마이크로 소프트웨어 9월 연재 )

 

서문
지난 호에서는 lucene의 소개와 함께 기본적인 내용에 대해서 다루어 보았다. 이번 호에서는 Apache Lucene 의 코어 클래스 위주로 깊이 있게 다루고자 한다.

Lucene의 핵심 요소인 Analyzer와 인덱스 튜닝 그리고 고급 검색기법 등에 대해 다루어 보고자 한다. 또한 이슈가 되고 있는 여러 문제점과 해결방안에 대해서도 같이 알아보자.

 

1. Analyzer
지난 회에선 Apache Lucene이 기본적으로 제공하는 4가지 built-in Analyzer에 대해 살펴 보았다. 이번 회 에서는 Analyzer에 대해 좀 더 상세히 살펴보고 Analyzer를 커스터마이징 하고 직접 작성해 보자. Lucene API의 org.apache.lucene.analysis 패키지를 보면 4개의 built-in Analyzer ( StopAnalyzer, SimpleAnalyzer, WhitespaceAnalyzer, StandardAnalyzer ) 와 함께 Token, Tokenizer, TokenStream, TokenFilter 등이 있다.
이중 주의 깊게 살펴 보아야 할게 Tokenizer 와 TokenFilter 인데 이 두 클래스가 Analyzer를 구성하는 핵심 요소가 된다. Tokenizer 와 TokenFilter는 모두 TokenStream을 상속받은 자식 클래스 로서 input 값을 입력 받아 Token 단위로 다양한 처리를 한다. 이 두 클래스의 차이점으로는  Tokenizer는 개별 문자(characters) 단위로 데이터를 처리하고 TokenFilter는 단어(words) 단위로 처리를 하며 Tokenizer는 Reader 타입의 입력 값을 받아서 처리하는 반면 TokenFilter는 아래 <그림1>의 클래스 계층 구조도 에서 짐작 할 수 있듯이 부모 객체인 TokenStream 형태로 입력 값을 받아서 사용한다. <그림1>은 lucene 에서 사용되는 TokenStream의 클래스 계층 구조도 인데 클래스 명만 봐도 어떤 역할을 하는지 대략 상상 할 수 있을 것 이다. 각각의 자세한 사용법이나 설명은 API를 참고 하도록 하고 이제 이러한 TokenStream을 조합해서 간단한 Analyzer를 작성해 보도록 하자.
실행 가능한 전체 소스는 '이달에 디스크'를 통해 확인할 수 있으며, <리스트1>에서는 핵심 코드만 추출하였다. TestAnalyzer 라는 새로운 Analyzer를 정의 하기 위해서 부모 클래스인 Analyzer를 상속 받고 tokenStream() 메소드를 재정의 해주면 된다.
이때 앞서 설명한 TokenFilter의 강력한 메커니즘을 엿볼 수 있는데 <리스트1>에서 tokenStream() 메소드 부분을 보면 다양한 Filter들을 TokenStream result = new LowerCaseFilter(result) 와 같은 방법으로 손쉽게 적용 하였다. 이는 TokenFilter가 TokenStream을 상속받으면서 입력값으로 TokenStream을 받아서 사용하기에 가능한 일이다. 전체 소스에서는 StandardToekenizer, StandardFilter, LowerCaseFilter, StopFilter 등을 사용하였다.






















<그림1> TokenStream의 클래스 계층 구조도

<리스트1> TestAnalyzer.java

public class TestAnalyzer extends Analyzer {
…..
    public TokenStream tokenStream(String fieldName, Reader reader){
         TokenStream result = new StandardTokenizer(reader);
         result = new StandardFilter(result);
         result = new LowerCaseFilter(result);
         return result;
     }
    …..
}


2. 문서의 파싱

지난 호에 소개한 간단한 인덱싱 예제(이달의 디스크 SimpleIndex.java) 에서는 단순히 입력받은 문자열을 분석하여 색인화 하는 과정을 거쳤다. 하지만 실전에서는 이와 같은 단순한 문자열 입력 보다는 다양한 문서를 색인화 하는 검색 하는 작업이 더 빈번할 것이다.  XML , PDF, HTML, MS WORD 와 같이 다양한 문서들을 색인화 하기 위해서는 <그림2> 에서와 같이 각각의 문서를 Lucene의 Analyzer가 이해할 수 있도록 해석(parse)해서 텍스트로 추출해 내는 과정이 필요하다. Lucene 패키지 안에도 편의를 제공하기 위한 클래스가 몇몇 존재하기는 하지만 아무래도 외부 서드파티 라이브러리에 의존적일 수 밖에 없다.  이번 연재는 색인화  과정의 이해와 튜닝에 주 초점을 두고 있으니 다양한 라이브러리에 대한 상세한 설명은 생략하기로 한다. 단  아래 <표1>에 이들 라이브러리에 대한 목록과 참고 사이트를   분류해 놓았으니 참고 하도록 하자.




































<그림2> 문서의 색인화 과정


XML

Dom, Sax, JDom,

Piccolo (http://piccolo.sourceforge.net)

Apache Disester (http://jakarta.apache.org/commons/digester/)

PDF

PDFBox (http://www.pdfbox.org), IndexFiles (lucene built-in) ,

LucenePDFDocument (lucene built-in)

Xpdf (http://www.foolabs.com/xpdf)

JPedal (http://www.jpedal.org)

Etymon PJ( http://www.etymon.com)

Html

Jtidy (http://jtidy.sourceforge.net ),
NekoHTML (http://people.apache.org/~andyc/neko/doc/index.html)

HTMLParser( http://htmlparser.sourceforge.net)

MS Word

POI (http://jakarta.apache.org/poi )

Text Extractors( http://textmining.org )

Antiword ( http://www.winfield.demon.nl)

OpenOffice SDK ( http://www.openoffice.org )

<표 1> 문서 파싱을 위한 라이브러리


3. 인덱스 오퍼레이션

지난 연재에서는 간단한 색인화 과정을 거처 인덱스 파일을 생성 시켜 보았었고 이번 단원에서는 이미 생성된 인덱스 파일에서 내용을 추가,수정,삭제 하는 법에 대해 알아보도록 하자. Lucene 에서 인덱스 파일은 바이너리 형태로 존재해 개발 언어나 플랫폼에 구애 받지 않으므로 여러 가지로 상당히 편리하다. 가령 자바 언어로 작성된 서버 프로그램과 윈도우 기반의 클라이언트 프로그램 과의 인덱스 파일을 공유하거나 동기화 작업등을 처리 할 때 상당한 이점이 있을 것이다. <리스트2> AppendIndex.java 예제는 인덱스 파일에 색인을 추가하는 예제 이다. 지난호에 소개한 SimpleIndex.java와의 차이점 이라면 단지 (1) 번 라인에서와 같이 IndexWriter 객체 생성시 생성자의 세번째 인자값에 true 대신 false를 사용한다는 것 뿐이다. True 일때는 인덱스 파일을 새로 생성하거나 덮어 쓰지만 false이면 기존 인덱스 파일에 Document를 추가하게 된다.  그리고 인덱스 파일을 읽거나 수정 또는 삭제 시 편의를 위해 (2)번 라인과 같이 keyword 필드를 추가하였다. 그럼 이제 생성된 인덱스 파일을 이용해 수정과 삭제 처리를 해보자. 수정과 삭제 처리를 위해서는 Lucene 패키지에 포함된 IndexReader(org.apache.lucene.index.IndexReader) 클래스를 사용하는데 delete() 메쏘드 외에도 여러가지 편리하고 직관적인 메쏘드들을 많이 제공하므로 API를 한번쯤 찾아 보는 것도 좋을 것이다. <리스트3>은 삭제 예제인데 (1)번 라인 에서와 같이 검색 Term 객체를 인자로 받아서 삭제하거나 (2)번 라인과 같이 keyword 필드의 id값을 이용해 삭제하는 것도 가능하다.  다만 한가지 기억하고 넘어갈 사항이 있는데 IndexReader 의 delete() 메소드는 Document를 즉시 삭제 하지 않고 '삭제' 상태로 마크 처리했다가 IndexReader 객체의 close() 후 IndexWireter 객체에 의해 인덱스 파일이 merge 된 후에야 완전히 삭제 처리 되므로 이미 수행한 명령을 롤백(undelete() 메쏘드)하거나 최종확정(commit() 메쏘드) 하는 것도 가능하다. '이달의디스크' 에서 전체 소스를 받아 실행 시켜 보면 시스템 로깅을 통해 확인 가능할 것이다. 그리고 인덱스의 업데이트 과정은 이미 소개한 삭제와 추가 예제의 조합이므로 굳이 추가적인 설명을 하지는 않겠다.



<리스트2>  AppendIndex.java
…..
private void index() throws IOException {
 Directory dir = FSDirectory.getDirectory("디렉토리경로", true);
 Analyzer analyzer=new WhitespaceAnalyzer();
 IndexWriter writer = new IndexWriter(dir, analyzer, false); ---------(1)

 for (int i = 10; i < 20; i++) {
  Document doc = new Document();
               doc.add(Field.Keyword("id",i+"")); ------------------(2)
  doc.add(Field.Text("title", "title is …"));
  doc.add(Field.Text("content", "content is…."));
  writer.addDocument(doc);
 }
 writer.optimize();
 writer.close();.
}
…..


<리스트3> DeleteIndex.java

private void deleteIndex() throws IOException {
  String dirPath="인덱스 파일 경로";
        IndexReader reader=IndexReader.open(dirPath);

        reader.delete(new Term("title","apache")); --------------(1)
        reader.delete(1); -----------------------(2)
reader.close();
    }
…..



4. 인덱스 튜닝

소위 검색 엔진이라 불리는 편리한 도구의 뒷면 에는 색인화 라는 무시무시한 괴물이 존재하고 있다. 필자의 경우엔 약 8천만건 정도 되는 DB데이터를 Lucene을 이용해 색인화 작업을 한적이 있는데 엄청난 Disk용량은 물론 이거니와 색인화 작업이 완료될 때 까지 걸리는 그 지루하고도 무지막지한 시간을 생각하면 아직도 치가 떨릴 지경이다. 이번 단원 에서는 대용량의 색인화 작업을 위해 알아둬야 할 몇 가지 사항에 대해 소개하고자 한다.  우선 Lucene을 이용한 색인화 작업에서 병목현상이 가장 빈번하게 발생하는 부분은 바로 Disk에 인덱스 파일을 쓰는 작업 일 것 이다. 몇 건 안되는 문서의 색인화 작업시 에는 디폴트 설정을 그대로 사용해서 색인화 해도 별 무리가 없지만 대량의 색인화 작업시 이러한 병목 현상을 줄이기 위해 IndexWriter 클래스는 <표2>와 같이 몇 가지 멤버 변수 설정을 제공한다.  인덱스 튜닝을 위한 첫번째 요소로 mergeFactor 가 있다. mergeFactor는 Disk에 쓰기전 얼마나 많은 문서를 메모리에 저장할 것인가에 대한 요소이며 또한 인덱스 Segment 를 얼마나 자주 병합(merge) 시킬 것인가를 결정 짓는다. 디폴트 값이 10 이므로 별다른 설정을 하지 않으면 1개의 segment를 쓰기 전에 10개의 Document를 메모리에 저장 하게 되며, 10개의 segment를 10배 용량을 가지는 하나의 segment 로 병합 가능하게 한다. 그리고 두번째 요소인 maxMergeDocs 는 한 segment 내에 담을 수 있는 Document 개수를 제한한다. 만약 mergeFactor의 값을 10000으로 하고 maxMergeDocs의 값을 1000으로 한다면 1000개의 Document를 포함 하는 segment 10개가 인덱싱 작업의 결과로 남게 된다.   이처럼 maxMergeDocs 를 크게 설정하면 인덱싱 시간은 줄어 들지만 대신 segment 파일의 개수가 많아 지므로 검색 작업시 불필요한 처리 시간을 소모하게 된다. 따라서 mergeFactor를 크게 잡은 경우엔 IndexWriter의 optimize() 메쏘드를 사용해 여러 segment를 병합시켜 주는 것이 좋다.  마지막으로 minMergeDocs 는 segment에 담길 Document에 대해 버퍼 처리를 얼마나 할지를 결정한다. 기본값이 10이므로 10개의 Document 를 버퍼링 해서 segment에 쓰게 된다. 얼핏 보면 mergeRactor와 비슷한 것 같지만 minMergeDocs 는 RAM 메모리를 사용해 버퍼링할 개수 만을 지정하며 Segment의 사이즈나 처리 개수에 대해서는 관여하지 않는다.
<리스트4> 는 mergeFactor, maxMergeDocs, minMergeDocs를 적용한 IndexTuning 샘플 예제이며 <리스트5>는 출력된 실행결과 이다. 개인 컴퓨터의 사양에 따라 결과가 조금씩 다르겠지만 각 요소의 적용시 minMergeDocs 를 크게 잡아줄 때가 가장 속도가 빠른 것을 확인할수 있을것이다.
java maso.lucene.indexing.IndexTuning mergeFactor maxMergeDocs minMergeDocs 와 같이 실행 가능하며, 예외 처리가 되지 않은 간단한 테스트 용이므로 해당 파라메터를 넘겨주지 않고 실행하면 에러가 발생한다.


 

변수명

속성 명

디폴트 값

설명

mergeFactor

Ogr.apache.lucene.mergeFactor

10

Segment가  merge되는  빈도수와 사이즈를 컨트롤 한다.

maxMergeDocs

Org.apache.lucene.maxMergeDocs

Integer.MAX_VALUE

하나의 segment에 담길 Document 의 개수를 제한한다.

minMergeDocs

Org.apache.lucene.minMergeDocs

10

색인 처리시 버퍼링에 사용될 RAM의 사이즈를 컨트롤 한다.

<표2> 인덱스 퍼포먼스 튜닝을 위한 요소들

 

<리스트4> IndexTuning.java

public class IndexTuning {
 private void index(int mergeFactor_i, int maxMergeDocs_i, int minMergeDocs_i) throws IOException {
……
  Analyzer analyzer=new WhitespaceAnalyzer();
  IndexWriter writer = new IndexWriter(dir, analyzer,true);

          writer.mergeFactor=mergeFactor_i;
          writer.maxMergeDocs=maxMergeDocs_i;
          writer.minMergeDocs=minMergeDocs_i;
 ……
System.out.println("소요 시간: "+(endTime-startTime)+" ms");
 }

 public static void main(String[] args) throws IOException {
  IndexTuning indexTunning = new IndexTuning();
  indexTunning.index(Integer.parseInt(args[0]),Integer.parseInt(args[1]),Integer.parseInt(args[2]));
 }
}


<리스트5> IndexTuning.java 실행 결과

mergeFactor = 10
maxMergeDocs = 9999
minMergeDocs = 10
소요 시간: 54407 ms


mergeFactor = 100
maxMergeDocs = 9999
minMergeDocs = 10
소요 시간: 44312 ms


mergeFactor = 10
maxMergeDocs = 9999
minMergeDocs = 100
소요 시간: 9938 ms


mergeFactor = 100
maxMergeDocs = 9999
minMergeDocs = 100
소요 시간: 8453 ms


5. 이슈 및 문제 해결


system lock
Lucene은 기본적으로 read시엔 락이 없지만 create,update,delete 시엔 항상 시스템 락을 건다 ( /temp/lucene-xxxxx.lock )  일련의 작업이 끝난후 IndexWriter가 close 될때 락을 해제하게 되는데 제대로 close 되지 않았거나 중간에 에러가 발생할 경우엔 Rock 이 해제되지 않은 채로 존재하기 때문에 옵티마이징이나  기타 다른 작업을 할수가 없다. 따라서 이때는 해당 락을 직접 삭제해줘야 한다.
(예외 : java.io.IOException: lock obtain timed out  ...  C:\temp\lucene-xxxxxxxxxxxx.lock )

최대 파일 열기 개수 초과 오류 ( Too many open files Exception )
Lucene을 이용해 검색 작업을 하다 보면 종종 '최대 파일 열기 개수 초과' 라는 예외상황이 발생하곤 한다. 이때 가장 먼저 체크 해봐야 할 곳은 인덱스 파일이 생성된 디렉토리 이다. 인덱스 파일이 수많은 segment 로 구성되어 있다면 IndexWriter 의 optimize() 를 이용해서 하나의 segment로 수정해줘야 한다. 인덱스 옵티마이징 이후에도 똑 같은 에러가 계속해서 발생 된다면 인덱스 파일을 읽어 들이는 IndexReader 객체가 사용 후 제대로 반환 되는지 체크해 볼 필요가 있다. Lucene의 IndexReader 는 쓰레드에 안전 하므로 풀링 기법 을 사용하거나 싱글톤 패턴을 적용시켜 사용하는 것이 좋다.
이런 저런 문제도 아닌 경우엔 OS에서 허용 가능한 파일 오픈 개수를 늘려줘야 한다.


비 영어권 문자의 검색
한글,중국어,일어 와 같은 아시아권 문자의 검색을 위해서는 utf-8 인코딩을 사용하면 비교적 간단 하게 해결 되지만 형태소 검색과 같은 기능은 구현하기 힘들게 된다. 이때는 lucene 의 sandBox에 포함된 CJK Analyzer를 사용하면 되는데 아래 링크에서 다운로드 받을 수 있다.
http://svn.apache.org/repos/asf/lucene/java/trunk/contrib/analyzers/src/java/org/apache/lucene/analysis/cjk/
CJKAnalyzer 적용에 관한 보다 상세한 자료는 윤용현님의 사이트인 jazzzvm.com 을 참고하자. ( http://www.jazzvm.net/board/view.jazz?code=864593&seq=827&currentPage=1&currentBlock=1 )
덧 붙여 CJKAnalyzer는 내부적으로 StopFilter를 사용하므로 다운로드 받은 CJKAnalyzer 의 멤버변수인 String[] STOP_WORDS 에 기본적인 불용어(StopWord)를 추가 해서 사용하는 것도 괜찮을 것이다.


6. Query Syntax (http://lucene.apache.org/java/docs/queryparsersyntax.html)


기본쿼리
tiele 필드와 text 필드에서 AND 검색을 하려면 아래 (1)과 같이 [필드명:검색어 AND 필드명:검색어] 와 같은 질의를 사용 가능하다. 디폴트 필드가 text 일 경우  (2)번 질의와 같이 text 필드를 생략 할 수 있다.
title:"The Right Way" AND text:go  -----------(1)
title:"Do it right" AND right ------------(2)


와일드카드 검색
? : 단일 문자 와일드카드
* : 다수 문자 와일드카드
와일드 카드에 사용되는 기호는 검색어의 중간이나 끝에 위치 가능하며 검색어의 시작 단어로는 사용할 수 없다.


Fuzzy 검색
roam~ 과 같은 검색 키워드를 사용하면  foam, roams와 같은 유사 검색을 한다.


Proximity 검색
한 문서 안에서 각각 10 단어 내에서 "apache"와 "jakarta"를 검색하려 한다면 "jakarta apache"~10과 같은 형태로 검색을 수행한다.


Range 검색
mod_date:[20020101 TO 20030101]
20020101과 20030101을 포함하여, 이 범위 안에 있는 값들을 가진 mod_date 필드들을 포함하고 있는 문서를 검색한다.
title:{Aida TO Carmen}
이것은 Aida와 Carmen 범위 내에 있는 title을 갖는 문서들을 찾는다. 이 때, Aida와 Carmen은 포함되지 않는다.

[] : 최소값과 최대값을 포함한 Range 검색
{} : 포함하지 않는 Range 검색


Boosting a Term
Lucene은 발견된 term들을 기반으로 문서가 일치하는 정도를 판단하는 기능을 제공한다. term을 boost하려면, 검색하려는 term의 끝에 boost factor (숫자)와 함께 캐럿 기호 "^"를 사용한다. boost factor가 높을수록, term과의 관련성이 더 높아진다.
jakarta ^4 apache :  Jakarta 에 boost 적용


Boolean 연산자
연산자들은 반드시 대문자여야 한다.

OR : 디폴트 결합 연산자.  기호 || 를 사용할 수도 있다.
AND :  교집합 연산자. 기호 &&를 사용할 수도 있다.
+ : 반드시 포함되어 있어야 하는 Term을 지정한다.  (ex: +jakarta apache) NOT : 차집합 연산자. ! 기호를 사용할 수 있다.
- : 이 연산자는 "-" 기호 다음에 있는 term을 포함하고 있는 문서들은 제외한다. "jakarta apache"는 포함하지만 "jakarta lucene"은 포함하지 않는 문서들을 검색하려면 "jakarta apache" -"jakarta lucene"과 같은 형태의 질의를 사용하면 된다.


Escaping Special Characters
아래와 같은 특수 문자를 escaping하기 위해 문자 얖에 역슬래쉬(\) 문자를 사용한다.
+ - && || ! ( ) { } [ ] ^ " ~ * ? : \
즉 (1+1):2를 검색하려면 \(1\+1\)\:2와 같은 질의를 사용한다.


|



  ( 월간 마이크로 소프트웨어 8월 연재 )



Apache Lucene은 Doug Cutting에 의해 순수 JAVA로 개발된 full-text 검색 엔진이다. 아파치 자카르타의 서브 프로젝트로 개발되어 오다 현재는 아파치 최상위 프로젝트로 승격되었으며, 너치(nutch)라는 자식 프로젝트 까지 갖춘 소위 대박난 오픈 소스 프로젝트 이다. 동급(아파치 프로젝트 레벨)의 다른 프로젝트에 비해 국내 개발자들 에겐  인지도가 무척 저조한 편이라 Lucene이 적용된 레퍼런스 조차 제대로 찾아보기 힘들 지만 Apache Lucene 프로젝트는 나날이 발전 되어서 현재는 C++, C#, Python, Perl 과 같은 여러 다른 언어로도 포팅 되어 널리 이용되고 있다.


------------------------------------------------------------------------------------------
너 치(nutch)는 lucene의 개발자인 Doug Cutting이 역시 수석 개발을 맡아 진행하는 lucene을 기반으로 한 오픈 소스 프로젝트로 구글과 같은 대형 검색 서비스사의 독점을 막고, 누구나 쉽게 사용하고 공유할 수 있는 오픈 소스 검색엔진을 만든다 라는 취지 하에 개발되게 되었고,  2005년 1월 아파치 인큐베이터 프로젝트에 소속되었다가 최근 탑 레벨 프로젝트인 lucene 의 서브 프로젝트로 승격 하게 되었다. (http://lucene.apache.org/nutch/ ) ------------------------------------------------------------------------------------------

1. Lucene의 탄생
Lucene 은 1997년 Doug Cutting의 개인 프로젝트로 시작된 그의 4번째(Xerox, Apple , Excite & Lucene) 검색 소프트웨어 이다 . 믿기지 않는 사실 이긴 이지만 그가 작성한 최초의 자바프로그램 이였다고 하니 수년간이나 자바공부에 전념을 해도 이렇다 할 진전이 없었던 우둔한 필자의 입장에선 부끄러움과 함께 절로 존경심이 생기지 않을 수 없다.
처 음 Lucene을 개발하던 당시에는 이 제품을 상용화 하려던 의도를 가지고 있었다고 한다. 하지만 곧 생각을 바꿔 sourceforge에 공개 함으로서 삽시간에 전세계 개발자에게 퍼지게 되었고  1년여 정도가 지나 아파치 재단에 채택되면서 Lucene은 말 그대로 개발의 날개를 달게 되었다. (실제로 Lucene의 로고는 날개 형상과 매우 흡사하게 생겼다.) 그리고 현재는 아파치 탑 레벨 프로젝트로 승격되었고, 여러 개발 언어로 번역되어 전세계 개발자에게 널리 퍼지면서 나중에 소개할 Luke 와 Limo 같은 서드파티(third-party) 툴까지 마구 양산 되면서 개발자를 날로 즐겁게 해주고 있다.

 

Version

Release date

이력

0.01

2000 3

최초 오픈소스 release ( sourceforge)

1.0

2000 10

 

1.01b

2001 7

마지막 sourceforge release

1.2

2002 6

Apache Jakarta release

1.3

2003 12

Compound index format, QueryParser 개선, remote searching, token positioning, extensible scoring API

1.4

2004 7

Sorting, span queries, term vectors

1.4.1

2004 8

버그 픽스( sorting performance)

1.4.2

2004 10

IndexSearcher optimization 과 기타 버그 픽스

1.4.3

2004년 겨울

기타 수정

<표 1> Lucene Release History



2. Lucene 의 활용
검 색엔진 이라 하면 아주 고가의 상용 솔루션을 먼저 떠올리던 시절이 있곤 했다. 하지만 Doug Cutting 과 여러 오픈 소스 개발자들의 노력으로 어느새 모든 개발자들은 문서를 Indexing 하고 Searching 하는 능력을 별다른 노고 없이 ( 솔직히 API를 살펴보는 최소한의 노고는 필요 할 것이다.) 갖출 수 있게 되었다. 이제 이 파워풀 한 능력을 어디에다 써먹을 수 있을까?  기본적인 문서 검색에서 시작해 이메일, CD컨텐츠, xml, 데이터베이스, 웹사이트 등등 무궁무진하게 많은 영역을 다룰 수 있을 것이다. 하지만 여기에도 한계는 있었다. 필자의 경우 공공기관 관련 SI프로젝트 에서 Lucene 검색 엔진을 도입 하려 했을 때 단지 오픈 소스 라는 이유로 혹은 지원이나 문제 발생시 책임 소지 등을 거론하며 냉대 받고 결국은 훨씬 성능이 떨어지면서 사용하기도 불편한 고가의 검색 엔진 솔루션을 구입해서 프로젝트를 진행했던 경험이 있었다.
그 후 다시 기회가 찾아 왔을 땐 구글의 데스크탑 검색과 Lucene.net (Lucene의 닷넷 버전)을 이용한 Microsoft의 email 검색 소프트웨어인  Lookout(그림1) 을 레퍼런스로 들면서 열심히 고객을 설득했고, 결국에 우리팀은 Lucene을 이용해 프로젝트에서 빈번하게 DB접속이 일어나 성능을 저하 시키는 모든 요소를 Lucene 검색엔진 으로 대처 하였고 대용량 DB의 like검색으로 인한 과부하를 적절하게 해소 할 수 있었다.


--Doug Cutting이 제시한 lucene의 인덱싱과 검색을 적용 가능한 일반적인 사례 ---
" 이메일 검색: 저장된 메시지를 검색할 수 있고 새로 도착한 메시지를 새인에 추가할 수 있는 이메일 애플리케이션.
" 온라인 문서 검색: 온라인 문서 또는 저장된 출판물을 검색할 수 있는 CD 기반이나 웹 기반 또는 애플리케이션에 포함된 문서 판독기(reader).
" 웹 페이지 검색: 사용자가 방문한 모든 웹 페이지를 색인화하기 위해 개인 검색 엔진을 만들 수 있는 웹 브라우저 또는 프록시 서버. 이것을 사용하여 쉽게 페이지를 다시 방문할 수 있다.
" 웹 사이트 검색: 웹 사이트를 검색할 수 있는 CGI 프로그램
" 내용 검색: 저장된 문서에서 특정 내용을 검색할 수 있는 애플리케이션. 내용 검색 기능은 문서 열기 대화상자에 통합될 수 있을 것이다.
" 버전 관리 및 컨텐트 관리: 문서나 문서 버전을 색인화해서 쉽게 검색할 수 있는 문서 관리 시스템.
" 뉴스 및 유선(wire) 서비스: 뉴스가 도착했을 때 기사를 색인할 수 있는 뉴스 서버나 릴레이 서버.






















< 그림1 > Lucene.Net 으로 개발된 Microsoft의 Lookout 을 설치한 outlook 화면





3. 인덱싱과 검색의 Core 클래스
이제 슬슬 본론으로 들어가 Lucene 검색 엔진을 살펴 보자. Lucene을 요리하기 위해 필요한 재료인 라이브러리와 api 는 http://lucene.apache.org 에서 구할 수 있다.
문서를 인덱싱 하고 검색하기 위해 필요한 핵심 클래스와 절차는 다음과 같다.

<인덱싱 요소>
  IndexWriter : 인덱스 파일을 생성하거나 수정(혹은 문서추가)하는 사용되는 클래스
  Directory : 인덱스 파일이 저장될 경로를 담는 클래스 이다. IndexWriter 객체의 생성자의 인자로 사용된다.
  Analyzer : 문서를 인덱싱 하는 과정에서 다양한 형태로 token을 분리하는 역할을 한다. 역시 IndexWriter 객체의 생성자의 인자로 사용된다.
  Document : Field의 조합으로 이루어진 하나의 문서. 데이터베이스 에서 여러 column으로 이루어진 1건의 row 와 비슷한 개념이다.
  Field : Document를 구성하는 단위. 데이터베이스에서 하나의 column과 비슷한 개념이다.


위에서 나열한 요소를 가지고 문서를 인덱싱 하기 위해서는 다음과 같은 순서를 따른다.
(1) 인덱스 파일이 저장될 경로 정보를 담는 Directory 객체 생성
(2) 인덱스 요소 분석을 위한 Analyzer 객체 생성
(3) Directory와 Analyzer 를 생성자의 인자로 IndexWriter 객체 생성
(4) Document 객체 생성 후 Document 객체에 필드 추가
(5) IndexWriter 객체에 Document 추가


<검색 요소>
  Searcher : 인덱스 파일을 read-only 모드로 열어서 검색하고 결과를 반환한다.
  Term : 검색의 기본 단위가 되는 클래스이며, 데이터 베이스 의 질의시 where name='maso' 와  같이  String 요소의 쌍으로 구성되어 있다.
  Query : 특정한 검색 포맷을 제공하는 클래스이다. 여러 구현체를 통해 다양한 검색 방법을 제공한다.
  TermQuery : Lucene이 제공하는 가장 일반적인 Query 클래스 이다.
  Hits : 검색결과를 담는 컨테이너 역할을 한다.

검색 절차
(1) 인덱스 디렉토리 경로를 인자 값으로 해서 Searcher 객체 생성
(2) 인덱스 요소 분석을 위한 Analyzer 객체 생성
(3) 검색을 위한 Query 객체 생성
(4) Searcher 객체의 search(Query query) 메쏘드를 호출하여 검색


4. 문서 인덱싱 및 검색 예제


<리스트1> 인덱싱 예제 (SimpleIndex.java)

package maso.lucene.indexing;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;

import java.io.IOException;

public class SimpleIndex {

 private void index() throws IOException {
  String dirPath =
    System.getProperty("java.io.tmpdir", "tmp") +
    System.getProperty("file.separator") + "simple-index";

  Directory dir = FSDirectory.getDirectory(dirPath, true);
  Analyzer analyzer=new WhitespaceAnalyzer();   ---------------(1)
  IndexWriter writer = new IndexWriter(dir, analyzer, true);

  for (int i = 0; i < 10; i++) {
   Document doc = new Document();
   doc.add(Field.Text("title", "Lucene 검색 엔진"));
   doc.add(Field.Text("content", "Lucene 의 소개 및 간단한 예제를 다룬다. "));
   writer.addDocument(doc);
  }
  writer.optimize();
  writer.close();
 }

 public static void main(String[] args) throws IOException {
  SimpleIndex si = new SimpleIndex();
  si.index();
 }

}


<리스트1>의 예제 코드는 인덱싱 작업을 하는 심플한 자바 프로그램 코드 이다. 지면상 핵심 코드만 추출하였지만 실행 가능한 전체 소스는 '이달의 디스크' 에서 찾아 볼수 있다.
이 예제에서 보는 바와 같이 Lucene을 이용해 인덱싱 하는 작업은 너무나도 간단하다. 먼저 인덱스 파일이 생성될 위치 정보를 담고 있는 Directory 객체와 Text 분석을 위한 Analyzer 객체를 생성 하고 이 두 객체를 생성자의 인자로 가지는 IndexWriter 객체를 이용해 Document를 담기만 하면 되는 것이다. 여기서 눈 여겨 볼 곳은 (1)번 표기가 된 Line의 Analyer 객체의 생성 부분이다. Lucene은 기본적으로 4개의 Built-in Analyzer 를 제공 하는데 이 예제에서 사용된 WhitespaceAnalyzer 와 StopAnalyzer, SimpleAnalyzer, StandardAnalyzer 등이 있다. 각각의 용도 및 특징은 다음 단원에서 좀더 세부적으로 알아보도록 하고, 여기에서 사용된 WhitespaceAnalyzer 가 공백 단위로 텍스트를 파싱 한다는 것 정도만 알고 넘어가자.  마지막으로 IndexWriter를 이용해 Document 를 저장한 후 프로그램을 반드시 호출해 줘야 하는 메소드가 있는데 예제 샘플의 마지막 두 라인에서 와 같이  optimize() 메쏘드와 close() 메소드가 있다. Close() 메쏘드는 index 파일의 변경된 내용을 적용시키고 관련된 모든 파일을 닫는다. 그리고 optimize() 메소드는 생성된 여러 인덱스 요소들을 하나로 묶는 기능을 수행한다. Optimize() 부분은 인덱스 튜닝과 연관되어 복잡하고 많은 내용을 담고 있으므로 다음 연재 에서 좀더 상세하게 다룰 것이다. '이달의 디스크'에서 전체 소스를 받아서 실행시켜 보면 시스템의 Temp 디렉토리( 일반적으로 C:\tmp )에 인덱스 파일이 생성되는 것을 볼 수 있을것이다.


<리스트2> 인덱스 검색 예제 (SimpleSearcher.java)


package maso.lucene.searching;

import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.queryParser.ParseException;

import java.io.IOException;

public class SimpleSearch {

 public Hits search() throws IOException, ParseException {

  String dirPath =
    System.getProperty("java.io.tmpdir", "tmp") +
    System.getProperty("file.separator") + "simple-index";

  Searcher searcher = new IndexSearcher(dirPath);
  Analyzer analyzer=new SimpleAnalyzer();

  String queryString="검색";
  String defaultField="title";
  Query query = QueryParser.parse(queryString,defaultField,analyzer);  ----------(1)

  System.out.println("query=="+query.toString());
  Hits hits= searcher.search(query);  ---------------------(2)

  searcher.close();
  return hits;

 }


 public static void main(String[] args) throws IOException, ParseException {

  SimpleSearch ss=new SimpleSearch();
  Hits hits=ss.search();
        int ctn=hits.length();

  System.out.println(ctn+"건의 문서가 검색 되었습니다.\n");
  for(int i=0;i<ctn;i++){
   System.out.println(i+"번째 : "+hits.doc(i).get("title"));
  }

 }
}


<리스트 2> 는 <리스트1>과 마찬가지로 인덱스 파일을 검색하는 자바 프로그램의 핵심 코드만 추출한 예제 이다. 검색 과정을 살펴 보면 먼저 인덱스 파일 경로 정보를 가진 IndexSearcher 객체를 생성하고 (Searcher 클래스는 IndexSearcher 와 MultiSearcher, ParallelMultiSearcher 등이 있다.)  (1) 표기 가 있는 라인 에서와 같이 검색할 query를 생성해서 (2)표기 라인에서 Hits 객체에 query 검색 결과를 담는다. (1)번 라인에서 QueryParser 에 의해 생성된 query는 Query 객체의 toString() 메쏘드를 호출하면 출력해볼수 있는데 query= ' title: 검색' 과 같은 단순한 구조 이다. 사족을 덧붙일 필요도 없겠지만 title 필드에서 '검색' 이라는 단어를 포함하는 문서를 찾는다 라는 의미 이다. 검색을 위해 필요한 핵심 클래스인 Searcher 와 Query 에 대한 내용도 다음 연재에서 보다 상세하게 다룰 것이다. 인덱싱 예제와 마찬가지로 검색 프로그램 에서도 반드시 Searcher 객체를 close 시켜야 하지만 Searcher 객체는 스레드에 안전하므로 성능을 위해서 오브젝트 풀링 기법을 사용하거나 싱글톤 패턴을 적용해 close 시키지 않고 재사용 하는 것도 무방하다.
<리스트2> 예제에서는 마지막 라인에서 Hits 객체를 return 시키고 끝나는데 '이달의 디스크'를 통해 전체 소스를 받아 보면 반환된 Hits 객체에서 검색된 문서의 수와 검색결과를 출력하는 예제를 볼 수 있을 것이다.


<부가설명2>

----------------------------------------------------------------------------------------------
오브젝트 풀링과 싱글톤 패턴에 대한 내용은 최범균님의 javacan 사이트에 방문 하시면 잘 정리된 기사를 찾아 보실 수 있습니다. 
오브젝트 풀링 : http://javacan.madvirus.net/main/content/contentRead.jsp?contentNo=7&block=4
싱글톤 패턴 : http://javacan.madvirus.net/main/content/contentRead.jsp?contentNo=5
----------------------------------------------------------------------------------------------------------


5. Lucene 의 built-in Analyzer


Luene에서 Analyzer는 크게 2가지 용도로 사용되는데 첫번째는 인덱싱 작업에서 문서를 필드 형태로 나누는데 사용이 되며, 두번째 용도로는 검색시 쿼리를 파싱하는데 사용된다. Lucene에서 이미 만들어진 4가지 built-in Analyzer를 제공 하는데 WhitespaceAnalyzer, StopAnalyzer , SimpleAnalyzer  그리고 StandardAnalyzer 등이 있다. 아래 <표2>을 통해 각각의 특징에 대해서 살펴 보자.


Analyzer

특징

WhitespaceAnalyzer

스페이스를 구분으로 token을 분리한다.

WhitespaceTokenizer 사용

SimpleAnalyzer

Letter를 구분으로 token을 분리한다.

LetterTokenizer LowerCaseFilter 사용

StopAnalyzer

Letter를 구분으로 token을 분리하고 , 중지단어를 token에서 제거한다.

LetterTokenizer , LowerCaseFilter , StopFilter 사용

StandardAnalyzer

다양한 문법기반으로 token을 분리 가능하지만 비영어권 문자는 그대로 사용할 수 없고 커스터마이징이 필요하다.

StandardTokenizer, StandardFilter, LowerCaseFilter, StopFilter 사용

<표 2> Lucene의 built-in Analyzer

다음은 "AB&C 한글 aaa@gmail.com" 이라는 문장을 <표2>에 나온 각각의 Analzer로 인덱싱 한 결과 이다.


    a. WhitespaceAnalyzer : [AB&C]  [한글]  [aaa@gmail.com]
    b. SimpleAnalyzer : [ab]  [c]  [com]  [gmail]  [한글]  [aaa]
    c. StopAnalyzer : [ab]  [c]  [com]  [gmail]  [한글]  [aaa]
    d. StandardAnalyzer :  [ab&c]  [aaa@gmail.com]


그럼 이제 각각의 Analyzer에 대해 하나씩 알아보자. 먼저 WhitespaceAnalyzer 는 lucene의 4가지 built-in Analyzer 중 가장 심플한 Anaylzer 로서 단지 스페이스 단위로 token을 분리한다. 그 다음 SimpleAnalyzer는 Letter 단위로 문자를 나누기 때문에 공백이나 물론 특수문자는 제외되고 가장 많은 token으로 분리되며, LowerCaseFilter를 사용하므로 대문자는 모두 소문자로 변환된다. 나누어진 token 결과가 다른 Analyzer 보다 많으므로 인덱싱후 index파일의 크기 역시 가장 클 것이다. 그리고 다음 StopAnalyzer는 기본적으로 SimpleAnalyzer와 동일한 기능을 가지기 때문에 결과값 역시 동일하다. 다만 StopFilter를 사용해서 검색에 제외될 항목들 가령 and, an, if, else 같은 특정한 항목들을 지정해서 제외 시킬 수 있으므로 인덱싱이나 검색에 소요되는 시간과 인덱싱 파일의 용량을 효율적으로 줄 일수 있다. 마지막으로 StandardAnalyzer 가 있는데 다양한 문법 기반 하에 토큰을 분리하는 데다 StopAnalyzer와 같이 StopFilter를 사용하므로 다른 built-in Analyzer에 비해 가장 기능이 뛰어난 Analyzer 라고 볼 수 있다. 하지만 위의 인덱싱 결과에서 처럼 아쉽지만 한글과 같은 비 영어권 문자는 기본적으로 인식하지 못한다. 하지만 StandardAnalyzer가 기본적으로 사용하는 StandardTokenizer의 소스를 수정하거나 Lucene의 SandBox에 위치한 CJKAnalyzer(org.apache.lucene.analysis.cjk.CJKAnalyzer)를 사용하면 충분히 처리가 가능하다.
( SandBox : http://lucene.apache.org/java/docs/lucene-sandbox/ )


6. Lucene 관련 유용한 유틸리티
이번 단원에서 소개할 내용은 lucene 관련 유용한 third-party 유틸리티 이다.



  Luke
가장 먼저 소개할 유틸리티는 아래 <그림2>에 나와 있는 인덱스 브라우저 Luke 이다. Lucene은 이진파일로 된 index파일을 사용하므로 언어에 관계없이 index파일을 읽을 수 있는데 이 luke 라는 유틸리티를 사용하면 마치 데이터베이스 관련 GUI 툴을 보듯 index파일의 내용을 일목요연 하게 볼 수 있으며 검색 기능도 제공한다. (http://www.getopt.org/luke/ 에서 찾아볼수 있다.)
 






























<그림2> 인덱스 브라우저 Luke


 Lucli
Lucli는 Dror Matalon에 의해 배포되고 있는 Lucene의 Command Line 인터페이스 이다. 문서를 인덱싱 하기 위해 굳이 코드를 작성하지 않더라도 이 Lucli 를 사용하면 쉽게 문서의 인덱싱이 가능하다. Lucene의 SandBox에서 구할수 있으며, 관련 jar파일을 클래스 패스로 지정한 후
$JAVA_HOME/bin/java  lucli.Lucli 명령을 실행하면 된다.
아직은 작성된 Document 도 없으며, 인덱싱 시에 StandardAnalyzer를 사용하도록 하드 코딩 되어 있으므로 실제 사용 시에 약간의 제약은 따른다.


  Limo
Limo는 Julien Nioche가 개발한 lucene Index Monitor 이다.  http://limo.sourceforge.net 에서 다운로드 받을 수 있으며  limo.war 파일을 ServletContainer 에 올려서 바로 사용 가능하다. 톰캣의 경우엔 $TOMCAT_HOME/webapps/ 폴더에 .war 파일을 복사한다.
이 제 서버를 실행하고 limo Application을 웹브라우저로 실행시켜 보자. Index 파일의 경로를 지정하는 폼 화면이 나온 후 경로를 적당히 지정해주면 아래 <그림3> 과 같은 화사한 웹 화면을 감상 하실 수 있을 것 이다. 물론 기본적으로 한글이 깨져서 나올 테지만 jsp 상단의 contentType 부분에 "charset=euc-kr" 을 추가해주면 한글 출력도 문제 없다. ( <%@page contentType="text/html;charset=euc-kr"%>)
Limo는 Index 파일의 정보를 일목요연 하게 보여주며, 부가적으로 인덱스 파일의 검색 기능도 제공한다. Luke와 비교해 각각 일장 일단이 있으므로 각각 한번씩 비교해 보기 바란다.
 






















<그림 3> Limo Application의 실행 화면


5. 결론
이번 연재 에서는 Lucene의 실전 활용 보다는 멋진 오픈 소스 검색엔진의 소개가 주 목적 이였기에 복잡한 내용은 최대한 배제하고 소개 글과 함께 기본 기능에 대해서만 간략하게 다루어 보았다. 다음 연재 에서는 Analyzer 의 보다 상세한 내용과 인덱스 튜닝에 대해 다루어 볼 예정이며 고급 검색 기법과 실전에 쓰일 만한 여러 가지 문서 포맷의 인덱싱 그리고 실제 Lucene의 적용시 격게 되는 여러 가지 문제점과 해결책에 대해  보다 심도 있게 다루어 보도록 하겠다. 지면상 광범위한 내용 전체를 전부 다룰 수가 없으므로 조금 아쉬움이 남기는 한다. 이번 기사에 부족함을 느끼는 독자들은 Apache Lucene 웹사이트와 wiki를 방문하면 다양한 레퍼런스를 포함해서 좋은 정보를 많이 얻을 수 있을 것 이다.


|

No7Do's Blog is powered by Daum & tistory