brunch

You can make anything
by writing

C.S.Lewis

by 강진우 Sep 02. 2017

rejected exception의 의미와 조치 방법

ElasticSearch 운영

일정 규모 이상의 ElasticSearch (이하 ES) 클러스터를 운영하다 보면 여러 가지 이슈를 만나게 됩니다. 그중에서도 애먹이게 하는 이슈 중에 하나가 rejected exception의 발생이 아닐까 합니다. 이번 글에서는 rejected exception이 무엇인지 그리고 어떻게 모니터링할 수 있으며 조치 방법에 대해서 이야기해 보려고 합니다.


rejected exception 이란


ES에는 Thread Pool이라는 것이 존재합니다. (참고 자료 : https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-threadpool.html ) Thread Pool은 ES의 주요 기능들 (bulk, indexing, search 등등)을 Thread Pool의 형태로 생성하여 관리하는 것을 의미합니다. 만약 bulk API를 통해 다량의 작업을 하게 되면 bulk Thread Pool에 생성되어 있는 다수의 bulk thread들을 통해서 요청을 처리하도록 하고 있습니다. 그런데 더 이상 인입되는 요청을 처리할 수 있는 thread가 없을 경우에는 어떻게 될까요? 우선은 각각의 Thread Pool 별로 가지고 있는 queue에 요청을 넣게 되지만 그마저도 부족하게 될 경우에는 요청을 거부하게 되고 이때 rejected exception이  발생하게 됩니다. 그리고 데이터를 인입시키는 쪽에서는 아래와 같은 에러 메시지를 만나게 됩니다.

[2017-06-13T10:31:48,594][INFO ][logstash.outputs.elasticsearch] retrying failed action with response code: 429 ({"type"=>"es_rejected_execution_exception", "reason"=>"rejected execution of org.elasticsearch.transport.TransportService$7@7b95e06b on EsThreadPoolExecutor[bulk, queue capacity = 50, org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor@481647a0[Running, pool size = 32, active threads = 32, queued tasks = 50, completed tasks = 2728908864]]"})

rejected exception은 ES의 처리량이 최대치를 넘었다는 것을 보여주는 일종의 성능 지표 중에 하나입니다. 이 메시지가 발생하게 되면 ES의 현재 처리량을 넘어서는 데이터의 인입 혹은 검색 쿼리의 유입 등이 발생하고 있다고 볼 수 있습니다. rejected exception을 잘 처리하는 클라이언트들의 경우에는 대부분 재시도를 통해서 데이터의 유실을 막을 수 있지만 그렇지 않은 경우에는 데이터의 유실이 발생할 수도 있습니다. 그렇다면 rejected exception 현상은 어떻게 모니터링할 수 있을까요?


rejected exception 모니터링 하기


ES 클러스터의 로그에는 rejected exception 관련된 메시지가 남지 않기 때문에 클러스터 자체의 로그 모니터링으로는 현재 클러스터에 rejected exception이 발생했는지를 확인할 수 없습니다. 그렇다면 클러스터를 운영하는 입장에서 rejected exception을 어떻게 모니터링할 수 있을까요? 방법은 두 가지가 있습니다.

첫 번째는 cat API를 사용하는 것입니다. cat API 중에 Thread Pool을 모니터링할 때 사용되는 /_cat/thread_pool API를 사용하면 됩니다. (참고 자료 : https://www.elastic.co/guide/en/elasticsearch/reference/master/cat-thread-pool.html) 출력 결과 중에 제일 우측에 위치한 값이 rejectd 값입니다.

/_cat/thread_pool 예제

cat API에는 format이라는 매개변수가 있어서 출력 값을 json 형태로도 볼 수 있습니다. 아마도 자동화 스크립트 같은 것을 사용한다면 json 형태가 더 나을 수도 있습니다.

cat API를 json 형태로 출력하기

두 번째로는 Nodes Stats API를 통해서도 확인할 수 있습니다. (참고 자료 : https://www.elastic.co/guide/en/elasticsearch/reference/master/cluster-nodes-stats.html

Nodes Stats API 결과 중 thread pool 관련된 부분

이렇게 ES는 API를 통해서 현재의 Thread Pool 상태를 확인할 수 있는 인터페이스를 제공해 주고 있습니다. 한 가지 주의해야 할 점은 rejected 값의 경우는 현재 발생한 개수가 아니라 지금까지 발생한 값들의 누적치를 보여준다는 것입니다. 

그렇기 때문에 rejected 값이 이전 호출했을 때와 비교해서 늘어났는지의 여부를 가지고 판단해야 합니다.

rejected exception에 대한 조치 사항


이전까지의 절을 통해서 rejected exception 발생 여부에 대해서 모니터링하는 방법을 알아봤습니다. 그렇다면 실제로 rejected exception이 발생했다면 어떻게 조치를 해야 할까요? 

사실 가장 좋은 조치 방법은 노드 증설입니다. 

앞에서 이야기했던 것처럼 rejected exception은 현재 ES 클러스터가 최대 처리 가능한 요청보다 많은 양의 요청을 처리하고 있다는 지표이기 때문입니다. 하지만 노드의 증설이 바로 여의치가 않을 경우 rejected exception이 발생한 thread에 대해서 queue_size 값을 조절하여 조치할 수 있습니다.

아래는 rejected exception이 다수 발생했던 클러스터의 성능 데이터입니다.

bulk rejected가 발생하는 상황
위 시스템은 자체적으로 만든 지표 수집기와 그라파나를 통해서 만든 ES 모니터링 시스템입니다. 내년 초 정도면.. github를 통해 공개할 수 있지 않을까요?? ^^;;

위 그래프를 보면 평균 100만 개 정도의 문서가 인입되고 있으며 각각의 노드에서 발생하는 bulk rejected exception양도 거의 천 단위로 발생하고 있습니다. 이 정도 수준의 bulk rejected exception이면 사실상 로그가 유실되는 수준까지도 갈 수 있다고 봐야 합니다. 하지만 문서가 인입되는 지표를 보면 순간적으로 많은 양의 문서가 인입되었다가 내려갔다가 하는 형태의 워크로드를 보이고 있는 것을 알 수 있습니다. 즉 순간적으로 몰리는 양만 처리가 된다면 bulk rejected exception에 의해 발생하는 문서의 유실을 막아낼 수 있지 않을까요? 그리고 이를 위해 bulk thread에 대한 queue_size를 늘려봤습니다. 

thread_pool.bulk.queue_size: 10000
모든 thread들의 queue_size 기본값은 200입니다.
queue_size 증가 후 상황

문서의 인입 패턴은 그대로이지만 거짓말처럼 bulk rejected exception이 사라진 것을 볼 수 있습니다. 이렇게 queue_size를 늘림으로써 순간적으로 많은 양의 요청이 인입되는 경우 발생하는 rejected exception을 예방하고 로그의 유실을 막을 수 있습니다.


queue_size 증가의 주의점


하지만 앞서 말한 것처럼 rejected exception의 근본적인 원인은 ES 클러스터의 처리량 부족입니다. queue_size 증가는 근본적인 문제를 해결한 것이라기보다는 일종의 응급처치라고 볼 수 있습니다. 특히 위에 예제로 들었던 클러스터의 경우는 배치 잡에 의해서 문서가 인입되기 때문에 배치 잡이 끝나면 문서의 인입이 줄어드는 타이밍이 있습니다. 그래서 순간적으로 문서가 많이 들어와도 잠시 queue에 쌓아 놓고 처리하면 되기 때문에 queue_size를 증가시키는 게 큰 효과를 보게 된 경우입니다. 그렇지 않고 계속해서 일정량의 문서가 인입되는 경우에는 queue_size를 증가시켜도 위 클러스터만큼의 큰 효과를 볼 수 없을 수도 있습니다. 

또한 queue에 쌓아 두고 처리하는 것을 클러스터의 처리량이 늘어난 것처럼 느낄 수 있기 때문에 클러스터의 처리 성능에 대한 왜곡이 발생할 수 있습니다. 실질적으로는 100개의 요청만큼만 처리가 가능한데, queue를 활용하게 되어 200개의 요청을 처리 가능한 걸로 생각할 수 있다는 의미입니다. 이렇게 되면 200개 이상의 요청이 들어왔을 때 더더욱 조치하기가 힘들어지며 이로 인해 더 큰 문제가 발생하게 됩니다.

그렇기 때문에 queue_size 증가는 노드를 증설할 여건이 안될 경우의 응급조치로 활용하고, 정확한 성능 테스트를 통해서 적합한 노드의 대수를 재산정해서 노드를 증설하는 형태로 진행해야 합니다.

마치며


이번 글에서는 rejected exception에 대해서 살펴봤습니다. rejected exception은 ES 클러스터의 처리 성능이 최대치에 도달했다는 것을 알려 주는 성능 지표입니다. 또한 queue_size 조절을 통해서 임시적인 방편이 가능합니다. 하지만 클러스터의 처리 성능이 최대치에 도달했기 때문에 정확한 성능 테스트를 통해서 적절한 노드 대수를 산정하고 클러스터의 노드 대수를 증설하는 것이 가장 효과적이고 바람직한 처리 방법이 될 것입니다.


오늘도 긴 글 읽어 주셔서 감사합니다.


ps. 아직도 많이 부족한 제가 책이 나왔습니다. ^^ 저와 같은 업무를 하고 계시는 SE분들 혹은 서버 운영을 하고 계시는 DevOps 분들에게 조금이라도 도움이 되었으면 하는 마음으로 썼습니다. 


http://www.yes24.com/24/goods/44376723?scode=032&OzSrank=3

많은 분들에게 도움이 될 수 있는 그런 책이었으면 좋겠습니다~

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari