엔지니어링

Elasticsearch 메모리 관리 및 문제 해결

안녕하세요! Elastic의 Elasticsearch Service Cloud 제품 확장 및 자동 온보딩을 통해, Elastic은 전체 운영 팀에서 데이터 엔지니어, 보안 팀 및 컨설턴트로 Elastic Stack 대상을 확대했습니다. Elastic 지원 엔지니어로서, 저는 더 다양한 배경의 사용자들과 훨씬 더 광범위한 사용 사례로 소통하는 것을 즐기고 있습니다. 

더 다양한 대상과 소통함에 따라, 저는 리소스 할당 관리, 특히 신비로운 샤드-힙 비율과 회로 차단기를 피하는 것에 대한 더 많은 질문을 접하고 있습니다. 이러한 질문들에 대해 저는 잘 알고 있습니다. Elastic Stack을 시작했을 때도 동일한 질문이 있었습니다. 제가 처음 했던 작업은 Java 힙 및 시계열 데이터베이스 샤드를 관리하고 자체 인프라를 확장하는 것이었습니다.

Elastic 팀에 합류했을 때, 저는 설명서 외에도 블로그와 사용지침서가 있어 빠르게 온보딩할 수 있다는 점이 좋았습니다. 하지만 그리고 나서 처음 한 달 동안 저는 제 이론적 지식을 사용자들이 제 티켓 대기열을 통해 보내는 오류와 연관시키려고 애썼습니다. 결국, 다른 지원 엔지니어와 마찬가지로, 보고된 오류의 대부분은 할당 문제의 증상일 뿐이며, 동일한 7개 정도의 링크를 사용하면 사용자가 리소스 할당을 성공적으로 관리하는 속도를 높일 수 있다는 사실을 알게 되었습니다.

지원 엔지니어로서, 다음 섹션에서는 우리가 사용자에게 보내는 할당 관리 이론의 상위 링크, 표시되는 주요 증상 및 리소스 할당 문제를 해결하기 위해 구성을 업데이트하도록 사용자를 안내해야 하는 위치에 대해 살펴보겠습니다.

이론

Java 애플리케이션의 경우, Elasticsearch를 사용하려면 시스템의 실제 메모리에서 일부 논리 메모리(힙)를 할당해야 합니다. 이 용량은 물리적 RAM의 절반까지가 최대여야 하며, 32GB로 제한되어야 합니다. 힙 사용량을 더 높게 설정하는 것은 일반적으로 값비싼 쿼리와 더 큰 데이터 저장 공간에 대한 응답입니다. 상위 회로 차단기의 기본값은 95%이지만, 리소스를 한 번 일정하게 85%까지 확장하는 것이 좋습니다. 

자세한 내용은 Elastic 팀이 작성한 다음 개요 글을 참조하실 것을 적극 권장합니다.

구성

기본으로 제공되는 Elasticsearch의 기본 설정은 노드 역할 및 총 메모리를 기준으로 JVM 힙 크기를 자동으로 조정합니다. 그러나 필요에 따라 다음 세 가지 방법으로 직접 구성할 수 있습니다.

1. 로컬 Elasticsearch 파일의 config > jvm.options 파일에서 직접

## JVM 구성
################################################################
## 중요: JVM 힙 크기
################################################################

#Xms는 총 힙 공간의 초기 크기를 나타냅니다
#Xmx는 총 힙 공간의 최대 크기를 나타냅니다
-Xms4g
-Xmx4g

2. docker-compose에서 Elasticsearch 환경 변수로

version: '2.2'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
environment:
- node.name=es01
- cluster.name=es
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.type=single-node
ulimits:
memlock:
soft: -1
hard: -1
ports:
- 9200:9200

3. Elasticsearch Service > 배포 > 편집 보기를 통해 참고: 슬라이더는 실제 메모리를 할당하고 약 절반은 힙에 할당됩니다.

blog-elasticsearch-memory.png

문제 해결

클러스터에서 현재 성능 문제가 발생하는 경우, 일반적으로 생각하는 바로 그것이 원인일 가능성이 높습니다

  • 구성 문제: 오버샤딩, ILM 정책 없음
  • 유발된 볼륨: 높은 요청 속도/부하, 중복되는 값비싼 쿼리/쓰기

아래의 cURL/API 요청은 모두 Elasticsearch Service > API 콘솔에서 Elasticsearch API의 cURL로 또는 Kibana > 개발 도구에서 이루어질 수 있습니다.

오버샤딩

데이터 인덱스는 유지 관리를 위해 그리고 검색/쓰기 요청 중에 힙을 사용하는 하위 하드에 저장됩니다. 샤드 크기는 50GB로 제한되어야 하며 숫자의 상한은 다음 수식을 통해 결정되어야 합니다.

shards = sum(nodes.max_heap) * 20

두 영역(총 2개의 노드가 할당됨)에 걸쳐 8GB의 실제 메모리가 있는 위의 Elasticsearch Service 예를 참조하세요.

#node.max_heap
8GB of physical memory / 2 = 4GB of heap
#sum(nodes.max_heap)
4GB of heap * 2 nodes = 8GB
#max shards
8GB * 20
160

그런 다음 이를 _cat/allocation와 교차 비교하거나

GET /kr/_cat/allocation?v=true&h=shards,node
shards node
41 instance-0000000001
41 instance-0000000000

또는 _cluster/health와 교차 비교합니다

GET /kr/_cluster/health?filter_path=status,*_shards
{
"status": "green",
"unassigned_shards": 0,
"initializing_shards": 0,
"active_primary_shards": 41,
"relocating_shards": 0,
"active_shards": 82,
"delayed_unassigned_shards": 0
}

따라서 이 배포에는 최대 160개 추천의 82개 샤드가 있습니다. 개수가 추천보다 높으면 다음 두 섹션에서 증상이 나타날 수 있습니다(아래 참조).

샤드가 active_shards 또는 active_primary_shards 외부에서 >0을 보고하는 경우, 성능 문제의 주요 구성 원인을 파악한 것입니다.

가장 일반적으로 이것이 문제를 보고하면, unassigned_shards>0이 됩니다. 이러한 샤드가 기본인 경우, 클러스터는 status:red로 보고하고 복제본만 있는 경우 status:yellow로 보고합니다. (클러스터에 문제가 발생하더라도 데이터 손실이 발생하지 않고 복구할 수 있도록 인덱스에 복제본을 설정하는 것이 중요한 이유입니다.)

status:yellow에 할당되지 않은 단일 샤드가 있는 상태를 가정해 보겠습니다. 조사하기 위해, _cat/shards를 통해 어떤 인덱스 샤드에 문제가 있는지 살펴봅니다

GET _cat/shards?v=true&s=state
index shard prirep state docs store ip node
logs 0 p STARTED 2 10.1kb 10.42.255.40 instance-0000000001
logs 0 r UNASSIGNED
kibana_sample_data_logs 0 p STARTED 14074 10.6mb 10.42.255.40 instance-0000000001
.kibana_1 0 p STARTED 2261 3.8mb 10.42.255.40 instance-0000000001

이는 할당되지 않은 복제본 샤드가 있는 비시스템 인덱스 로그에 대한 것입니다. _cluster/allocation/explain을 실행하여 무엇이 문제를 야기하고 있는지 살펴보겠습니다(프로 팁: 여러분이 지원팀으로 에스컬레이션하면, Elastic이 정확히 이런 작업을 합니다)

GET _cluster/allocation/explain?pretty&filter_path=index,node_allocation_decisions.node_name,node_allocation_decisions.deciders.*
{ "index": "logs",
"node_allocation_decisions": [{
"node_name": "instance-0000000005",
"deciders": [{
"decider": "data_tier",
"decision": "NO",
"explanation": "node does not match any index setting [index.routing.allocation.include._tier] tier filters [data_hot]"
}]}]}

이 오류 메시지는 인덱스 수명 주기 관리(ILM) 정책의 일부인 data_hot을 가리키며, ILM 정책이 현재 인덱스 설정과 일치하지 않음을 나타냅니다. 이 경우, 이 오류의 원인은 지정된 hot-warm 노드 없이 hot-warm ILM 정책을 설정했기 때문입니다. (무언가가 실패하리라는 것을 보장해야 했기 때문에, 여러분 모두를 위해 제가 강제적으로 발생하도록 한 오류 사례입니다. 오류를 만들 수 밖에 없었죠 😂)

참고로, 할당되지 않은 샤드가 없을 때 이 명령을 실행하면, 보고할 문제가 없기 때문에 설명할 할당되지 않은 샤드를 찾을 수 없다는 오류 400이 표시됩니다.

논리적이지 않은 원인(예: 할당 중 노드 왼쪽 클러스터와 같은 일시적인 네트워크 오류)이 발생하면, Elastic의 편리한 _cluster/reroute를 사용할 수 있습니다.

POST /kr/_cluster/reroute

사용자 정의가 없는 이 요청은 모든 state:UNASSIGNED 샤드를 할당하려고 시도하는 비동기 백그라운드 프로세스를 시작합니다. (저처럼 하지는 마세요. 완료될 때까지 기다리지 마시고 개발팀에 연락하세요. 왜냐하면 저는 오류가 순간적으로 일어나게 되며 우연히 제시간에 에스컬레이션하게 될 것이기 때문에 그 시간이 지나면 더 이상 아무 오류도 없을 것이므로 개발팀에서는 아무 문제도 없다고 말하게 될 것이라고 생각했기 때문입니다.)

회로 차단기

힙 할당을 최대화하면 클러스터에 대한 요청이 시간 초과되거나 오류가 발생할 수 있으며 클러스터에 회로 차단기 예외가 발생하는 경우가 많습니다. 회로 차단으로 인해 다음과 같은 elasticsearch.log 이벤트가 발생합니다

Caused by: org.elasticsearch.common.breaker.CircuitBreakingException: [parent] Data too large, data for [] would be [num/numGB], which is larger than the limit of [num/numGB], usages [request=0/0b, fielddata=num/numKB, in_flight_requests=num/numGB, accounting=num/numGB]

조사하려면, _cat/nodes를 보고 heap.percent를 확인합니다

GET /kr/_cat/nodes?v=true&h=name,node*,heap*
#힙 = JVM(힙을 위해 예약된 논리 메모리)
#RAM = 실제 메모리
name node.role heap.current heap.percent heap.max
tiebreaker-0000000002 mv 119.8mb 23 508mb
instance-0000000001 himrst 1.8gb 48 3.9gb
instance-0000000000 himrst 2.8gb 73 3.9gb

또는 이전에 활성화한 경우, Kibana > 스택 모니터링으로 이동합니다.

blog-elasticsearch-memory-2.png

메모리 회로 차단기에 부딪혔다는 것을 확인한 경우, 조사하기 위해 무엇을 할지 결정할 기회를 확보하고자 일시적으로 힙을 늘리는 것을 고려해야 할 것입니다. 근본 원인을 조사할 때는 클러스터 프록시 로그 또는 elasticsearch.log에서 이전의 연속된 이벤트를 확인합니다. 다음과 같이

  • 특히 값비싼 쿼리를 찾아보세요.
    • 높은 버킷 집계
      •  검색이 검색 크기나 버킷 크기를 기준으로 쿼리를 실행하기 전에 힙의 특정 포트를 일시적으로 할당한다는 사실을 알았을 때 너무 어리석다고 느껴졌습니다. 그래서 10,000,000을 설정하는 것은 정말로 제 운영 팀의 속이 쓰리게 만드는 것이었습니다.
    • 최적화되지 않은 매핑
      • 어리석다고 느끼는 두 번째 이유는 계층적 보고를 하는 것이 평면화된 데이터보다 더 나은 검색이라고 생각했을 때였습니다(사실은 그렇지 않습니다).
  • 요청 볼륨/속도: 일반적으로 배치 또는 비동기식 쿼리

확장을 위한 시간

회로 차단기를 처음 사용하는 것이 아니거나 지속적인 문제가 될 것으로 예상되는 경우(예: 일정하게 85%에 이르는 경우, 확장 리소스를 살펴볼 때), JVM 메모리 부담을 장기적인 힙 표시기로 자세히 살펴보아야 합니다. Elasticsearch Service > 배포에서 이를 확인하실 수 있습니다

blog-elasticsearch-memory-3.png

또는 _nodes/stats에서 이를 계산하실 수 있습니다

GET /kr/_nodes/stats?filter_path=nodes.*.jvm.mem.pools.old
{"nodes": { "node_id": { "jvm": { "mem": { "pools": { "old": {
"max_in_bytes": 532676608,
"peak_max_in_bytes": 532676608,
"peak_used_in_bytes": 104465408,
"used_in_bytes": 104465408
}}}}}}}

위치는 다음과 같습니다

JVM Memory Pressure = used_in_bytes / max_in_bytes

이로 인해 발생할 수 있는 증상은 elasticsearch.log의 가비지 컬렉터(gc) 이벤트에서 발생하는 빈도가 높고 지속 시간이 길다는 것입니다

[timestamp_short_interval_from_last][INFO ][o.e.m.j.JvmGcMonitorService] [node_id] [gc][number] overhead, spent [21s] collecting in the last [40s]

이 시나리오를 확인하는 경우, 클러스터를 확장하거나 클러스터에 미치는 수요를 줄여야 합니다. 조사/고려해야 할 사항은 다음과 같습니다.

  • 힙 리소스 증가(힙/노드, 노드 수)
  • 샤드 감소(불필요한/오래된 데이터 삭제, ILM을 사용해 데이터를 warm/cold 저장 공간에 저장하여 축소 가능, 손실되어도 상관없는 데이터에 대한 복제본 끄기)

결론

이제 다 보셨군요! Elastic 지원에서 확인한 바로는, 할당되지 않은 샤드, 불안정한 샤드-힙, 회로 차단기, 높은 가비지 수집 및 할당 오류 등과 같이 가장 일반적인 사용자 티켓들을 요약한 것입니다. 모두 핵심 리소스 할당 관리 대화의 증상입니다. 이제 이론과 해결 단계도 알게 되셨기를 바랍니다.

하지만 이 시점에서 문제를 해결하는 데 어려움을 겪고 계시다면, 언제든지 연락하시기 바랍니다. Elastic이 기꺼이 도와드리겠습니다! Elastic토론 게시판, Elastic 커뮤니티 Slack, 컨설팅, 교육 및 지원을 통해 연락하실 수 있습니다.

Elastic Stack의 리소스 할당을 노옵스(non-Ops)로(물론 Ops로도) 자체 관리할 수 있는 능력에 건배!