본문 바로가기

Lucene

[about Lucene] 루씬으로 검색엔진 개발하기 - IndexWriter -

루씬으로 뭔가 작업을 한다면 사실 가장 어려운 부분이 Analyzer를 입맛에 맞게 만드는 부분일 것 입니다.
지금까지 작성한 Analyzer에 대한 내용이 이렇게 길었던 것도
그런 이유였고요...

이제부터 이야기 할 Indexing, Search, Query, Sort등은 사실 사용하기가
그렇게 어려운 것은 아닙니다. 이미 API자체가 너무나도 잘 만들어졌기 때문에
그냥 특별한 옵션 없이 사용하는 것 만으로도 충분히 잘 사용 할 수가 있습니다.

그 다음엔 사실 경험이구요... 포스트의 내용도 기본적인 API에 대한 테스트케이스와 
이것들을 사용하면서 기억에 남았던 내용들에 대한 것을 소개해드리는 내용이 될 것 같습니다. 

사실 대부분의 내용은 JAVADOC이나 루씬인액션과 같은 책을 읽어보는 것이
훨씬 도움이 될 것 입니다. 

그래서 이번부터는 간단하게 TestCase를 작성하여
API가 어떻게 사용되는지 보여드리고 필수적은 옵션들에 대해서만
설명을 하려고 합니다. 

색인 할 때 사용하는 IndexWriter나 검색시 사용하는 IndexSearcher등은 실제로 이를 사용하는 개발자가
어떻게 사용할까의 문제이지 이 API자체를 사용하기는 어렵지 않습니다.

우선 IndexWriterTestCase를 보겠습니다. 이 테스트케이스는 Lucene in Action에 있는 
예제를 조금 수정한 예제입니다.

 
IndexWriterTest.java

예전의 API와는 다르게 최근 버전의 루씬의 IndexWriterIndexWriterConfig라는 객체를 받아서 생성하도록
되어있습니다. 그외 나머지 생성자들은 거의 대부분 deprecated 처리가 되었죠.. 4.0이 되면 아마
정리를 하지 않을까 생각됩니다.

색인을 하기 위한 클래스가 IndexWriter입니다. 이 클래스는 Document를 받아 색인파일을 작성하는 클래스입니다.
내부적으로는 색인파일을 읽기 위한 IndexReader 클래스를 가지고 있습니다.

색인파일을 작성하기위해 필요한 것이 Directory인데요 루씬에서는 몇가지 종류의 디렉토리를 제공하고
있습니다. 위 예제는 RAMDirectory로 JVM메모리에 공간을 만들어 이곳에 색인파일을 작성합니다.
당연히 JVM이 내려가면 색인 내용도 전부 사라집니다. 하지만 속도가 빠르기 때문에 작은 용량의 색인 파일을
메모리에 올려놓고 사용 할 경우에는 유용하게 사용됩니다.

앞선 포스트중에서 동의어필터를 개발 할 때 이 RAMDirectory를 사용하였었습니다.

그 다음에 제공되는 것이 FSDirectory입니다.
이 클래스는 실제로 물리적인 하드에 색인 파일을 작성합니다.
사용법은 아래와 같습니다.

Directory directory = FSDirectory.open(new File("d:/index"));


내부적으로는 더 많은 종류의 Directory가 있지만 일단 크게 이렇게 두가지로 보시면 될 것 같습니다.

이렇게 만들어진 Directory를 사용하여 IndexWriter를 생성합니다.

IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_33, new WhitespaceAnalyzer(Version.LUCENE_33));
IndexWriter indexWriter = new IndexWriter(directory, conf);


IndexWriterConfig에는 많은 설정을 할 수 있는데 그 중 필수 설정이 바로 Analyzer의 설정입니다. 
이렇게 설정된 Analyzer에 의해서 Document의 각 필드의 내용을 Token으로 분석하여 색인합니다.

위 경우에는 문서의 내용을 WhitespaceAnalyzer를 사용하여 색인하고 있습니다.

기본적으로 공백을 기준으로 Token을 만들어 색인이 될 것이라고 생각 할 수 있습니다.

나중에 다시 한번 언급을 하겠지만 색인에서 사용되는 Analyzer와 검색에서 검색어를 분석 할 때 사용되는
Analyzer는 기본적으로 같은 것을 사용합니다.

"나는 개발자다" 라는 문장에 대해서 색인 할 때는 "나는+개발자다" 라고 분석하여 색인하고
검색어 분석에 사용되는 Analyzer에서는 "나"+"개발자"라고 분석한다면 위 문장은
절대로 검색이 되지 않을 것 이기 때문입니다.

그 후 실제로 Document를 생성하고 indexWriter.addDocument(Document d) 메서드를 통해서
Document를 색인합니다.
이때 Document를 생성 할 때 사용되는 것이 Field 클래스입니다.

Document는 여러개의 Field로 이루어져있고 이 Field는 이름과 값을 가지고 있습니다.
루씬의 스킴은 기본적으로 자유스킴(schema-free)이기 때문에 하나의 Document에 같은 이름을 가진 Field가 여러개
존재해도 상관이 없고, 하나의 IndexWriter를 이용하여 색인 할 때 사용되는 Document들이
모두 같은 Field를 가지고 있을 필요도 없습니다.

Field를 생성하는 부분을 보면

new Field("ids", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED)


로 되어있습니다.

맨 앞의 파라메터는 이름, 두번째는 값입니다.
3,4번째의 파라메터가 중요한데요 Field.Store.YES는 이 값을 색인 파일에서
가지고 있을 것이냐를 결정합니다. 이것이 YES로 설정이 되면 IndexSearcher를 통해 검색 된 Document에서
이 필드의 값을 가져 올 수 있습니다. 물론 그만큼 색인파일의 사이즈는 커지겠죠... 보통 검색결과 페이지에서
화면에 보여 줄 내용들을 이 옵션을 통해서 처리합니다.

4번째 파라메터는 이 필드의 값을 색인 할 것이냐와 분석 할 것이냐를 결정합니다.
5가지 옵션이 존재하는데요.. 그 중 3가지에 대해서만 간단히 설명 드리겠습니다.

Field.Index.NOT_ANALYZED
Field.Index.ANALYZED
Field.Index.NO


NOT_ANALYZED는 색인은 하되 분석은 하지 않는 다는 것입니다. 즉, 위 예제에서 WhitespaceAnalyzer를 통해
문장을 Token으로 쪼개지 않고 통채로 색인을 하겠다는 것 입니다. 이 구문은 보통 숫자나 PK값등 분석이 필요없는 필드에 많이 사용됩니다.

Analyzed는 위와 반대로 분석을 하겠다는 것이구요...

NO는 색인을 하지 않겠다는 것 입니다.

그외 2가지 옵션은 여기서 설명하기는 다소 양이 많으므로 책을 보시기를 추천합니다. ^^

맨 마지막으로는 commit을 하고 close를 합니다.

IndexWriter를 통해 변경된 색인 파일의 내용은 IndexWritercommit을 해야 실제 색인파일에 반영이 됩니다.
이 단순한 한줄에도 많은 이야기들이 들어있는데.. 일단 IndexReaderIndexSearcher
IndexWrtier에 의해서 최종 commit된 내용만 볼 수 있다는 것을 알아두셨으면 합니다. (예외도 있습니다..)

위 예제 중 testDeleteDocument 메서드를 보시면 indexWriter를 통해서 ids 1번 문서를 삭제하고 있습니다.
그리고 commit을 하지 않고 다시 문서수를 체크하여보니 여전이 3개로 나오죠..
하지만 commit을 하고 체크를 하니 2개의 문서를 가지고 있는 것으로 나옵니다.

추가적으로 testDeleteAndOptimizeDocument 메서드를 보시면
numDocsmaxDoc 메서드의 차이를 보여주고 있습니다.
실제로 IndexWriter를 통해서 document를 삭제하면 바로 실제 색인파일에서 document
삭제되는 것은 아닙니다. commit을 하더라도 document에는 삭제된 document라는 마킹만 되어
있는 상태인 것이죠... numdocmaxdoc은 그 차이이고 색인파일 최적화(optimize)는 이러한 마킹 된 document들을
실제로 삭제 처리하게 됩니다.

그리고 IndexWriter는 색인파일을 열때 lock 메커니즘으로 동작을 합니다.
하나의 색인 파일에 대해서는 한번에 하나의 IndexWriter만이 생성 될 수 있습니다.

예를 들어서 아래와 같은 경우에는
org.apache.lucene.store.LockObtainFailedException 이 발생합니다.


ndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_33, new WhitespaceAnalyzer(Version.LUCENE_33));
IndexWriter indexWriter = new IndexWriter(directory, conf);

IndexWriter indexWriter2 = new IndexWriter(directory, conf);


indexWriter를 열고 close하지 않은 상태에서
indexWriter2를 생성하려고 하면서 에러가 발생하게 됩니다.

IndexWriter 그리고 DocumentField에 대한 이야기는 책으로도 한 챕터가 나올만큼 굉장히 방대한 양입니다. 

때문에 앞서 말씀드린대로 포스트에서는 기본이 되는 내용에 대해서만 언급을 하였습니다.

하지만 위 정도의 내용과 테스트 케이스의 소스만 사용하셔도
기본적인 색인과 테스트들은 해보 실 수 있을 것 이라 생각됩니다.

내용이 좀 부실 한 것 같습니다.. 하지만 색인을 한다면 이정도로도 충분히 색인이 가능 할 것이라 생각되고요..
저 같은 경우도 IndexWriter의 사용에 있어서는 Document들의 설계나 IndexWriter를 생성하고 닫는 구현의
방법만 조금 달리 할 뿐 기본적으로 위 예제에서 크게 다르지는 않습니다. 

위 테스트 케이스는

https://github.com/need4spd/aboutLucene
에서 체크아웃 받으 실 수 있습니다.