'search'에 해당되는 글 42건

  1. 2018.04.06 [Elasticsearch] Split Index 기능 맛보기
  2. 2018.04.06 [Elasticsearch] Elasticsearch Case Study 1) Data 노드에 Index/Shard 구성 시작 해보기
  3. 2018.01.31 [검색] re-ranking 시 사용하는 함수에 주의 하세요.
  4. 2017.12.27 [링크] 검색 Trend
  5. 2017.07.31 [Lucene] SynonymFilter -> SynonymGraphFilter + FlattenGraphFilter
  6. 2017.07.31 [미미박스 검색이야기] 뷰티풀 솔루션 (Beautyfull Solution)
  7. 2017.07.28 [미미박스 검색이야기] Quick Search - 퀵서치
  8. 2017.07.17 [Elasticsearch] Multi Search API 사용
  9. 2017.06.23 [Arirang] 사전 기반으로만 형태소 분석 처리 해보기
  10. 2017.06.09 [Arirang Analyzer - lucene 6.5.0] Term startOffset 정렬 오류

[Elasticsearch] Split Index 기능 맛보기

Elastic/ElasticsearchReferences 2018. 4. 6. 14:24

Split index 기능에 대해서 공식 문서에 나와 있는 내용을 그대로 테스트 해봤습니다.

(Shrink 와 Split 은 모두 Resize Action 에 해당 합니다.)


Reference)

https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-split-index.html


이 split index API 는 기존에 존재하는 또는 생성된 index 의 primary shard 를 신규 index 로 기존 primary shard 크기 보다 크게 split 해서 생성해 주는 역할을 합니다.


RESTful endpoint 는 _split 입니다.


근데 7.0 에서는 삭제 된다고 하니 제가 이걸 왜 쓰고 있는지 모르겠습니다.

하지만 늘 그렇듯이 Elasticsearch 에서는 기능이 없어지게 되면 대체 가능한 기능을 추가로 제공해 줍니다. :)


이 API 사용시 제일 중요한 부분은 IMPORTANT 에 잘 나와 있습니다.


The _split API requires the source index to be created with a specific number_of_routing_shards in order to be split in the future. This requirement has been removed in Elasticsearch 7.0.


※ Source index 를 생성 할때 설정을 해줘야 한다는 이야기 입니다.


그럼 문서에 나와 있는 데로 한번 돌려 보겠습니다.

하지 전에, elasticsearch 하나 띄워 놓으시고 kibana 도 띄워 놓으세요. 그래야 편하게 dev tool 이용해서 request 할 수 있겠죠.


Step 1) Preparing an index for splitting

Source index 를 생성 하는 과정이며 이 과정에서 반드시 routing shard 정보를 설정 하셔야 합니다.


PUT my_source_index

{

    "settings": {

        "index.number_of_shards" : 1,

        "index.number_of_replicas" : 0,

        "index.number_of_routing_shards" : 2

    }

}


Step 2) Set read-only mode

Split 하기 전에 반드시 읽기 전용으로 반드시 설정을 하셔야 합니다. 안하시면 에러 납니다.

그럼 왜 해야 할까요?

보통 DB 마이그레이션 작업 하실때 생각을 해보시면 됩니다. :)


PUT /my_source_index/_settings

{

  "settings": {

    "index.blocks.write": true 

  }

}


문서를 자세히 보기 전까지 저 설정이 block 단위로 write 할 수 있도록 해주는 설정인 줄 알았습니다. ^^;


Step 3) Splitting an idex

이제 primary shard 를 2 개로 늘려 보겠습니다.


POST my_source_index/_split/my_target_index

{

  "settings": {

    "index.number_of_shards": 2,

    "index.number_of_replicas" : 0

  }

}


이 단계 작업을 하면서 주의 할 점은 당연한 내용이지만, 

1. target index 가 없어야 겠죠.

2. source index 의 primary shard 보다 커야 겠죠.

3. factor 조건을 만족 해야 합니다.


routingNumShards % numTargetShards != 0 이면 안되구요.


if (sourceNumberOfShards < targetNumberOfShards) { // split

factor = targetNumberOfShards / sourceNumberOfShards;

if (factor * sourceNumberOfShards != targetNumberOfShards || factor <= 1) { 이면 안되구요. }

}


즉,

- 나눠서 나머지가 없어야 합니다.

- target shard 는 routing shard 보다 작거나 같아야 합니다.


아래는 factor 조건을 만족하지 않는 예시 입니다.


PUT my_source_index

{

    "settings": {

        "index.number_of_shards" : 1,

        "index.number_of_replicas" : 0,

        "index.number_of_routing_shards" : 4

    }

}


PUT /my_source_index/_settings

{

  "settings": {

    "index.blocks.write": true 

  }

}


POST my_source_index/_split/my_target_index

{

  "settings": {

    "index.number_of_shards": 3,

    "index.number_of_replicas" : 0

  }

}


조건 1) 

4 % 3 => 1 // 0 이 되어야 합니다.


조건 2) 

assert getRoutingFactor(3, 4) >= 0


if ( 3 < 4 ) {

    factor = 4 / 3; // factor = 1

    if ( factor * 3 != 4 || factor <= 1) {

        throw new IllegalArgumentException(.....);

    }

}



혹시라도 사용하셔야 하는 분들이 계시다면 참고 하시면 되겠습니다.

:

[Elasticsearch] Elasticsearch Case Study 1) Data 노드에 Index/Shard 구성 시작 해보기

Elastic/Elasticsearch 2018. 4. 6. 10:13

Elasticsearch Case Study 1) Data 노드에 Index/Shard 구성 시작 해보기

- Data Node 3개, Active Index 1개

- Data Node Spec)

CPU : 16 cores

- 지속적인 Read/Write operaiton 이 발생 하는 경우로 가정 하겠습니다.


Primary/Replica Shard Sizing)

- Shard 구성을 하실 때 가장 쉽게 접근 할 수 있는 방법은 CPU 코어 크기를 가지고 판단 하시면 됩니다.

- Data 노드 하나당 active shard 로 배치 할 수 있는 가장 기본은 코어 크기와 똑같이 구성 하시는 것입니다.


위에 16 코어로 가정했기 때문에 Data 노드에는 16개의 Shard 를 할당 할 수 있습니다.


Elasticsearch Settings 정보)

curl -XPUT http://localhost:9200/case-study-1/_settings '{

    "settings" : {

        "index" : {

            "number_of_shards" : 15,

            "number_of_replicas" : 2

        }

    }

}'


이와 같이 Index 를 생성 하게 되면 아래와 같이 shard 가 배치 됩니다.


Data node 1 : 

Primary Shards : 0, 1, 2, 3, 4

Replica Shards : 5, 6, 7, 8, 9, 10, 11, 12, 13, 14

Data node 2 :

Primary Shards : 5, 6, 7, 8, 9

Replica Shards : 0, 1, 2, 3, 4, 10, 11, 12, 13, 14

Data node 3 :

Primary Shards : 10, 11, 12, 13, 14

Replica Shards : 5, 6, 7, 8, 9, 0, 1, 2, 3, 4


보시게 되면 모든 Data 노드에 Shard 가 15개씩 할당 되어 있는 것을 확인 하실 수 있습니다.

여기까지 확인 하셨으면 이제 부터가 시작 입니다.


Data 노드에 Index, Shard에 대한 기본 설정은 했지만 이게 최적화 된 것인지는 알수 없습니다.

그래서 사용환경에 맞춰서 성능 테스트를 해 보셔야 합니다.


아래 질문에 답변을 해보세요.


1. 내가 사용하고자 하는 클러스터는 질의 및 분석에 최적화 되어야 한다.

2. 내가 사용하고자 하는 클러스터는 색인에 최적화 되어야 한다.

3. 내가 사용하고자 하는 클러스터는 질의, 분석 그리고 색인 모두 최적화 되어야 한다.


1번을 원하신다면 아래 항목들에 대해서 검토를 해보시면 좋습니다.

질의 와 분석은 CPU 와 Memory 를 많이 사용하기 때문에 충분한 자원이 준비 되어 있어야 합니다.

그리고 Document 에 대한 mapping 정보 최적화가 필요 합니다.

1. Match query 종류를 사용해야 하는가? (주로 Full text query를 의미 합니다.)

- Elastic 사에서는 Full text queries 라고 합니다.

2. Term query 종류를 사용해야 하는가? (주로 Exact match query를 의미 합니다.)

- Elastic 사에서는 Term level queries 라고 합니다.

3. Aggregation 질의가 많은가?

4. Nested 유형의 Aggregation 질의가 많은가?


2번을 원하신다면 아래 항목들에 대해서 검토를 해보시면 좋습니다.

색인 작업은 CPU 와 Disk I/O 성능에 영향을 많이 받습니다.

또한 사용하는 형태소 분석기에 따른 성능 변화도 확인을 하셔야 합니다.


사실 색인에 최적화 라는건 Bulk Indexing 이 아니고서는 다른 주제 인것 같습니다.

로그 데이터의 경우 보통은 Data 노드의 처리량을 고려해서 앞단에 Queue 를 사용하고,

여기서 Beats 나 Logstash 와 같은 Shipper 를 이용해서 Elasticsearch 로 색인하도록 구성을 합니다.

결과적으로 Queue + Beats/Logstash + Elasticsearch 에 대한 최적화 작업 없이는 어려운 작업 입니다.


Elasticsearch 관점에서 바라보면:Bulk Indexing)

1. Dynamic mapping 사용을 피하는게 좋습니다.

2. 불필요한 Analyzed 과정을 제거 하는게 좋습니다.

3. Bulk 요청 시 Replica 와 Refresh 를 사용하지 않는게 좋습니다.

4. _all Field 사용을 피하는게 좋습니다.

5. _id 는 가능 하면 임의 설정을 하지 않는게 좋습니다.

6. Index Buffer Size 를 512MB 정도까지 크게 설정 하는게 좋습니다.

7. 기타 등등 사소한 튜닝 팁들이 많습니다.


3번을 원하신다면 아래 항목들에 대해서 검토를 해보시면 좋습니다.

가장 어려운 요구사항 입니다.

이런 경우 클러스터의 노드와 인덱스 구성을 분리해서 사용하시는게 좋습니다.

1. Cross Cluster Search(Tribe Node) 에 대해서 검토해 봅니다.

2. Hot-Warm Architecture 에 대해서 검토해 봅니다.

3. Index Alias 기능에 대해서 검토해 봅니다.

4. Shrink, Split, Reindex, Rollover, Rollup 기능에 대해서 검토해 봅니다.

5. Snapshot 과 Restore 기능에 대해서 검토해 봅니다.


이번 Case Study 는 아주 단순 합니다.

요구사항은 알수 없고 단순히 Data 노드 규모와 스펙만으로 Index/Shard 배치를 설정 하는 것이였습니다.

"왜 이렇게/저렇게 해야 하지?" 라는 궁금증이 드시는 부분은 이해를 돕기 위해 추가적인 설명이 들어가야 하는데 과감히 생략 했습니다. (커뮤니티에 질문 주시거나 저를 만나시면 물어보세요. 친절히 설명해 드리겠습니다.)


항상 반복 되는 이야기지만,

사용 환경에 맞게 테스트 하시고 최적화 하는게 정답입니다.

시작 하기 전에 조금이나마 도움이 될 수 있는 내용을 작성한 것이지 이대로 하면 된다는 것은 아닙니다.

알아야 할 것도 많고 검증 해야 할 것도 많습니다.

시간과 인력이 부족 하시다면 Elastic 사에서 지원하는 좋은 프로그램들이 있으니 참고 하셔도 좋을 것 같습니다.


궁금 하신 것들이 있으시면 Facebook 유저 커뮤니티에 질문으로 올려 주세요.

제가 할 수 있다면 도움 드릴 수 있도록 하겠습니다.

:

[검색] re-ranking 시 사용하는 함수에 주의 하세요.

ITWeb/검색일반 2018. 1. 31. 11:19

Elasticsearch 나 Solr 나 모두 내장 함수를 제공 하고 있습니다. (Lucene function 이기도 합니다.)

그런데 이 내장 함수를 이용해서 re-ranking 작업을 많이들 하시는 데요.

하는건 문제가 없지만 시스템 리소스에 영향을 주거나 성능적으로 문제가 되는 함수는 사용하지 않도록 주의 하셔야 합니다.

그냥 무심코 사용했다가 왜 성능이 안나오지 하고 맨붕에 빠지실 수 있습니다.

Function Score Query 나 Script 를 이용한 re-ranking 시 꼭 검토하세요.

re-ranking 은 보통 질의 시점에 수행을 하기 때문에 기본적으로 operation cost 가 비쌉니다.


lucene/queries/function/valuesource

TermFreqValueSource.java)

/**
* Function that returns {@link org.apache.lucene.index.PostingsEnum#freq()} for the
* supplied term in every document.
* <p>
* If the term does not exist in the document, returns 0.
* If frequencies are omitted, returns 1.
*/
public class TermFreqValueSource extends DocFreqValueSource {
public TermFreqValueSource(String field, String val, String indexedField, BytesRef indexedBytes) {
super(field, val, indexedField, indexedBytes);
}

@Override
public String name() {
return "termfreq";
}

@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
Fields fields = readerContext.reader().fields();
final Terms terms = fields.terms(indexedField);

return new IntDocValues(this) {
PostingsEnum docs ;
int atDoc;
int lastDocRequested = -1;

{ reset(); }

public void reset() throws IOException {
// no one should call us for deleted docs?

if (terms != null) {
final TermsEnum termsEnum = terms.iterator();
if (termsEnum.seekExact(indexedBytes)) {
docs = termsEnum.postings(null);
} else {
docs = null;
}
} else {
docs = null;
}

if (docs == null) {
docs = new PostingsEnum() {
@Override
public int freq() {
return 0;
}

@Override
public int nextPosition() throws IOException {
return -1;
}

@Override
public int startOffset() throws IOException {
return -1;
}

@Override
public int endOffset() throws IOException {
return -1;
}

@Override
public BytesRef getPayload() throws IOException {
throw new UnsupportedOperationException();
}

@Override
public int docID() {
return DocIdSetIterator.NO_MORE_DOCS;
}

@Override
public int nextDoc() {
return DocIdSetIterator.NO_MORE_DOCS;
}

@Override
public int advance(int target) {
return DocIdSetIterator.NO_MORE_DOCS;
}

@Override
public long cost() {
return 0;
}
};
}
atDoc = -1;
}

@Override
public int intVal(int doc) {
try {
if (doc < lastDocRequested) {
// out-of-order access.... reset
reset();
}
lastDocRequested = doc;

if (atDoc < doc) {
atDoc = docs.advance(doc);
}

if (atDoc > doc) {
// term doesn't match this document... either because we hit the
// end, or because the next doc is after this doc.
return 0;
}

// a match!
return docs.freq();
} catch (IOException e) {
throw new RuntimeException("caught exception in function "+description()+" : doc="+doc, e);
}
}
};
}
}



:

[링크] 검색 Trend

ITWeb/스크랩 2017. 12. 27. 09:58

검색 Trend 를 알아보기 위한 대표 링크.


[네이버]

https://datalab.naver.com/


[구글]

https://trends.google.co.kr/trends/explore

:

[Lucene] SynonymFilter -> SynonymGraphFilter + FlattenGraphFilter

ITWeb/검색일반 2017. 7. 31. 18:37

오늘 뭐 좀 보다가 그냥 공유해 봅니다.
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html


lucene 6.6 에서는 SynonymFilter@Deprecated 되어 있습니다.

대체 filter 는 글에도 나와 있지만 SynonymGraphFilter 인데요.

재밌는건 이넘은 search time 에서 동작 하는 거라 index time 에는 여전히 SynonymFilter 또는 FlattenGraphFilter 를 사용해야 한다는 점입니다.

아직 깊게 분석해보지 않아서 ^^; 


간만에 lucene 코드 까서 이것 저것 테스트 해보니 재밌내요.

그냥 참고 하시라고 올려봤습니다.


:

[미미박스 검색이야기] 뷰티풀 솔루션 (Beautyfull Solution)

ITWeb/미미박스검색이야기 2017. 7. 31. 16:03

2017년 7월 31일.

고객의 고민 해결을 위한 서비스를 이커머스 회사에서 이렇게 해냅니다. ^^


물건을 팔기 위함이 목적이 아닌 고객의 고민을 해결해 드리기 위해 기획하고 만든 서비스 입니다.

아직 부족한 점이 많지만 고객님들의 고민이 해결 되는 그날까지 최선을 다 해보겠습니다.


경험해 보기)

https://m.search.memebox.com/memebox/solution/home


검색어 입력 방법)

일반적인 얼굴 부위에 대한 메이크업 고민, 피부 트러블에 대한 고민, 스킨케어에 대한 고민을 입력해 주시면 됩니다.


예)

"얼굴이 너무 커요"

"유분이 많아서 화장이 무너져요"



※ 본 글은 회사의 입장과는 전혀 상관이 없으며 개인적인 의견으로 작성된 글 임을 알려 드립니다.


:

[미미박스 검색이야기] Quick Search - 퀵서치

ITWeb/미미박스검색이야기 2017. 7. 28. 16:35


기능 적용한지 좀 되었는데 이제야 홍보 합니다. ^^;


퀵서치라는 기능은 기본적으로 모바일 환경에서 검색어를 입력하지 않고 검색 필터 기능을 활용해서 쉽게 원하는 조건의 상품을 검색해서 볼 수 있도록 제공하기 위해 만들어 졌습니다.


더불어 해당 영역은 검색 이외 "바로가기" 기능이나 특정 페이지로 랜딩 시키기 위한 용도로도 활용이 가능 합니다.


구경하기)

http://m.search.memebox.com/





※ 본 글은 회사의 입장과는 전혀 상관이 없으며 개인적인 의견으로 작성된 글 임을 알려 드립니다.


:

[Elasticsearch] Multi Search API 사용

Elastic/Elasticsearch 2017. 7. 17. 12:48

2.X 랑 5.X 랑 크게 바뀐 부분은 없습니다.

다만 5.X 에서는 template 지원도 함께 됩니다.


참고문서)

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html


Multi Search API를 얼마나 많은 분들이 사용하고 계신지는 잘 모르겠습니다.

이 API 를 한 줄로 정의 하면 "통합 검색 API" 라고 할 수 있습니다.

뭐 동의 하지 않으시는 분들이 계시다면 어쩔수 없구요.


설명을 풀어서 하면, 서로 다른 query 를 하나의 index 에 질의 하거나 하나의 query 를 서로 다른 index 로 질의 할 때 사용 하시면 유용 합니다.


Case 1)

Single Query + Multi Index


Case 2)

Multi Query + Single Index


Case 3)

Multi Query + Multi Index


위 참고문서 에서는 Request 에 대한 내용은 나와 있지만 실제 Response 에 대한 예시는 나와 있지 않아 실제 실행 보지 않고서는 어떻게 결과가 나올지 모르실 수도 있습니다.

(사실 상상은 되실거예요.)


아래는 제가 테스트로 하나의 클러스터에 "Case 3" 으로 실행한 결과 입니다.


Request API)

Endpoint : http://xxxx/_msearch

Method : POST (raw)


Request Query)

{"index":"service_product"}

{"query":{"match_all":{}},"from":0,"size":1}

{"index":"service_item"}

{"query":{"term":{"title":{"value":"틴트"}}},"from":0,"size":1}


Response Data)

{

    "responses": [

        {

            "took": 1,

            "timed_out": false,

            "_shards": {

                "total": 1,

                "successful": 1,

                "failed": 0

            },

            "hits": {

                "total": xxxxxx,

                "max_score": 1,

                "hits": [

                    {

                        "_index": "service_product_201707171215",

                        "_type": "deal",

                        "_id": "7510",

                        "_score": 1,

                        "_source": {

                        ... 생략 ...

                        }

                    }

                ]

            }

        },

        {

            "took": 1,

            "timed_out": false,

            "_shards": {

                "total": 1,

                "successful": 1,

                "failed": 0

            },

            "hits": {

                "total": xxxxxx,

                "max_score": 7.0183215,

                "hits": [

                    {

                        "_index": "service_item_201707171215",

                        "_type": "item",

                        "_id": "1170617",

                        "_score": 7.0183215,

                        "_source": {

                            ... 중략 ...

                            "title": "라네즈 투톤틴트바 No.3 2g 틴트민트",

                            ... 중략 ...

                        }

                    }

                ]

            }

        }

    ]

}


결과적으로 보면, 검색엔진에서 해당 연산과 실행을 하실지 아니면 별도 API Gateway 같은 WAS 에서 연산과 실행을 하실지에 대한 문제로 트래픽과 용량을 잘 산정 하셔서 사용하시면 매우 유용하리라 생각 합니다.

(너무 오래 전에 비슷한 내용을 올린 것 같아 5.5 릴리즈 기념으로 한번 더 복습해 봤습니다.)


:

[Arirang] 사전 기반으로만 형태소 분석 처리 해보기

ITWeb/검색일반 2017. 6. 23. 13:51

그냥 사전만 가지고 몇 가지 형태소 분석 처리를 하기 위한 팁 정보 입니다.

한마디로 노가다 입니다.

모든 부분에 공통적으로 적용 되는 것은 아니며 사용 형태에 따라 수정 하셔야 하는 부분이니 그냥 참고 정도만 하자라고 생각해 주세요.


공통)

- 복합명사 분해 시 분해 된 단어가 용언일 경우 복합명사를 사용하지 말고 확장사전에 등록해서 사용을 합니다.

  또는 분해 된 단어가 용언일 경우 찾아서 체언 처리를 해줍니다.

- 체언과 기타품사 차이는 체언은 단독으로 사용 시 형태소 분석이 되지만 기타품사는 분석 되지 않습니다.


  복합명사)

    그리는게:그리는,게:0000


  확장사전)

    그리,100000000X

    그리는게,100000000X


  분해)

    그리는게

      그리는게

    그리는

      그리


'그리는' 자체를 체언으로 분해 하고 싶을 경우 확장 사전에 체언으로 등록이 되어야 하며, 그리에 대한 용언도 동일하게 체언처리가 되어야 합니다.



- '~요', '~해요' 로 끝나는 용언 처리

  좋아요

  좋아해요


  확장사전)

    좋아,100000000X

    좋아해,100000000X



- '~져', '~져서', '~서' 로 끝나는 용언 처리

  기존 용언으로 등록된 단어를 체언으로 변경 해야 합니다.

  010000000X -> 100000000X

  '~서' 의 경우 사전에 '서,110000000X' 와 같이 등록이 되어 있어 복합명사 사전에 추가 등록을 합니다.

  복합명사 등록 시 분해된 명사에 대한 확장사전 등록이 되어 있어야 합니다.


  확장사전)

    어두워지,100000000X

    어두워,100000000X

    늘어지,100000000X

    늘어져,100000000X


  복합명사)

    어두워서:어두워서,어두워:0000

    어두워져:어두워져,어두워:0000

    어두워져서:어두워져서,어두워:0000

    늘어져서:늘어져서,늘어져:0000



- 복합용언 + '~요' 로 끝나는 용언 처리

  크고낮아요

  말려들어요


  복합명사)

    크고낮아:크고,낮아:0000

    말려들어:말려,들어:0000



- '~다', '~데' 로 끝나는 용언 처리

  크다

  작다

  큰데

  작은데

  '~다' 끝나는 용언이 형태소분리가 되기 위해서는 확장사전에 등록이 되어야 합니다.


  확장사전)

    크다,100000000X

    작다,100000000X

    큰데,100000000X

    작은데,100000000X

  


- '~ㄴ', '~은', '~는' 으로 끝나는 용언 처리

  짧은

  넒은

  튀어나온

  어울리는

    어울리,010000000X 용언 처리가 되어 있기 때문에 체언으로 fully 등록 합니다.

  잃어가는


  확장사전)

    짧은,100000000X

    넓은,100000000X

    튀어나온,100000000X

    잃어가는,100000000X

    어울리는,100000000X



- 'ㅎ' 불규칙 용언 처리

  노랗고

  동그랗고


  확장사전)

    노랗,100000000X


  복합명사)

    노랗고:노랗고,노랗:0000


- '~하', '~한' 으로 끝나는 용언 처리

  확장사전에 용언 처리가 되어 있는지 확인 합니다.

  용언 처리가 되어 있다면 체언으로 변경해 줍니다.

  확장사전에 ~하, ~한 을 제외 및 하다 동사 표기를 포함한 체언으로 등록 합니다.


  확장사전 1)

    넓적하,010000000X -> 100000000X

    넓적,100100000X

:

[Arirang Analyzer - lucene 6.5.0] Term startOffset 정렬 오류

ITWeb/검색일반 2017. 6. 9. 10:23

[arirang-analyzer-6.5.0]

  term analyzed 시 startOffset 정보에 대한 정렬이 역전 되는 오류

  개별 term 에서의 startOffset 이 역전 되기 때문에 아래 class 의 method 에서 정렬을 다시 맞춰줍니다.

  (정상적인 방법 이라기 보다는 일단 문제를 회피하기 위한 방법 입니다.)


  Class : KoreanFilter

  Method 1 :

    private void analysisKorean(String input) throws MorphException {


  //  input = trimHangul(input);

      List<AnalysisOutput> outputs = morph.analyze(input);

      if (outputs.size() == 0) {

        return;

      }


      Map<String, KoreanToken> map = new LinkedHashMap<String, KoreanToken>();

      if (hasOrigin) {

        map.put("0:" + input, new KoreanToken(input, offsetAtt.startOffset()));

      }


      extractKeyword(outputs, offsetAtt.startOffset(), map, 0);


      Collection<KoreanToken> values = map.values();

      for (KoreanToken kt : values) {

        kt.setOutputs(outputs);

      }


      // 이 부분에서 map 에 등록된 정보를 정렬 합니다.


      morphQueue.addAll(map.values());

    }


  Method 2 :

    private void analysisKorean(String input) throws MorphException {


  //  input = trimHangul(input);

      List<AnalysisOutput> outputs = morph.analyze(input);

      if (outputs.size() == 0) {

        return;

      }


      Map<String, KoreanToken> map = new LinkedHashMap<String, KoreanToken>();

      if (hasOrigin) {

        map.put("0:" + input, new KoreanToken(input, offsetAtt.startOffset()));

      }


      extractKeyword(outputs, offsetAtt.startOffset(), map, 0);


      Collection<KoreanToken> values = map.values();

      for (KoreanToken kt : values) {

        kt.setOutputs(outputs);

      }


      morphQueue.addAll(map.values());

      // 이 부분에서 morphQueue 에 등록된 정보를 정렬 합니다.

      morphQueue.sort(Comparator.comparingInt(KoreanToken::getOffset));

    }


  Method 3 : 

    protected void extractKeyword(List<AnalysisOutput> outputs, int startoffset,

      final Map<String, KoreanToken> map, int position) {

      ... 원본 코드 생략


      // 이 부분에서 map 에 대한 등록된 정보를 정렬 합니다.

    }


  정렬 방법 :

    참고) https://stackoverflow.com/questions/109383/sort-a-mapkey-value-by-values-java


    final List<Map.Entry<String, KoreanToken>> offsetSorts = map.entrySet().stream()

        .sorted(Map.Entry.comparingByValue(Comparator.comparingInt(KoreanToken::getOffset)))

        .collect(Collectors.toList());


    map.clear();


    offsetSorts.stream().forEachOrdered(e -> map.put(e.getKey(), e.getValue()));


  Method 4 :

    KoreanFilter 를 상속받아 CustomKoreanFilter 를 만들어 사용 하면 됩니다.


: