어느 회사에서든 비슷하겠지만 내가 다녔던 회사는 운영 서버를 패치하는 날이면
모든 팀원들이 각각의 서비스들을 빌드하고 배포한 후에 운영에 이상이 없는지 전체적인 테스트를 거치게 된다.

(모든 개발자가 세상에서 제일 예민한 시간)
나도 마찬가지로 내가 담당하고 있는 서버를 빌드 배포한 후에 로그와 함께
예기치 못한 Exception 이 나지는 않는지, 새로 추가된 기능들이 정상적으로 동작하는지 등의 점검을 진행했다.
로그를 계속적으로 모니터링 하고 있는데, 사용자에게 표출되지 않는 내부 스케줄러 로직에서
에러가 계속적으로 나고 있는걸 확인할 수 있었고,
Elastic Search관련 로직에서 'HTTP/1.1 429 Too Many Requests'과 같은 에러 로그가 출력되고 있었다.
무슨 로그였을까?
에러 로그를 자세히 확인해보니 다음과 같은 문장들이 반복적으로 출력되고 있었다.
rejected execution of processing of
...
EsThreadPoolExecutor[name = elasticsearch-0/write, queue capacity = 200, org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor@b8667c4[Running, pool size = 2, active threads = 2, queued tasks = 203, completed tasks = 23834127]]
로그를 대충 훑어봐도 queue capacity (큐 용량) 이 200으로 설정되어 있는데,
queue tasks (큐로 되고자 하는 작업) 들이 203개 인걸 확인할 수 있었다.
즉, 허용할 수 있는 queue task 수를 넘어 task를 요청하고 있기에 나는 에러라는 것을 확인할 수 있었다.
queue task 점유 상황을 확인할 수 있는 방법 다음과 같다.
curl IP:PORT/_cat/thread_pool?v
node_name name active queue rejected
elasticsearch-0 analyze 0 0 0
elasticsearch-0 ccr 0 0 0
elasticsearch-0 fetch_shard_started 0 0 0
elasticsearch-0 fetch_shard_store 0 0 0
elasticsearch-0 flush 0 0 0
elasticsearch-0 force_merge 0 0 0
elasticsearch-0 generic 0 0 0
elasticsearch-0 get 0 0 0
elasticsearch-0 listener 0 0 0
elasticsearch-0 management 1 0 0
elasticsearch-0 refresh 0 0 0
elasticsearch-0 rollup_indexing 0 0 0
elasticsearch-0 search 0 0 0
elasticsearch-0 search_throttled 0 0 0
elasticsearch-0 security-token-key 0 0 0
elasticsearch-0 snapshot 0 0 0
elasticsearch-0 transform_indexing 0 0 0
elasticsearch-0 warmer 0 0 0
elasticsearch-0 watcher 0 0 0
elasticsearch-0 write 3 457 0
elasticsearch-1 analyze 0 0 0
elasticsearch-1 ccr 0 0 0
elasticsearch-1 fetch_shard_started 0 0 0
elasticsearch-1 fetch_shard_store 0 0 0
elasticsearch-1 flush 0 0 0
elasticsearch-1 force_merge 0 0 0
elasticsearch-1 generic 0 0 0
elasticsearch-1 get 0 0 0
elasticsearch-1 listener 0 0 0
elasticsearch-1 management 3 0 0
elasticsearch-1 refresh 0 0 0
elasticsearch-1 rollup_indexing 0 0 0
elasticsearch-1 search 0 0 0
elasticsearch-1 search_throttled 0 0 0
elasticsearch-1 security-token-key 0 0 0
elasticsearch-1 snapshot 0 0 0
elasticsearch-1 transform_indexing 0 0 0
elasticsearch-1 warmer 0 0 0
elasticsearch-1 watcher 0 0 0
elasticsearch-1 write 0 0 0
위와 같은 명령어를 검색하면 결과가 다음같이 나온다.
확인해보면 굉장히 많은 task 들이 존재하고 그 작업을 하는 여러 thread들이 있었는데,
내가 에러가 났던 write 행위를 하는 thread의 개수가 어마 무시하게 많았음을 알 수 있었다.
해결 방안은 무엇일까?
1차원적인 해결 방안은 누구나 생각나듯이 감당할수있는 queue size를 늘리는 것일 것이다.
기본적으로 Elastic search의 write.queue_size의 기본값은 200이고
나는 그 값을 그대로 운영환경에 사용하고 있었다.
일단 운영 환경에서는 데이터 유실이 있으면 안 되니 1차 대응으로는 해당 설정값을 늘리게 되었다.
그렇다면 효율적으로 관리하려면 어떻게 해야 할까?
무작정 queue size의 설정값을 늘리는 건 최선의 대응 방안이 아닐 것이다.
설정값을 무한정으로 늘릴 수도 없는 노릇이고, 그만큼 많은 thread를 동시에 관리하게 되면
리소스도 과하게 사용하게 될 것이기 때문이다.
Elastic search에서는 이에 indexing 속도를 튜닝할 수 있는 다양한 방법을 제공하고 있는데
그 문서를 읽어보고 정리하게 되었다.
(사실 처음 들어보는 단어도 굉장히 많았다.. 공부하자..)
- Use bulk requests
- Bulk requests는 Single request보다 더 나은 성능을 제공한다고 한다.
- 이는 RDBMS에서도 겪어봤듯이 단일 save() 보단 saveAll()의 성능이 더 좋은 것과 같은 이치라고 생각이 든다.
- Bulk request 최적 크기를 알기 위해 단일 샤드 단일 노드에 설치된 모듈에서 테스트를 해야 하며, Bulk request 또한 너무 크면 많은 요청이 동시에 전송될 때 클러스터가 메모리 압박을 받을 수 있으므로 요청당 수십 메가바이트는 초과하지 않는 것이 좋다.
- Use multiple workers/threads to send data to elasticsearch
- Bulk requests를 보내는 단일 thread는 인덱싱 용량을 최대화할 수 없을 것이다.
- 이 말은 즉 하나의 thread로 구성하여 데이터를 쓰는 것보다 여러 thread나 procces로 구성하여 데이터를 써야 클러스터의 모든 리소스를 사용하여 좀 더 빠른 쓰기를 할 수 있을 것이다.
- Unset or increase the refresh interval
- 검색에 변경사항을 표시하는 작업(새로고침)은 비용이 많이 들고 인덱싱 속도가 저하될 수 있다.
- 인덱스 별로 검색 트래픽이 없거나 매우 적다면 인덱싱 속도를 최적화하는 것이 좋다.
- index.refresh_interval 설정값을 설정해서 적절한 인덱싱 속도를 튜닝한다.
- Disable replicas for initial loads
- 한 번에 로드하려는 데이터가 많은 경우에 인덱싱 속도를 높이기 위해서는 index.number_of_replicas를 0으로 설정하는 게 좋다고 한다.
- 하지만 복제본이 없다는 것은 데이터가 있는 노드가 손실되면 데이터를 잃는다는 말이 되므로 초기 로드를 재시도할 수 있도록 데이터는 다른 곳에 있는 곳이 중요하다.
- 또한 초기 로드가 완료되면 index.number_of_replicas를 원래 값으로 되돌릴 수 있다.
- Disable swapping
- 스와핑을 비활성화하여 운영체제가 자바 프로세스를 스와핑하지 않는지 확인해야 한다.
- Give memory to the filesystem cache
- I/O 작업을 버퍼링하기 위해 파일 시스템 캐시가 사용된다.
- 머신 메모리 중 적어도 절반을 파일 시스켐 캐시에 제공해야 한다.
- Use auto-generated ids
- 명시적 ID가 있는 문서를 인덱싱하게 되면 동일한 ID를 가진 문서가 있는지 확인하는 과정이 필요하기에 이는 비용이 굉장히 많이 드는 작업으로 인덱스가 커질수록 비용이 더 많이 든다.
- Use faster hardware
- 더 빠른 드라이브를 구입하는 방법을 조사할 필요도 있다.
- Indexing buffer size
- 노드가 과도한 인덱싱을 수행하게 되면 indices.memory.index_buffer_size값이 수행하는 샤드당 최대 512MB의 인덱싱 버퍼를 제공할 만큼 충분히 커야 한다.
- Use cross-cluster replication to prevent searching from stealing resource from indexing
- 단일 클러스터로 구성을 하면 인덱싱과 검색은 리소스를 놓고 경쟁할 수 있다.
- 적어도 두 개의 클러스터를 설정하고 클러스터 간 복제를 구성하여 리소스를 자유롭게 사용할 수 있게 구성하여야 한다.
각종 업무를 진행하면서 이런저런 최적화 튜닝들을 조금씩 알아보고 진행해보지만, 이런 작업들은 사실 명확한 답이 없고
우리의 서버 상황, 트래픽 상황 심지어는 개발 인력 상황에 맞춰해야 하는 업무다 보니 굉장히 어려운 것 같다.
경험과 공부만이 답이다! (아마도?)
'Database > Elastic Search' 카테고리의 다른 글
Elastic Search 데이터 필드타입 변경하기 (Re-Index) (0) | 2022.04.07 |
---|