|
Elastic/Elasticsearch 2022. 7. 20. 13:47
Elasticsearch 8.3.2 + Lucene 9.2.0 에서 변경된 내용을 정리해 봅니다.
Lucene 8.11 to 9.2)
pom.xml 내 수정
# lucene-analyzers-common 은 9.x 에서는 더 이상 지원 하지 않음
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>${lucene.version}</version>
</dependency>
to
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analysis-common</artifactId>
<version>${lucene.version}</version>
</dependency>
package 변경
org.apache.lucene.analysis.util.TokenFilterFactory
to
org.apache.lucene.analysis.TokenFilterFactory
org.apache.lucene.analysis.standard.ClassicFilter
to
org.apache.lucene.analysis.classic.ClassicFilter
org.apache.lucene.analysis.util.TokenizerFactory
to
org.apache.lucene.analysis.TokenizerFactory
org.apache.lucene.analysis.util.TokenFilterFactory
to
org.apache.lucene.analysis.TokenFilterFactory
org.apache.lucene.analysis.util.TokenFilterFactory
to
org.apache.lucene.analysis.TokenFilterFactory
elasticsearch analyzer plugin)
# AbstractIndexAnalyzerProvider 에서 IndexSettings 제거됨
org.elasticsearch.client.node.NodeClient
to
org.elasticsearch.client.internal.node.NodeClient
JDK 수정
build 시 jdk 17 사용
$jenv local 17
<java.version>17</java.version> 수정
❖ lucene 에서는 analyzer 패키지 변경이 있었으며, elasticsearch 에서는 NodeClient 에 대한 패키지 변경이 있었습니다.
Elastic/Elasticsearch 2021. 5. 11. 20:51
[추가 사항]
https://issues.apache.org/jira/browse/SOLR-12655?focusedCommentId=16604160&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel&fbclid=IwAR3jRIpaCQ497v-qhofkc3DVmNabPab1ErDQhXnOsA0LNoqpHypa5cUSpy0#comment-16604160
아래 발생한 오류는 UnknownDictionaryBuilder.java 에서 아래 코드 수정으로 해결 되었습니다.
기본적으로 ivy.xml, build,xml 에 보면 사전 버전 정보가 들어가 있습니다.
이 사전이 변경 되면서 POS tag list 가 달라 졌는데요. 이 영향으로 에러가 발생 하게 됩니다.
private static final String NGRAM_DICTIONARY_ENTRY = "NGRAM,1801,3561,3668,SY,*,*,*,*,*,*,*";
코드를 수정 하지 않으려면 사전 버전을 맞춰서 사용 하시면 됩니다.
Elasticsearch User Group 에 #유정인 님이 도움 주셨습니다.
https://github.com/jimczi/nori/blob/master/how-to-custom-dict.asciidoc
https://bitbucket.org/eunjeon/mecab-ko/src/mecab-0.996/ https://bitbucket.org/eunjeon/mecab-ko-dic/src/v2.1.1/
$ git clone https://bitbucket.org/eunjeon/mecab-ko.git $ git checkout tags/mecab-0.996 $ ./configure $ make $ sudo make install $ mecab -v
https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/
$ wget https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.1.1-20180720.tar.gz $ tar -xvzf mecab-ko-dic-2.1.1-20180720.tar.gz $ cd mecab-ko-dic-2.1.1-20180720 $ brew install autoconf automake libtool $ autoreconf $ ./configure $ make $ sudo make install $ ./tools/add-userdic.sh
$ tar cvzf custom-mecab-ko-dic.tar.gz mecab-ko-dic-2.1.1-20180720 $ git clone https://github.com/apache/lucene.git $ git checkout tags/releases/lucene-solr/8.8.1 $ vi lucene/analysis/nori/ivy.xml
~ <!--artifact name="mecab-ko-dic" type=".tar.gz" url="https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.0.3-20170922.tar.gz" /-->
+ <artifact name="mecab-ko-dic" type=".tar.gz" url="file:///Users/mzc02-henryjeong/Temp/fastcampus/analysis-nori/custom-mecab-ko-dic.tar.gz" />
$ vi lucene/analysis/nori/build.xml
~ <!--property name="dict.version" value="mecab-ko-dic-2.0.3-20170922" /-->
+ <property name="dict.version" value="mecab-ko-dic-2.1.1-20180720" />
$ cd lucene/analysis/nori // apache ant 설치 $ mkdir -p ~/.ant/lib $ ant ivy-bootstrap $ ant regenerate build-dict: [delete] Deleting /Users/mzc02-henryjeong/Temp/analysis-nori/lucene/lucene/analysis/nori/src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$buffer.dat [delete] Deleting /Users/mzc02-henryjeong/Temp/analysis-nori/lucene/lucene/analysis/nori/src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$fst.dat [delete] Deleting /Users/mzc02-henryjeong/Temp/analysis-nori/lucene/lucene/analysis/nori/src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$posDict.dat [delete] Deleting /Users/mzc02-henryjeong/Temp/analysis-nori/lucene/lucene/analysis/nori/src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$targetMap.dat [java] Exception in thread "main" java.lang.AssertionError [java] at org.apache.lucene.analysis.ko.util.BinaryDictionaryWriter.put(BinaryDictionaryWriter.java:112) [java] at org.apache.lucene.analysis.ko.util.UnknownDictionaryWriter.put(UnknownDictionaryWriter.java:39) [java] at org.apache.lucene.analysis.ko.util.UnknownDictionaryBuilder.readDictionaryFile(UnknownDictionaryBuilder.java:71) [java] at org.apache.lucene.analysis.ko.util.UnknownDictionaryBuilder.readDictionaryFile(UnknownDictionaryBuilder.java:47) [java] at org.apache.lucene.analysis.ko.util.UnknownDictionaryBuilder.build(UnknownDictionaryBuilder.java:41) [java] at org.apache.lucene.analysis.ko.util.DictionaryBuilder.build(DictionaryBuilder.java:39) [java] at org.apache.lucene.analysis.ko.util.DictionaryBuilder.main(DictionaryBuilder.java:52)
BUILD FAILED $ git status . HEAD detached at releases/lucene-solr/8.8.1 Changes not staged for commit: (use "git add/rm ..." to update what will be committed) (use "git restore ..." to discard changes in working directory) modified: build.xml modified: ivy.xml deleted: src/resources/org/apache/lucene/analysis/ko/dict/CharacterDefinition.dat deleted: src/resources/org/apache/lucene/analysis/ko/dict/ConnectionCosts.dat modified: src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$buffer.dat modified: src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$fst.dat modified: src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$posDict.dat modified: src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$targetMap.dat deleted: src/resources/org/apache/lucene/analysis/ko/dict/UnknownDictionary$buffer.dat deleted: src/resources/org/apache/lucene/analysis/ko/dict/UnknownDictionary$posDict.dat deleted: src/resources/org/apache/lucene/analysis/ko/dict/UnknownDictionary$targetMap.dat $ ant jar ...중략... -jar-core: [jar] Building jar: /Users/mzc02-henryjeong/Temp/analysis-nori/lucene/lucene/build/analysis/nori/lucene-analyzers-nori-8.8.1-SNAPSHOT.jar ...중략...
/Users/mzc02-henryjeong/Works/app/apache-ant-1.10.10
일단 시간이 별로 없어서 이 정도까지만 테스트 하고 오류는 나중에 심각 하게 살펴 보겠습니다.
Arirang 만 잘 해도 되는데 Nori 도 할 줄 알아야 하니까... 근데 사전 관리 방식은 Arirang 이 편하고 좋습니다.
사실 한자 사전 고치려다가 여기까지 왔네요.
Elastic/Elasticsearch 2021. 4. 30. 09:25
https://github.com/HowookJeong/elasticsearch-analysis-arirang/tree/hanguel-jamo-tokenizer-7.12.0
Checkout 받으신 후 빌드 하시고 설치 하시면 됩니다.
$ mvn clean install -DskipTests=true $ bin/elasticsearch-plugin install file:///Users/mzc02-henryjeong/Works/github/howookjeong/elasticsearch-analysis-arirang/target/elasticsearch-analysis-arirang-7.12.0.zip
[Request] curl --location --request POST 'http://localhost:9200/_arirang/jamo?text=엘라스틱서치&token=CHOSUNG'
[Method]
GET / POST
[Response] CHOSUNG -> ㅇㄹㅅㅌㅅㅊ JUNGSUNG -> ㅔㅏㅡㅣㅓㅣ JONGSUNG -> ㄹㄱ KORTOENG -> dpffktmxlrtjcl
[Parameters]
- text
형태소 분석할 문자열
- token
분석 유형 지정 CHOSUNG (초성) JUNGSUNG (중성) JONGSUNG (종성) KORTOENG (한영 변환)
기능 테스트로 넣어 둔거라서 성능적인 검증은 하지 않았습니다.
Elastic/Elasticsearch 2021. 4. 5. 17:23
Nori Analyzer 기본 테스트 입니다.
공홈 참고문서)
www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-nori.html
기본 사전)
bitbucket.org/eunjeon/mecab-ko-dic/src/master/
POS Tag)
lucene.apache.org/core/8_8_0/analyzers-nori/org/apache/lucene/analysis/ko/POS.Tag.html
여기서 주의 할 점은 filter 선언 시 postags 가 아닌 stoptags 로 선언 하셔야 합니다.
제가 실수로 postags 로 작성을 했었네요. (수정해 두었습니다.)
_analyze API 를 이용해서 RESTful API 호출로 테스트 한 내용입니다.
{
"tokenizer": {
"type": "nori_tokenizer",
"decompound_mode": "mixed",
"discard_punctuation": "true",
"user_dictionary_rules": ["c++ c+ +", "C샤프", "세종", "세종시 세종 시"]
},
"filter": [
{
"type": "nori_part_of_speech",
"stoptags": [
"E",
"IC",
"J",
"MAG", "MAJ", "MM",
"SP", "SSC", "SSO", "SC", "SE",
"XPN", "XSA", "XSN", "XSV",
"UNA", "NA", "VSV"
]
},
{
"type": "nori_readingform"
}
],
"text": "世宗市에서 c++ 언어를 가르치는 학원이 있나요?",
"attributes" : ["posType", "leftPOS", "rightPOS", "morphemes", "reading"],
"explain": true
}
더보기
실행한 결과)
{ "detail": { "custom_analyzer": true, "charfilters": [], "tokenizer": { "name": "__anonymous__nori_tokenizer", "tokens": [ { "token": "世宗", "start_offset": 0, "end_offset": 2, "type": "word", "position": 0, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": "세종", "rightPOS": "NNG(General Noun)" }, { "token": "市", "start_offset": 2, "end_offset": 3, "type": "word", "position": 1, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": "시", "rightPOS": "NNG(General Noun)" }, { "token": "에서", "start_offset": 3, "end_offset": 5, "type": "word", "position": 2, "leftPOS": "J(Ending Particle)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "J(Ending Particle)" }, { "token": "c++", "start_offset": 6, "end_offset": 9, "type": "word", "position": 3, "positionLength": 2, "leftPOS": "NNG(General Noun)", "morphemes": "c+/NNG(General Noun)++/NNG(General Noun)", "posType": "COMPOUND", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "c+", "start_offset": 6, "end_offset": 8, "type": "word", "position": 3, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "+", "start_offset": 8, "end_offset": 9, "type": "word", "position": 4, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "언어", "start_offset": 10, "end_offset": 12, "type": "word", "position": 5, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "를", "start_offset": 12, "end_offset": 13, "type": "word", "position": 6, "leftPOS": "J(Ending Particle)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "J(Ending Particle)" }, { "token": "가르치", "start_offset": 14, "end_offset": 17, "type": "word", "position": 7, "leftPOS": "VV(Verb)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "VV(Verb)" }, { "token": "는", "start_offset": 17, "end_offset": 18, "type": "word", "position": 8, "leftPOS": "E(Verbal endings)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "E(Verbal endings)" }, { "token": "학원", "start_offset": 19, "end_offset": 21, "type": "word", "position": 9, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "이", "start_offset": 21, "end_offset": 22, "type": "word", "position": 10, "leftPOS": "J(Ending Particle)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "J(Ending Particle)" }, { "token": "있", "start_offset": 23, "end_offset": 24, "type": "word", "position": 11, "leftPOS": "VA(Adjective)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "VA(Adjective)" }, { "token": "나요", "start_offset": 24, "end_offset": 26, "type": "word", "position": 12, "leftPOS": "E(Verbal endings)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "E(Verbal endings)" } ] }, "tokenfilters": [ { "name": "__anonymous__nori_part_of_speech", "tokens": [ { "token": "世宗", "start_offset": 0, "end_offset": 2, "type": "word", "position": 0, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": "세종", "rightPOS": "NNG(General Noun)" }, { "token": "市", "start_offset": 2, "end_offset": 3, "type": "word", "position": 1, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": "시", "rightPOS": "NNG(General Noun)" }, { "token": "c++", "start_offset": 6, "end_offset": 9, "type": "word", "position": 3, "positionLength": 2, "leftPOS": "NNG(General Noun)", "morphemes": "c+/NNG(General Noun)++/NNG(General Noun)", "posType": "COMPOUND", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "c+", "start_offset": 6, "end_offset": 8, "type": "word", "position": 3, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "+", "start_offset": 8, "end_offset": 9, "type": "word", "position": 4, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "언어", "start_offset": 10, "end_offset": 12, "type": "word", "position": 5, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "가르치", "start_offset": 14, "end_offset": 17, "type": "word", "position": 7, "leftPOS": "VV(Verb)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "VV(Verb)" }, { "token": "학원", "start_offset": 19, "end_offset": 21, "type": "word", "position": 9, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "있", "start_offset": 23, "end_offset": 24, "type": "word", "position": 11, "leftPOS": "VA(Adjective)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "VA(Adjective)" } ] }, { "name": "__anonymous__nori_readingform", "tokens": [ { "token": "세종", "start_offset": 0, "end_offset": 2, "type": "word", "position": 0, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": "세종", "rightPOS": "NNG(General Noun)" }, { "token": "시", "start_offset": 2, "end_offset": 3, "type": "word", "position": 1, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": "시", "rightPOS": "NNG(General Noun)" }, { "token": "c++", "start_offset": 6, "end_offset": 9, "type": "word", "position": 3, "positionLength": 2, "leftPOS": "NNG(General Noun)", "morphemes": "c+/NNG(General Noun)++/NNG(General Noun)", "posType": "COMPOUND", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "c+", "start_offset": 6, "end_offset": 8, "type": "word", "position": 3, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "+", "start_offset": 8, "end_offset": 9, "type": "word", "position": 4, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "언어", "start_offset": 10, "end_offset": 12, "type": "word", "position": 5, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "가르치", "start_offset": 14, "end_offset": 17, "type": "word", "position": 7, "leftPOS": "VV(Verb)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "VV(Verb)" }, { "token": "학원", "start_offset": 19, "end_offset": 21, "type": "word", "position": 9, "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "NNG(General Noun)" }, { "token": "있", "start_offset": 23, "end_offset": 24, "type": "word", "position": 11, "leftPOS": "VA(Adjective)", "morphemes": null, "posType": "MORPHEME", "reading": null, "rightPOS": "VA(Adjective)" } ] } ] } }
synonyms filter 추가)
주의 할 사항은 user_dic.txt 에 정의 되지 않은 단어의 경우 의도한 결과가 나오지 않을 수 있습니다.
{
"tokenizer": {
"type": "nori_tokenizer",
"decompound_mode": "mixed",
"discard_punctuation": "true",
"user_dictionary_rules": ["c++ c+", "c샤프", "c샵", "삼성전자", "세종", "세종시 세종 시"]
},
"filter": [
{
"type": "synonym_graph",
"synonyms": [
"삼성전자, 삼전",
"c샤프, c샵"
]
},
{
"type": "nori_part_of_speech",
"stoptags": [
"E",
"IC",
"J",
"MAG", "MAJ", "MM",
"SP", "SSC", "SSO", "SC", "SE",
"XPN", "XSA", "XSN", "XSV",
"UNA", "NA", "VSV"
]
},
{
"type": "nori_readingform"
}
],
"text": "世宗市에서 c++, c샤프 언어를 가르치는 삼성전자 학원이 있나요?",
"attributes" : ["posType", "leftPOS", "rightPOS", "morphemes", "reading"],
"explain": false
}
nori_userdict.txt)
user_dictionary_rules 를 user_dictionary 로 변경해서 설정을 하게 되면 아래와 같습니다.
- "user_dictionary": "nori_userdict.txt"
- 위 파일은 elasticsearch 가 설치된 위치의 config 경로 아래 위치 합니다.
c++ c+
c샤프
c샵
삼성전자
세종
세종시 세종 시
ITWeb/검색일반 2018. 10. 5. 14:43
가끔 착각하게 되는 내용이라 기술해 봅니다.
우리가 흔히 이야기 하는 불용어라고 하는 것은 추출된 색인어에 대해서 색인어 처리를 하지 않도록 하는 것입니다. 이것이 stopwords 라고 부르는 것이고 stop token filter 하고 합니다.
analyze 과정에서 char filter 이후 tokenizer 단계에서 사용되는 filter 기능이 되는 것입니다. 다시 말해 색인어 추출 후 적용하는 filter 기능이라고 보시면 됩니다.
그럼 char filter 는 언제 동작 하게 될까요? 이 filter 기능은 색인어 추출 전 그러니까 tokenizer 로 text 가 전달 되기 전에 text 내 불필요한 문자들을 제거 하기 위해 사용을 합니다. 이것이 char filter 가 되겠습니다.
그냥 저 혼자 복습 차원에서 기술해 봤습니다.
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-tokenfilter.html https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html
Elastic/Elasticsearch 2018. 6. 28. 11:35
arirang plug 을 적용해서 돌려본 실행 속도 입니다.
[DEBUG] 2018-07-02 13:12:18.407 [main] MainIndexer - Bulk operation elapsed time [811.0869750976562] sec [DEBUG] 2018-07-02 13:14:16.595 [main] MainIndexer - Force merge operation elapsed time [118.18800354003906] sec
[Settings] "settings":{ "number_of_shards":1, "number_of_replicas":0, "index.refresh_interval":"1h", "index.routing.allocation.require.box_type":"indexing", "index.similarity.default.type":"BM25", "index":{ "analysis":{ "analyzer":{ "arirang_custom_analyzer":{ "tokenizer":"arirang_tokenizer", "filter":[ "lowercase", "trim", "custom_synonym", "arirang_filter" ] } }, "filter":{ "custom_synonym":{ "type":"synonym_graph", "synonyms":[] } } } } }
[Mappings] "mappings" : { "_doc": { "_source": { "enabled": true }, "dynamic_templates": [ { "strings": { "match_mapping_type": "string", "mapping": { "type": "text", "analyzer": "arirang_custom_analyzer", "fields": { "raw": { "type": "keyword", "ignore_above": 50 } } } } } ] } }
얼마전 밋업에서 김종민님이 공유해 주신 위키피디아 문서를 가지고 테스트한 결과 입니다. 그냥 제가 만든 Indexer 모듈에 대한 성능 테스트용도로 한번 실험 삼아 돌려 본거라 별 도움이 안될 수 있습니다.
아래 문서는 언제까지 올라가 있을지 모르겠습니다. 빨리 받으시면 될 듯 하내요.
문서 다운로드 링크) https://s3.ap-northeast-2.amazonaws.com/kr.elastic.co/sample-data/kr-wikipedia-dump.json.tar.gz
위키피디아 문서 색인 테스트 결과) MacBook Pro CPU 2.8G i7 MEM 16GB API : HighLevelRestClient REST API : _bulk Settings primary shard : 1 replica shard : 0 refresh_interval : 1h similarity : BM25 default analyzer (standard) Mappings dynamic mapping Indexing File Source Size 3.6GB Indexing Segment File Size (force merge 전) 11 GB Indexing Segment File Size (force merge 후) 4.28 GB Bulk Request Thread Size 5 Bulk Request Document Size (per thread) 500 Bulk Indexing / Force Merge Time [DEBUG] 2018-06-28 11:06:13.780 [main] MainIndexer - Bulk operation elapsed time [366.6780090332031] sec [DEBUG] 2018-06-28 11:08:22.254 [main] MainIndexer - Force merge operation elapsed time [128.47300720214844] sec Indexing Documents Total indexed document are 1020662
결과적으로 튜닝한 내용은 전혀 없고 그냥 색인기 기능 테스트 정도로 보셔도 될 것 같습니다.
Elastic/Elasticsearch 2018. 1. 30. 11:01
몇 가지 오류와 누락 된 기능이 있어서 추가해서 릴리즈 했습니다. 1. StartOffset 과 EndOffset 정보에 대한 order 가 깨져서 에러가 나는 부분이 있었는데 수정해서 올렸습니다. 2. REST Action API 하나 누락 되어서 추가해서 올렸습니다.
https://github.com/HowookJeong/elasticsearch-analysis-arirang/releases
Elastic/Elasticsearch 2017. 11. 15. 23:49
페북에 올렸더니 스팸 이라고 삭제 당했내요. ㅡ.ㅡ; https://github.com/HowookJeong/elasticsearch-analysis-arirang/tree/6.0.0 https://github.com/HowookJeong/elasticsearch-analysis-arirang/releases/download/6.0.0/elasticsearch-analysis-arirang-6.0.0.zip
설치 방법은 잘 아시겠지만 두 가지 입니다. $ bin/elasticsearch-plugin install file:///elasticsearch-analysis-arirang-6.0.0.zip $ bin/elasticsearch-plugin install https://github.com/HowookJeong/elasticsearch-analysis-arirang/releases/download/6.0.0/elasticsearch-analysis-arirang-6.0.0.zip
적용된 version 은 아래와 같습니다. elasticsearch-6.0.0 lucene-7.0.1 arirang.lucene-analyzer-7.0.1 arirang.morph-1.1.0
혹시 arirang plugin 을 어떻게 만드는지 궁금하신 분들은 아래 글 참고하세요. [Elasticsearch] Arirang Analyzer + Elasticsearch Analyzer Plugin 사용자 관점 개발리뷰
Elastic/Elasticsearch 2017. 10. 19. 14:25
사용자 관점에서 어떻게 개발 하는지 정리해 보았습니다.
Elasticsearch를 서비스에 사용하면서 한글 처리를 위해 어떤 analyzer를 사용해야 할지 고민해 보신적이 있을 것입니다. 오늘은 제가 사용하고 있는 Lucene Korean Analyzer와 이를 Elasticsearch에 plugin으로 설치하고 사용하는 방법을 알아 보도록 하겠습니다.
들어 가기에 앞서 lucene에서 제공하는 analyzer의 기본 구성과 동작에 대해서 살펴 보겠습니다. Lucene에서 제공하는 analyzer 는 하나의 tokenizer와 다수의 filter로 구성이 됩니다. Filter 는 CharFilter와 TokenFilter 두 가지가 있습니다. CharFilter는 입력된 문자열에서 불필요한 문자를 normalization 하기 위해 사용되며 TokenFilter는 tokenizer에 의해 분해된 token에 대한 filter 처리를 하게 됩니다. 결과적으로 아래와 같은 순서로 analysis 된다고 이해 하면 됩니다.
Input Text → Character Filter → Filtered Text → Tokenizer → Tokens → Token Filter → Filtered Tokens → Output Tokens
이제 본론으로 들어 가겠습니다. Lucene Korean Analyzer는 현재 이수명님에 의해 개발 및 유지보수가 되고 있으며 오픈소스로 등록이 되어 있습니다. 관련 소스코드는 아래 두 가지 repository를 통해서 제공 되고 있습니다.
[svn 주소] https://lucenekorean.svn.sourceforge.net/svnroot/lucenekorean
[github 주소] https://github.com/korlucene
※ Lucene Korean Analyzer 는 지금 Arirang 이라고 부르고 있습니다.
Arirang의 프로젝트 구성은 크게 두 부분으로 나뉩니다. - arirang analyzer
- arirang morph
1. arirang morph 이 프로젝트는 한글 형태소에 대한 기본 분석과 사전 정보로 구성이 되어 있습니다. 한글 처리와 사전 정보를 변경 하고 싶을 경우 본 프로젝트의 코드를 분석하고 수정 해서 활용을 하실 수 있습니다.
2. arirang analyzer 이 프로젝트는 lucene의 analyzer를 상속받아 lucene에서 사용 할 수 있도록 구성이 되어 있습니다. Lucene의 analyzer pipeline에 필요한 - KoreanAnalyzer - KoreanFilter - KoreanFilterFactory - KoreanToken - KoreanTokenizer - KoreanTokenizerFactory 등이 주요 클래스로 구현이 되어 있습니다.
한글 형태소 분석에서 중요한 역할을 하는 부분으로 사전 이라는 것이 있으며, 이를 알아 보도록 하겠습니다. arirang.morph 프로젝트에 포함이 되어 있으며 언급 한것과 같이 지속적인 업데이트 및 변경이 가능 합니다.
1. Dictionary classpath org/apache/lucene/analysis/ko/dic
2. Dictionary files org/apache/lucene/analysis/ko korean.properties org/apache/lucene/analysis/ko/dic abbreviation.dic cj.dic compounds.dic eomi.dic extension.dic josa.dic mapHanja.dic occurrence.dic prefix.dic suffix.dic syllable.dic total.dic uncompounds.dic
3. 주요 사전 설명 주요 사전 설명 이라고는 했지만 쉽고 빠르게 활용할 수 있는 사전이라고 이해 하시면 좋을 것 같습니다.
- total.dic
이 사전 파일은 arirang analyzer 에서 사용하는 기본 사전으로 그대로 사용을 하시면 됩니다. 다만, 수정이 필요 하실 경우 아래 extension.dic 파일을 활용 하시면 됩니다.
- extension.dic
확장사전이라고 부르며, 사전 데이터를 추가 해야 할 경우 이 파일에 추가해서 운영 및 관리를 하시면 됩니다.
- compounds.dic
복합명사 사전으로 하나의 단어가 여러개의 단어로 구성이 되어 있을 경우 이를 분해하기 위한 사전 정보를 관리 하는 파일 입니다.
4. total.dic / extension.dic 파일 구조 체언 용언 기타품사 하여(다)동사 되어(다)동사 '내'가붙을수있는체언 NA NA NA 불규칙변경
예) # 엘사는 명사이고 동사, 기타품사, 불규칙이 아니다, 라고 가정하면 아래와 같이 표현이 됩니다. 엘사,100000000X
# 노래는 명사이고 하여(다) 동사가 됩니다. 노래,100100000X
# 소리는 명사이고 소리내다와 같이 내가 붙을 수 있는 명사 입니다. 소리,100001000X
불규칙 정보는 아래와 같으며 원문을 참고 하시기 바랍니다. B : ㅂ 불규칙 H : ㅎ 불규칙 L : 르 불규칙 U : ㄹ 불규칙 S : ㅅ 불규칙 D : ㄷ 불규칙 R : 러 불규칙 X : 규칙 ※ 원문 : http://cafe.naver.com/korlucene/135
5. compound.dic 파일 구조 분해전단어:분해후단어1,분해후단어2,...,분해후단어N:DBXX
분해전단어에 하여(다)동사(D), 되어(다)동사(B) 가 붙을 수 있는지 확인 하셔야 합니다.
예)
이와 같이 된 이유는 객관화하다 객관화되다 가 되기 때문입니다.
참고) http://krdic.naver.com/search.nhn?query=%EA%B0%9D%EA%B4%80%ED%99%94&kind=all
이제 부터는 소스 코드를 내려 받아서 빌드 후 Elasticsearch plugin을 만드는 방법을 알아 보겠습니다.
1. 프로젝트 clone 기본적으로 master branch 를 받습니다.
$ git clone https://github.com/korlucene/arirang.morph.git $ git clone https://github.com/korlucene/arirang-analyzer-6.git
2. Maven build - arirang-analyzer-6 프로젝트에 기본적으로 arirang.morph 패키지가 등록이 되어 있기 때문에 별도 arirang.morph를 수정 하지 않았다면 arirang-analyzer-6 만 빌드하시면 됩니다.
arirang.morph $ mvn clean package arirang-analyzer-6 $ mvn clean package
3. 기능 테스트 - 기능 테스트는 arirang-analyzer-6 프로젝트에 포함된 test code를 이용해서 확인해 보시면 됩니다.
- src/test 아래 TestKoreanAnalyzer1 클래스를 참고하시면 됩니다.
☞ 아래는 이해를 돕기 위해 원본 테스트 코드를 추가 하였습니다. /** * Created by SooMyung(soomyung.lee@gmail.com) on 2014. 7. 30. */ public class TestKoreanAnalyzer1 extends TestCase {
public void testKoreanAnalzer() throws Exception {
String[] sources = new String[]{ "고려 때 중랑장(中郞將) 이돈수(李敦守)의 12대손이며", "이돈수(李敦守)의", "K·N의 비극", "金靜子敎授", "天國의", "기술천이", "12대손이며", "明憲淑敬睿仁正穆弘聖章純貞徽莊昭端禧粹顯懿獻康綏裕寧慈溫恭安孝定王后", "홍재룡(洪在龍)의", "정식시호는 명헌숙경예인정목홍성장순정휘장소단희수현의헌강수유령자온공안효정왕후(明憲淑敬睿仁正穆弘聖章純貞徽莊昭端禧粹顯懿獻康綏裕寧慈溫恭安孝定王后)이며 돈령부영사(敦寧府領事) 홍재룡(洪在龍)의 딸이다. 1844년, 헌종의 정비(正妃)인 효현왕후가 승하하자 헌종의 계비로써 중궁에 책봉되었으나 5년 뒤인 1849년에 남편 헌종이 승하하고 철종이 즉위하자 19세의 어린 나이로 대비가 되었다. 1857년 시조모 대왕대비 순원왕후가 승하하자 왕대비가 되었다.", "노벨상을" };
KoreanAnalyzer analyzer = new KoreanAnalyzer();
for (String source : sources) { TokenStream stream = analyzer.tokenStream("dummy", new StringReader(source));
CharTermAttribute termAtt = stream.addAttribute(CharTermAttribute.class); PositionIncrementAttribute posIncrAtt = stream.addAttribute(PositionIncrementAttribute.class); PositionLengthAttribute posLenAtt = stream.addAttribute(PositionLengthAttribute.class); TypeAttribute typeAtt = stream.addAttribute(TypeAttribute.class); OffsetAttribute offsetAtt = stream.addAttribute(OffsetAttribute.class); MorphemeAttribute morphAtt = stream.addAttribute(MorphemeAttribute.class); stream.reset();
while (stream.incrementToken()) { System.out.println(termAtt.toString() + ":" + posIncrAtt.getPositionIncrement() + "(" + offsetAtt.startOffset() + "," + offsetAtt.endOffset() + ")"); } stream.close(); }
} }
이제 arirang에 대한 빌드와 기능테스트가 끝났으니 elasticsearch에 설치 하기 위한 plugin 만드는 방법을 알아 보도록 하겠습니다. 먼저, elasticsearch에서 제공하는 plugins 관련 문서를 시간이 된다면 한번 읽어 보시고 아래 내용을 보시길 추천 드립니다.
※ Elasticsearch Plugins and Integrations : https://www.elastic.co/guide/en/elasticsearch/plugins/5.5/index.html
Elastic에서 공식문서에서 제공해 주고 있는 예제는 아래 링크에 나와 있으니 구현 시 참고하시기 바랍니다. ☞ https://github.com/elastic/elasticsearch/tree/master/plugins/jvm-example
※ 제가 추천하는 것은 elasticsearch source code를 다운받아 official하게 작성된 plugin 코드를 참고하여 구현하는 방법 입니다.
그럼 analysis plugin의 기본 프로젝트 구조를 살펴 보겠습니다.
1. Project Directory src/main assemblies plugin.xml java org/elasticsearch index/analysis ${CUSTOM-ANALYZER-NAME}AnalyzerProvider ${CUSTOM-ANALYZER-NAME}TokenFilterFactory ${CUSTOM-ANALYZER-NAME}TokenizerFactory plugin/analysis/arirang Analysis${CUSTOM-ANALYZER-NAME}Plugin resources plugin-descriptor.propeties
2. Files and classes - plugin.xml
maven assembly plugin을 이용한 패키징을 하기 위한 설정을 구성 합니다. - plugin-descriptor.propeties
plugin authors 정보를 구성 합니다. elasticsearch reference) https://www.elastic.co/guide/en/elasticsearch/plugins/5.5/plugin-authors.html - ${CUSTOM-ANALYZER-NAME}AnalyzerProvider
custom analyzer 생성자 제공을 위한 코드를 작성 합니다. - ${CUSTOM-ANALYZER-NAME}TokenFilterFactory
custom filter 생성자 제공을 위한 코드를 작성 합니다. - ${CUSTOM-ANALYZER-NAME}TokenizerFactory
custom tokenizer 생성자 제공을 위한 코드를 작성 합니다. - Analysis${CUSTOM-ANALYZER-NAME}Plugin
custom analyzer plugin 등록을 위한 코드를 작성 합니다.
이와 같은 구조를 이용하여 elasticsearch-analysis-arirang plugin을 만들어 보도록 하겠습니다. 본 plugin에서는 arirang에서 제공하는 dynamic dictionary reload 기능을 사용하기 위한 Rest Handler도 추가해서 만들어 보도록 하겠습니다.
소스코드 참고) https://github.com/HowookJeong/elasticsearch-analysis-arirang/tree/5.5.0
Step1) Step2) - Plugin project structure를 구성 합니다.
Step3) - root path에 lib 폴더를 생성하고 arirang analyzer 관련 jar 파일을 복사해 놓습니다.
- arirang.lucene-analyzer-VERSION.jar
- arirang-morph-VERSION.jar
Step4) - pom.xml에서 local jar 파일에 대한 dependency 설정을 추가해 줍니다.
<dependency> <groupId>com.argo</groupId> <artifactId>morph</artifactId> <version>${morph.version}</version> <scope>system</scope> <systemPath>${project.basedir}/lib/arirang-morph-${morph.version}.jar</systemPath> <optional>false</optional> </dependency>
<dependency> <groupId>com.argo</groupId> <artifactId>arirang.lucene-analyzer-${lucene.version}</artifactId> <version>${morph.version}</version> <scope>system</scope> <systemPath>${project.basedir}/lib/arirang.lucene-analyzer-${lucene.version}-${morph.version}.jar</systemPath> <optional>false</optional> </dependency>
Step5) - analysis plugin 관련 코드를 작성 합니다.
@Override public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings, IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, IndexNameExpressionResolver indexNameExpressionResolver, Supplier<DiscoveryNodes> nodesInCluster) { return singletonList(new ArirangAnalyzerRestAction(settings, restController)); }
@Override public Map<String, AnalysisProvider<TokenFilterFactory>> getTokenFilters() { return singletonMap("arirang_filter", ArirangTokenFilterFactory::new); }
@Override public Map<String, AnalysisProvider<TokenizerFactory>> getTokenizers() { Map<String, AnalysisProvider<TokenizerFactory>> extra = new HashMap<>(); extra.put("arirang_tokenizer", ArirangTokenizerFactory::new);
return extra; }
@Override public Map<String, AnalysisProvider<AnalyzerProvider<? extends Analyzer>>> getAnalyzers() { return singletonMap("arirang_analyzer", ArirangAnalyzerProvider::new); }
Step6) // ArirangAnalyzerProvider private final KoreanAnalyzer analyzer;
public ArirangAnalyzerProvider(IndexSettings indexSettings, Environment env, String name, Settings settings) throws IOException { super(indexSettings, name, settings);
analyzer = new KoreanAnalyzer(); }
@Override public KoreanAnalyzer get() { return this.analyzer; }
// ArirangTokenFilterFactory public ArirangTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { super(indexSettings, name, settings); }
@Override public TokenStream create(TokenStream tokenStream) { return new KoreanFilter(tokenStream); }
// ArirangTokenizerFactory public ArirangTokenizerFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { super(indexSettings, name, settings); }
@Override public Tokenizer create() { return new KoreanTokenizer(); }
Step7) - rest action 관련 코드를 작성 합니다.
// ArirangAnalyzerRestAction @Inject public ArirangAnalyzerRestAction(Settings settings, RestController controller) { super(settings);
controller.registerHandler(RestRequest.Method.GET, "/_arirang_dictionary_reload", this); }
@Override protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { try { DictionaryUtil.loadDictionary(); } catch (MorphException me) { return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.NOT_ACCEPTABLE, "Failed which reload arirang analyzer dictionary!!")); } finally { }
return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.OK, "Reloaded arirang analyzer dictionary!!")); }
// ArirangAnalyzerRestModule @Override protected void configure() { // TODO Auto-generated method stub bind(ArirangAnalyzerRestAction.class).asEagerSingleton(); }
Step8) - plugin-descriptor.properties 관련 코드를 작성 합니다.
classname=org.elasticsearch.plugin.analysis.arirang.AnalysisArirangPlugin name=analysis-arirang jvm=true java.version=1.8 site=false isolated=true description=Arirang plugin version=${project.version} elasticsearch.version=${elasticsearch.version} hash=${buildNumber} timestamp=${timestamp}
Step9) - 패키징을 하기 위한 plugin.xml 관련 코드를 작성 합니다.
<file> <source>lib/arirang.lucene-analyzer-6.5.1-1.1.0.jar</source> <outputDirectory>elasticsearch</outputDirectory> </file> <file> <source>lib/arirang-morph-1.1.0.jar</source> <outputDirectory>elasticsearch</outputDirectory> </file> <file> <source>target/elasticsearch-analysis-arirang-5.5.0.jar</source> <outputDirectory>elasticsearch</outputDirectory> </file> <file> <source>${basedir}/src/main/resources/plugin-descriptor.properties</source> <outputDirectory>elasticsearch</outputDirectory> <filtered>true</filtered> </file>
Step10) $ mvn clean package -DskipTests=true
여기서는 작성된 코드는 일부만 발췌 했기 때문에 github에 올라간 소스코드를 참고하시기 바랍니다. 또한, 위 단계는 순서가 중요한 것이 아니며 구성과 어떻게 구현을 해야 하는지를 이해 하시는게 중요 합니다.
이제 빌드가 완료 되었으니 설치 및 기능 점검을 수행해 보도록 하겠습니다.
1. 설치 $ bin/elasticsearch-plugin install --verbose file:///path/elasticsearch-analysis-arirang-5.5.0.zip
2. 기능점검 $ bin/elasticsearch [2017-08-22T18:56:17,223][INFO ][o.e.n.Node ] [singlenode] initializing ... [2017-08-22T18:56:17,289][INFO ][o.e.e.NodeEnvironment ] [singlenode] using [1] data paths, mounts [[/ (/dev/disk1)]], net usable_space [489.3gb], net total_space [930.3gb], spins? [unknown], types [hfs] [2017-08-22T18:56:17,289][INFO ][o.e.e.NodeEnvironment ] [singlenode] heap size [1.9gb], compressed ordinary object pointers [true] [2017-08-22T18:56:17,309][INFO ][o.e.n.Node ] [singlenode] node name [singlenode], node ID [saCA_25vSxyUwF-RagteLw] [2017-08-22T18:56:17,309][INFO ][o.e.n.Node ] [singlenode] version[5.5.0], pid[12613], build[260387d/2017-06-30T23:16:05.735Z], OS[Mac OS X/10.12.5/x86_64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_72/25.72-b15] [2017-08-22T18:56:17,309][INFO ][o.e.n.Node ] [singlenode] JVM arguments [-Xms2g, -Xmx2g, -XX:+UseConcMarkSweepGC, -XX:CMSInitiatingOccupancyFraction=75, -XX:+UseCMSInitiatingOccupancyOnly, -XX:+DisableExplicitGC, -XX:+AlwaysPreTouch, -Xss1m, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djna.nosys=true, -Djdk.io.permissionsUseCanonicalPath=true, -Dio.netty.noUnsafe=true, -Dio.netty.noKeySetOptimization=true, -Dio.netty.recycler.maxCapacityPerThread=0, -Dlog4j.shutdownHookEnabled=false, -Dlog4j2.disable.jmx=true, -Dlog4j.skipJansi=true, -XX:+HeapDumpOnOutOfMemoryError, -Des.path.home=/Users/jeonghoug/dev/server/elastic/elasticsearch-5.5.0] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [aggs-matrix-stats] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [ingest-common] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [lang-expression] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [lang-groovy] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [lang-mustache] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [lang-painless] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [parent-join] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [percolator] [2017-08-22T18:56:18,131][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [reindex] [2017-08-22T18:56:18,132][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [transport-netty3] [2017-08-22T18:56:18,132][INFO ][o.e.p.PluginsService ] [singlenode] loaded module [transport-netty4] [2017-08-22T18:56:18,132][INFO ][o.e.p.PluginsService ] [singlenode] loaded plugin [analysis-arirang] [2017-08-22T18:56:19,195][INFO ][o.e.d.DiscoveryModule ] [singlenode] using discovery type [zen] [2017-08-22T18:56:19,686][INFO ][o.e.n.Node ] [singlenode] initialized [2017-08-22T18:56:19,687][INFO ][o.e.n.Node ] [singlenode] starting ... [2017-08-22T18:56:24,837][INFO ][o.e.t.TransportService ] [singlenode] publish_address {127.0.0.1:9300}, bound_addresses {[fe80::1]:9300}, {[::1]:9300}, {127.0.0.1:9300} [2017-08-22T18:56:27,899][INFO ][o.e.c.s.ClusterService ] [singlenode] new_master {singlenode}{saCA_25vSxyUwF-RagteLw}{_fn1si8zTT6bkZK1q6ilxQ}{127.0.0.1}{127.0.0.1:9300}, reason: zen-disco-elected-as-master ([0] nodes joined) [2017-08-22T18:56:27,928][INFO ][o.e.h.n.Netty4HttpServerTransport] [singlenode] publish_address {127.0.0.1:9200}, bound_addresses {[fe80::1]:9200}, {[::1]:9200}, {127.0.0.1:9200} [2017-08-22T18:56:27,928][INFO ][o.e.n.Node ] [singlenode] started
http://localhost:9200/_analyze?pretty&analyzer=arirang_analyzer&text=한국 엘라스틱서치 사용자 그룹의 HENRY 입니다.
{ "tokens" : [ { "token" : "한국", "start_offset" : 0, "end_offset" : 2, "type" : "korean", "position" : 0 }, { "token" : "엘라스틱서치", "start_offset" : 3, "end_offset" : 9, "type" : "korean", "position" : 1 }, { "token" : "엘라", "start_offset" : 3, "end_offset" : 5, "type" : "korean", "position" : 1 }, { "token" : "스틱", "start_offset" : 5, "end_offset" : 7, "type" : "korean", "position" : 2 }, { "token" : "서치", "start_offset" : 7, "end_offset" : 9, "type" : "korean", "position" : 3 }, { "token" : "사용자", "start_offset" : 10, "end_offset" : 13, "type" : "korean", "position" : 4 }, { "token" : "그룹", "start_offset" : 14, "end_offset" : 16, "type" : "korean", "position" : 5 }, { "token" : "henry", "start_offset" : 18, "end_offset" : 23, "type" : "word", "position" : 6 }, { "token" : "입니다", "start_offset" : 24, "end_offset" : 27, "type" : "korean", "position" : 7 } ] }
- 형태소분석기 RESTful endpoint 실행 및 결과
실행) http://localhost:9200/_arirang_dictionary_reload
결과) Reloaded arirang analyzer dictionary!!
이제 기본적인 arirang analyzer와 elasticsearch용 plugin 까지 살펴 보았습니다. 마지막으로 arirang analyzer의 사전 데이터 수정과 반영을 살펴 보겠습니다.
☞ arirang 에서 제공하는 기본 dictionary path 변경을 하지 않고 사전 내용만 변경 하는 것으로 하겠습니다.
1. 사전 파일에 대한 classpath 설정 - elasticsearch 실행 시 사전 파일에 대한 classpath 등록이 되어 있어야 정상적으로 로딩이 됩니다.
- elasticsearch.in.sh 파일을 수정해 줍니다.
ES_CLASSPATH="$ES_HOME/lib/elasticsearch-5.5.0.jar:$ES_HOME/lib/*:$ES_CONF_PATH/dictionary"
예) 위에서 언급한 사전 관련 path와 파일들이 존재해야 합니다. config/dictionary/org/apache/lucene/analysis/ko config/dictionary/org/apache/lucene/analysis/ko/dic - ES_CONF_PATH는 기본 path.conf 정보와 동일해야 합니다.
2. 사전 정보 수정 및 반영 - 1번 path에 위치한 사전 파일을 수정합니다.
3. 사전 reload - elasticsearch restart 없이 /_arirang_dictionary_reload API를 호출하여 반영 합니다.
여기까지 오셨으면 이제 arirang analyzer와 elasticseearch-analysis-arirang plugin 그리고 dictionary에 대한 기본 활용을 하실수 있게 되셨다고 생각합니다. 기술된 모든 정보는 모두 오픈소스이기 때문에 출처를 정확히 명시해 주시고 언제든지 오류와 개선에 대해서는 적극적인 참여 부탁 드립니다.
참고 사이트) http://cafe.naver.com/korlucene https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html https://www.elastic.co/guide/en/elasticsearch/plugins/current/index.html
|