ElasticSearch
오늘은 ElasticSearch(이후 ES) 운영 중에 맞닥뜨릴 수 있는 가장 흔한 주제 중에 하나인 OutOfMemory(이하 OOM)에 대해서 이야기해보려고 합니다. OOM의 전조 증상과 그에 따른 나름대로의 대비책에 대해 살펴보겠습니다.
Java 기반의 애플리케이션들은 힙 메모리라는 것을 필요로 합니다. 이 영역에 연산에 필요한 다양한 데이터들을 저장하게 됩니다. 힙 메모리는 Java 애플리케이션을 실행할 때 파라미터를 통해서 설정할 수 있으며 처음 설정한 용량이 고정되어 이 영역 안에서만 사용하게 됩니다. 그래서 특정 시점이 되면 GarbageCollector(이하 GC)를 이용해서 사용하지 않는 메모리 영역의 데이터들을 삭제하고 다시 데이터를 담을 수 있게 용량을 확보하는 일을 주기적으로 합니다. 그리고 이 과정에서 어떤 방식의 GC를 사용할 것인가가 Java 기반의 애플리케이션의 성능 튜닝의 주요 지점이 되기도 합니다.
그런데 GC를 이용해서 비우려고 해도 영역이 계속 커지기만 하고 결국에 설정해 놓은 힙 메모리 영역을 모두 사용하게 되면 Java 애플리케이션은 OutOfMemory라는 에러를 뱉어내고 죽어 버리게 됩니다.
java.lang.OutOfMemoryError: Java heap space
ES 역시 Java 애플리케이션 이기 때문에 OOM을 피해갈 수 없습니다. 사용 패턴에 적합한 힙 메모리를 적절하게 설정하는 것이 필수이며, 그래서 힙 메모리 관련된 논의도 활발합니다. (참고 자료 : https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html)
그럼 OOM, 막을 수는 없을까요? OOM이 일어날 때의 순간을 한 번 살펴보겠습니다.
우선 정상적인 상태의 힙 메모리 사용 패턴을 살펴보겠습니다.
하지만 OOM이 발생하는 순간의 힙 메모리 사용 패턴을 보면 아래와 같습니다.
뭔가 다른 게 확 느껴지시나요?
정상적인 패턴의 경우 특정 순간 (약 75%~85% 사이)에서 GC가 일어나서 힙 메모리의 사용량이 확 내려갑니다.
(참고 자료 : https://www.elastic.co/blog/found-understanding-memory-pressure-indicator) 하지만 OOM이 발생하는 순간을 살펴보면 GC가 발생해도 메모리의 회수가 일어나지 않고 계속해서 사용률이 증가한다는 것을 알 수 있습니다. 네. 그렇습니다. 이를 통해서 OOM이 발생하기 전의 전조 증상을 알 수 있습니다.
85% 이상의 힙 메모리 사용률을 보이기 시작한다면 OOM 이 발생할 확률이 급격히 늘어난다는 것이죠.
앞에서 살펴봤듯이 힙 메모리 사용률을 모니터링하다가 특정 수치 이상이 되면 OOM이 발생할 가능성이 급격하게 커진다는 것을 알 수 있습니다. 전조 증상을 보았으니 막을 수는 없을까요? 그냥 앗~ 하면서 당해야 할까요? 어떻게 하면 막을 수 있을까를 고민하다가 ES의 Task Management API를 발견했습니다.
(참고 자료 : https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html)
저희가 운영하는 클러스터에서 가장 많이 OOM이 발생한 경우는 아주 넓은 기간으로 Kibana에서 데이터를 확인하거나 많은 양의 그래프가 있는 대시보드를 열어보려고 할 때였습니다. 즉, 대규모의 어그리게이션이 발생할 때 갑작스럽게 힙 메모리가 풀이 나서 OOM이 발생하는 경우가 있었습니다. 그래서 Task Management API를 통해 힙 메모리가 특정 수치 이상이 되면 Task들을 스캔한 후 검색 관련된 요청들을 모두 cancel 시켰습니다. (참고 자료 : https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#task-cancellation) 너무 무거운 쿼리에 의해서 노드들이 OOM이 발생해 클러스터가 다운되는 것보다는 차라리 요청을 모두 취소시켜서 클러스터의 안정성을 확보하는 것이 더 중요했기 때문입니다. 그러고 나서 쿼리를 분석해서 노드들의 메모리를 증설하거나 힙 메모리를 늘리거나 하는 등의 후속 조치를 할 수 있는 시간을 벌 수 있었습니다.
종합해 보자면 아래와 같은 룰로 동작하는 자동화 시스템을 만들었습니다.
1. 힙 메모리의 사용량이 90% 이상
2. Task Management API를 통해서 현재 동작 중인 Task 스캐닝
3. 스캐닝된 Task들 중에 검색 관련된 Task들을 cancel
기준을 90%로 설정한 이유는 정상적인 경우에도 85%를 살짝 넘는 경우가 왕왕 있어서입니다.
ES 클러스터를 운영하다 보면 다양한 이슈들을 경험하게 되는데요, 갑작스럽게 찾아오는 OOM도 그중 한 가지입니다. 개인적으로는 ES 운영을 가장 힘들게 만드는 이슈가 아닌가 싶습니다. 저장 공간의 부족 같은 문제는 모니터링을 잘 하면 어느 정도 패턴을 읽어낼 수 있기 때문에 대비가 가능하지만 불청객처럼 찾아오는 OOM은 정말 예측할 수 없는 시간에 예측할 수 없는 방식으로 찾아오기 때문입니다.
물론 이 글에서 언급한 방법이 최선을 아닙니다. OOM을 일으키는 원인 중에는 무거운 쿼리만 있는 게 아니라 실질적으로 힙 메모리가 부족한 경우도 있기 때문입니다. 하지만 문제를 인식하고 그것을 해결하기 위해 고민하고 있다는 것이 중요한 게 아닐까요? 여러분은 ES에서 발생하는 OOM을 어떻게 처리하고 계신가요? 함께 이야기를 나눠 보고 싶습니다.