에러 분석
SpringBoot (Java Code)
현재 엘라스틱서치 클라이언트로 RestHighLevelClient를 사용하고 있다
해당 라이브러리에서 형태소 분석을 위해 한 API가 analyze()을 사용하고 있다
AnalyzeRequest request = AnalyzeRequest.withIndexAnalyzer(indexName, analyzerName, message);
request.explain(true);
AnalyzeResponse response = restHighLevelClient.indices().analyze(request, RequestOptions.DEFAULT);
그런데 특정 단어에서 IOException이 발생하였다
(분석기는 nori를 사용했다)
java.io.IOException: Unable to parse response body for Response{requestLine=GET /chat-autocomplete/_analyze HTTP/1.1, host=http://localhost:9200, response=HTTP/1.1 200 OK}
at org.elasticsearch.client.RestHighLevelClient.internalPerformRequest(RestHighLevelClient.java:1533) ~[elasticsearch-rest-high-level-client-7.6.2.jar:7.6.2]
... (생략)
Caused by: org.elasticsearch.common.xcontent.XContentParseException: [1:2579] [analyze_response] failed to parse field [detail]
at org.elasticsearch.common.xcontent.ObjectParser.parseValue(ObjectParser.java:429) ~[elasticsearch-x-content-7.6.2.jar:7.6.2]
... (생략)
Caused by: org.elasticsearch.common.xcontent.XContentParseException: [1:2579] [detail] failed to parse field [tokenfilters]
... 69 more
Caused by: org.elasticsearch.common.xcontent.XContentParseException: [1:2579] [token_list] failed to parse field [tokens]
... 69 more
Caused by: com.fasterxml.jackson.core.JsonParseException: Duplicate field 'positionLength'
at [Source: (org.apache.http.nio.entity.ContentInputStream); line: 1, column: 2606]
... 69 more
익셉션이 발생하는 위치를 찾아보았다
[1:2579] [analyze_response] failed to parse field [detail]
[1:2579] [detail] failed to parse field [tokenfilters]
[1:2579] [token_list] failed to parse field [tokens]
// ObjectParser.java
private void parseValue(XContentParser parser, FieldParser fieldParser, String currentFieldName, Value value, Context context)
throws IOException {
try {
fieldParser.parser.parse(parser, value, context);
} catch (Exception ex) {
throw new XContentParseException(parser.getTokenLocation(),
"[" + name + "] failed to parse field [" + currentFieldName + "]", ex);
}
Caused by: com.fasterxml.jackson.core.JsonParseException:
Duplicate field 'positionLength'
public void setCurrentName(String name) throws JsonProcessingException {
_currentName = name;
if (_dups != null) { _checkDup(_dups, name); }
}
private void _checkDup(DupDetector dd, String name) throws JsonProcessingException {
if (dd.isDup(name)) {
Object src = dd.getSource();
throw new JsonParseException(((src instanceof JsonParser) ? ((JsonParser) src) : null),
"Duplicate field '"+name+"'");
}
}
파싱이 제대로 이뤄지지 않았고, parser.nextToken()에서 _checkDup이 True가 되면서 JsonParseException이 발생했다
Json 값을 파싱하다 positionLength 필드 중복이 발생했다
Elasticsearch
ES에 직접 조회해보았다
GET chat-autocomplete/_analyze
{
"analyzer": "nori_analyzer",
"text": "판타스틱",
"explain": true
}
결과를 보니 몇몇 token에서 positionLength 필드가 중복되어 내려오고 있었다
positionLength 필드를 자바 코드에서 파싱하다가 중복이 발생해서 익셉션이 발생한 것이다
{
"name" : "nori_posfilter",
"tokens" : [
{
"token" : "팬더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0,
"positionLength" : 2,
"bytes" : "[ed 8c ac eb 8d 94]",
"leftPOS" : null,
"morphemes" : null,
"posType" : null,
"positionLength" : 2,
"reading" : null,
"rightPOS" : null,
"termFrequency" : 1
},
{
"token" : "판",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0,
"bytes" : "[ed 8c 90]",
"leftPOS" : null,
"morphemes" : null,
"posType" : null,
"positionLength" : 1,
"reading" : null,
"rightPOS" : null,
"termFrequency" : 1
},
{
"token" : "판",
"start_offset" : 0,
"end_offset" : 1,
"type" : "word",
"position" : 0,
"positionLength" : 2,
"bytes" : "[ed 8c 90]",
"leftPOS" : "NNG(General Noun)",
"morphemes" : null,
"posType" : "MORPHEME",
"positionLength" : 2,
"reading" : null,
"rightPOS" : "NNG(General Noun)",
"termFrequency" : 1
},
{
"token" : "더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 1,
"bytes" : "[eb 8d 94]",
"leftPOS" : null,
"morphemes" : null,
"posType" : null,
"positionLength" : 1,
"reading" : null,
"rightPOS" : null,
"termFrequency" : 1
},
{
"token" : "스틱",
"start_offset" : 2,
"end_offset" : 4,
"type" : "word",
"position" : 4,
"bytes" : "[ec 8a a4 ed 8b b1]",
"leftPOS" : "NNG(General Noun)",
"morphemes" : null,
"posType" : "MORPHEME",
"positionLength" : 1,
"reading" : null,
"rightPOS" : "NNG(General Noun)",
"termFrequency" : 1
}
]
}
이렇게 positionLength가 중복되어 내려오는 현상이 어떤 경우인지 찾아보았다
"판타스틱"을 nori 분석기로 형태소 분석을 해보면 이렇게 내려왔다
"판"이라는 토큰이 중복되고 있다
{
"tokens" : [
{
"token" : "팬더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0,
"positionLength" : 2
},
{
"token" : "판",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0
},
{
"token" : "판",
"start_offset" : 0,
"end_offset" : 1,
"type" : "word",
"position" : 0,
"positionLength" : 2
},
{
"token" : "더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 1
},
{
"token" : "스틱",
"start_offset" : 2,
"end_offset" : 4,
"type" : "word",
"position" : 4
}
]
}
사전을 확인해보니.
Fan의 동의어로 판, 팬더, 판더가 등록되어 있었다
판더는 따로 용어사전에 등록되어 있지 않았다
그러다보니 nori로 인해 "판" + "더"로 분해했다
이렇게 토큰이 중복되는 경우에 positionLength 필드가 중복되어져 내려오는 것을 확인하였다
해결
판더를 용어사전에 넣어주면 해결된다
그러면 nori 분석기는 "판더"를 그대로 토큰으로 인식한다.
GET chat-autocomplete/_analyze
{
"analyzer": "nori_analyzer",
"text": "판타스틱"
}
그리고 중복되는 토큰이 없다
{
"tokens" : [
{
"token" : "팬더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0
},
{
"token" : "판더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0
},
{
"token" : "판",
"start_offset" : 0,
"end_offset" : 1,
"type" : "word",
"position" : 0
},
{
"token" : "스틱",
"start_offset" : 2,
"end_offset" : 4,
"type" : "word",
"position" : 3
}
]
}
explain을 통해 확인해보면, positionLength가 1개만 내려온다
{
"name" : "nori_posfilter",
"tokens" : [
{
"token" : "팬더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0,
"bytes" : "[ed 8c ac eb 8d 94]",
"leftPOS" : null,
"morphemes" : null,
"posType" : null,
"positionLength" : 1,
"reading" : null,
"rightPOS" : null,
"termFrequency" : 1
},
{
"token" : "판더",
"start_offset" : 0,
"end_offset" : 1,
"type" : "SYNONYM",
"position" : 0,
"bytes" : "[ed 8c 90 eb 8d 94]",
"leftPOS" : null,
"morphemes" : null,
"posType" : null,
"positionLength" : 1,
"reading" : null,
"rightPOS" : null,
"termFrequency" : 1
},
{
"token" : "판",
"start_offset" : 0,
"end_offset" : 1,
"type" : "word",
"position" : 0,
"bytes" : "[ed 8c 90]",
"leftPOS" : "NNG(General Noun)",
"morphemes" : null,
"posType" : "MORPHEME",
"positionLength" : 1,
"reading" : null,
"rightPOS" : "NNG(General Noun)",
"termFrequency" : 1
},
{
"token" : "스틱",
"start_offset" : 2,
"end_offset" : 4,
"type" : "word",
"position" : 3,
"bytes" : "[ec 8a a4 ed 8b b1]",
"leftPOS" : "NNG(General Noun)",
"morphemes" : null,
"posType" : "MORPHEME",
"positionLength" : 1,
"reading" : null,
"rightPOS" : "NNG(General Noun)",
"termFrequency" : 1
}
]
}
댓글