티스토리 뷰
[Elasticsearch] 데이터 매핑 및 색인화 - 2. 동시성, Analyzer, Flattened, Mapping Exception
dev_jun 2022. 11. 22. 22:13동시성
Elasticsearch와 같은 분산형 시스템을 다룰 때 동시성 관련 문제가 생길 수 있다. 두 클라이언트가 동시에 작업을 수행하려고 하면 어떻게 될까? 이것이 바로 동시성 문제이다. Elasticsearch에선 이를 어떻게 해결할 수 있을지 알아보자.
- Optimistic Concurrency Control
- 업데이트에 관해 다룰 때
_version
을 얘기한 것과 비슷한 방식이다. - 차이점은 단일 version field 대신 sequence number와 해당 sequence를 소유하는 기본 shard가 있다는 것이다. sequence number와 primary term을 함께 가져옴으로써 해당 document의 고유한 연대 기록을 갖게 된다.
- 두 개의 요청이 있고, 각 요청은 모두
_seq_no: 9, _primary_term: 1
을 가지고 있다고 가정하자. 이 경우 첫번째 요청이 데이터를 증가시키면 첫번째 요청은_seq_no: 10, _primary_term: 1
로 업데이트가 된다. 이때 두번째 요청이 데이터를 업데이트하려고 하면 현재의_seq_no
는 9가 아닌 10이기 때문에 에러가 발생하고 업데이트가 되지 않으며_seq_no
를 다시 10으로 업데이트를 한 후에 요청이 정상적으로 업데이트 된다. 그리고 이 과정은 업데이트시retry_on_conflict
옵션을 사용하여 자동으로 충돌 발생시 재시도를 할 수 있게 한다.- e.g)
curl -XPOST 127.0.0.1:9200/movies/_doc/109487/_update?retry_on_conflict=5 -d '{~~~}'
- e.g)
- 즉, 이 seq_no를 사용해 서로 충돌이 발생하지 않고 충돌이 발생하더라도 재시도를 할 수 있는 해결 방법이다.
- 업데이트에 관해 다룰 때
Analyzer & Tokenizer 사용
Analyzer가 text field 검색 방법을 제어하는데 어떻게 도움이 되는지 알아보자.
이제 텍스트 정보를 포함하는 field가 있을 때마다 다음의 의사 결정이 필요하다.
- 정확히 매치
- index mapping을 정의할 때,
keyword
type을 사용한다. keyword
를 사용하면 text field에 대한 분석이 완전히 금지되며, 정확한 매치 항목만 허용한다.- 검색 결과가 정확히 일치하고 대소문자를 구분하며 모든 항목 일치하는 결과만 반환한다.
- index mapping을 정의할 때,
- 부분 일치
- 부분 일치 항목 중 관련도에 따라 순위가 매겨졌다는 것이다.
- 이를 수행하기 위해서는
text type match
를 선택해야 한다. - 이는
analyzer
라고 부르는 것을 field에서 실행한다. - 여러 개의 검색어와 모두 일치할 필요는 없다. 검색어 중 하나와 텍스트 필드가 일치하면 검색 결과 후보가 된다. 단지 관련성 점수가 낮을 뿐이다.
- example
- 기존에 생성한 index의 mapping은 다시 변경할 수 없기 때문에 새로운 mapping을 생성해줘야한다.
curl -XPUT 127.0.0.1:9200/movies -d '{ > "mappings": { > "properties": { > "id": {"type": "integer"}, > "year": {"type": "date"}, > "genre": {"type": "keyword"}, > "title": {"type": "text", "analyzer": "english"} > } > } > }'
- 기존에 생성한 index의 mapping은 다시 변경할 수 없기 때문에 새로운 mapping을 생성해줘야한다.
- 예시의
genre
처럼 type에keyword
를 입력하면 완전히 일치해야만 검색 결과로 노출이 되며,title
과 같이text
로 입력했을 경우에는 부분 일치를 허용하는 것이다. 또한analyzer
의 유형도 선택하여 지정할 수 있다.
Flattened Data Type
내부 field가 많은 Document를 처리해야 하는 경우 Elasticsearch 성능이 저하되기 시작할 수 있다. 이는 각 하위 field가 기본적으로 동적 mapping을 통해 개별 field에 mapping되기 때문이다. 'mapping explosions'에 도달하지 않도록 elasticsearch는 모든 하위 field를 개별 field로 mapping하지 않고 원본 데이터를 포함해 mapping할 수 있는 플랫 데이터 유형을 제공한다.
default mapping에 대해는 다음의 커맨드로 확인해볼 수 있다.
curl -XGET "http://127.0.0.1:9200/_cluster/state?pretty=true" >> es-cluster-state.json
- 생성한 파일 내부에서
indices
를 검색해보면 생성한 index에 대한 메타 데이터를 확인할 수 있으며, mapping에 대한 정보도 확인할 수 있다.
- 생성한 파일 내부에서
보다 자세하게 이를 이해해보자.
Updating Cluster State
클러스터 상태를 수신하면 각 노드는 마스터 노드에 수신 완료 신호인 Acknowledgement를 다시 보낸다. Document에 추가된 새로운 field에 대해서 Elasticsearch에 새 mapping이 생성된다. 새로운 mapping에 대한 클러스터 상태도 변경된다. 각 클러스터 상태가 변경된 후, 다른 노드를 동기화해야한다.
Mapping Explosions
index에 field를 자주 추가하면 클러스터의 상태가 커질 뿐만 아니라 모든 노드에서 클러스터 상태 업데이트가 발생하기 때문에 성능 저하의 원인이 될 수 있으며 클러스터 자체가 작동이 중지될 수 있다. 만약 노드에서 클러스터 상태가 업데이트 되지 않는다면 색인화 및 검색과 같은 기본적인 작업조차 수행할 수 없다. 이렇듯 mapping에 field가 너무 많아 클러스터가 충돌하는 현상을 'mapping explosions'라고 한다.
Flattend Data Type
이 현상을 방지하기 위해서 Elasticsearch에서는 flattened data type
을 도입했다. 기본적으로 이 data type은 전체 개체와 내부 필드를 단일 필드로 매핑한다. 즉, 내부 field가 포함된 경우 flat data type은 상위 field를 flat이라는 단일 유형으로 mapping하고 내부 field는 mapping에 표시하지 않음으로 전체 map field가 줄어드는 것이다.
Usage
실제로는 다음과 같이 간단하게 적용할 수 있다.
curl -XPUT "http://127.0.0.1:9200/demo-flattened/_mapping" -d'{
"properties": {
"host": {
"type": "flattened"
}
}
}'
demo-flattended
라는 인덱스의 mapping의 properties중 host field의 type을 flattened으로 지정해주었다. 결과는 다음과 같이 조회된다.
"host" : {
"type" : "flattened"
}
이후 host field에 내부 field를 추가하더라도 mapping에서 확인했을 때는 똑같이 flattened type으로 표시되기 때문에 변화가 없다. 실제로 document를 검색하면 업데이트 사항이 적용되는 것을 확인할 수 있다.
주의사항
Flattend type을 사용할 때 주의사항을 살펴보자.
- Flattend Data Type Object의 Field가 Elasticsearch에서 keyword로 처리된다.
- 이는 Analyzer, Tokenizer가 적용되지 않는다는 말이다.
- 따라서 검색 기능이 제한된다.
- Elasticsearch의 결과 강조 표시 기능이 해당 field에 활성화되지 않는다.
이런 점을 주의해야함에도 불구하고 Mapping Explosions을 방지하기 위한 효과적인 옵션임은 분명하다.
Mapping 예외 처리
mapping은 index의 필수적은 기초로써 Elasticsearch의 핵심으로 볼 수 있다. 따라서 mapping을 제대로 관리하는 것은 매우 중요하지만 mapping에서 문제가 발생할 수 있다. 따라서 mapping에서 발생할 수 있는 다양한 문제와 대처 방법을 살펴보자.
mappings
이에 대해 살펴보기 전에 mapping의 주요 사항을 간단히 살펴보고 가려고 한다. mapping은 Process, Result 두 부분으로 이루어져 있다.
- Process
- Json 문서가 index에 저장되는 방법을 정의한다.
- Explicit Mapping (명시적 매핑)
- 추가 변수와 함께 저장할 field 및 field type을 정의
- Dynamic Mapping (동적 매핑)
- Elasticsearch가 자동으로 적절한 data type을 확인하고 그에 따라 매핑을 업데이트
- Result
- 프로세스 과정에서 발생하는 메타데이터의 구조이다.
문제와 해결
그렇다면 어디서 문제가 발생할 수 있을까?
첫 번째로, Explicit Mapping에서 field가 일치하지 않을 때 특정 safety zone
밖에서 불일치가 발생하면 예외가 발생한다. 다음의 예제를 살펴보자.
curl --request PUT 'http://localhost:9200/microservice-logs' --data-raw '{
"mappings": {
"properties": {
"timestamp": {
"type": "date"},
"service": {"type": "keyword"},
"host_ip": {"type": "ip"},
"port": {"type": "integer"},
"message": {"type": "text"}
}
}
}'
위의 예제에 따르면 port 번호는 integer 타입으로 지정했다. 이제 integer가 아닌 전혀 다른 type의 데이터를 넣으려고 하면 다음과 같이 표시된다.
student@es7:~$ curl --request POST 'http://localhost:9200/microservice-logs/_doc?pretty' --data-raw '{"timestamp": "2020-04-11T12:34:56.789Z", "service": "XYZ", "host_ip": "10.0.2.15", "port": "NONE", "message": "I am not well!"}'
{
"error" : {
"root_cause" : [
{
"type" : "mapper_parsing_exception",
"reason" : "failed to parse field [port] of type [integer] in document with id 'DuRZn4QBW6XjXGdaOiA3'. Preview of field's value: 'NONE'"
}
],
"type" : "mapper_parsing_exception",
"reason" : "failed to parse field [port] of type [integer] in document with id 'DuRZn4QBW6XjXGdaOiA3'. Preview of field's value: 'NONE'",
"caused_by" : {
"type" : "number_format_exception",
"reason" : "For input string: \"NONE\""
}
},
"status" : 400
}
port 번호에 NONE
을 입력하니 type과 관련한 에러가 발생하는 것을 확인할 수 있다.
이 문제는 한번에 해결할 수 있는 방법은 없다. 이런 상황에서는 ignore_malformed
매핑 변수를 정의하여 이 문제를 부분적으로 해결할 수 있다. 이 변수는 비동적이기 때문에 index를 생성할 때 설정하거나 index를 닫고 설정값을 변경한 후 다시 열어주어야 한다.
curl --request POST 'http://localhost:9200/microservice-logs/_close'
curl --location --request PUT 'http://localhost:9200/microservice-logs/_settings' --data-raw '{"index.mapping.ignore_malformed": true}'
curl --request POST 'http://localhost:9200/microservice-logs/_open'
이제 위에서 에러를 발생시켰던 커맨드를 다시 입력해보면 에러 발생 없이 저장됨을 확인할 수 있다. 하지만 port 지정한 타입 외의 데이터가 저장되었기에 부분적인 해결 방법이다. 또한ignore_malformed
는 JSON type의 입력을 처리할 수 없기 때문에 입력이 JSON 형태로 들어온다면 이 또한 에러가 발생한다.
JSON을 저장할 수 있는 object
타입의 field가 바로 payload
이다.
curl --request POST 'http://localhost:9200/microservice-logs/_doc?pretty' --data-raw '{"timestamp": "2020-04-11T12:34:56.789Z", "service": "ABC", "host_ip": "10.0.2.15", "port": 12345, "message": "Received...", "payload": {"data": {"received": "here"}}}'
두 번째로, 기본 Dynamic Mapping을 유지하면서 더 많은 field를 추가하면 Mapping Explosions로 인해 클러스터가 중단될 수 있다.
매핑은 기본적으로 1000개까지 생성할 수 있도록 설정되어 있다. 이 상황을 만들어보기 위해서 jq
를 사용하여 임의적으로 매핑을 1001개를 생성해본다.
sudo apt-get install jq
thousandone_field_json=$(echo {1..1001..1} | jq -Rn '( input | split(" ") ) as $nums | $nums[] | . as $key | [{key:($key|tostring),value:($key|tonumber)}] | from_entries' | jq -cs 'add')
이제 index를 생성한 후 index에 데이터를 import 해보자.curl --location --request PUT 'http://localhost:9200/big-objects'
curl --request POST 'http://localhost:9200/big-objects/_doc?pretty' --data-raw "$thousandone_field_json"
{
"error" : {
"root_cause" : [
{
"type" : "mapper_parsing_exception",
"reason" : "failed to parse"
}
],
"type" : "mapper_parsing_exception",
"reason" : "failed to parse",
"caused_by" : {
"type" : "illegal_argument_exception",
"reason" : "Limit of total fields [1000] has been exceeded while adding new fields [1001]"
}
},
"status" : 400
}
의도한대로 illegal_argument_exception
이 발생했다.
해결 방법으로는 필요시에는 1000개에서 제한 사항을 여유롭게 늘릴 수 있지만 이 경우 더 복잡해지고 잠재적인 성능 저하와 메모리 부담이 발생할 수 있음에 주의해야한다. 설정은 다음의 명령으로 변경할 수 있다.
curl --location --request PUT 'http://localhost:9200/big-objects/_settings' --data-raw '{"index.mapping.total_fields.limit": 1001}'
이후 다시 import 하면 정상적으로 document가 생성됨을 확인할 수 있다.
'Data Engineering > Elasticsearch' 카테고리의 다른 글
[Elasticsearch] Script 혹은 Library를 사용하여 데이터 import (0) | 2022.11.23 |
---|---|
[Elasticsearch] Section 3. Elasticsearch를 사용한 검색 (0) | 2022.11.23 |
[Elasticsearch] Data 매핑 및 색인화 - 1 (0) | 2022.11.22 |
Elasticsearch 기본 (0) | 2022.11.15 |
Elasticsearch 개요 (0) | 2022.11.15 |
- Total
- Today
- Yesterday
- Hadoop
- kubernetes
- Algorithm
- Elasticsearch
- HDFS
- CSAPP
- 프로그래머스
- Flutter
- cka
- kafka
- 빅데이터
- mahout
- heapq
- DP
- elasticsaerch
- OS
- Espher
- CS
- DFS
- GROK
- 파이썬
- 네트워크
- 백준
- Python
- 빅데이터를지탱하는기술
- sqoop
- oozie
- BOJ
- 이코테
- logstash
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |