puma 웹서버 주기적으로 재시작

2016년 루비 대림절 달력(Advent Calendar)에 추가할 글로 작성했습니다.

원래는 12월 5일에 작성하기로 했는데 AWS re:invent 행사가서 힘들고 갔다 와서는 밀린 일 정리하느라 늦어졌네요. ㅜㅜ

최근 iOS 개발만 해서 루비 관련해서 뭘 적어야 할지 고민하다가 미국 갔다 와서 회사 레일스 서버 소스에 제가 커밋한 히스토리 보다 보니 오늘 글 쓸 주제가 보이더라고요!

시작

회사에서 puma를 이용해 레일스를 실행하고 있는데 어느 날부터 배포하는데 메모리 부족(Cannot allocate memory)으로 실패하는 현상이 발생했습니다.

서버 모니터링을 통해 확인해보니 배포 직후 서버 전체 메모리 사용률이 50% 이하로 내려갔다가 시간이 지나면서 90% 가까이 점유하고 있었습니다. 중간마다 메모리 사용률이 잠깐씩 떨어지기는 했지만, 조금이었고 언제라도 다시 배포하지 못하는 현상이 발생할 수 있었습니다.

메모리 누수 지점을 찾기 위해 검색을 해보니 메모리 누수를 찾는다는 것이 그리 쉬운 일이 아니라는 것을 알게 되었고 대안으로 서버를 주기적으로 재시작하면 당장 닥친 배포를 못 하는 문제는 해결이 가능할 것 같아서 검색을 하던 중 puma_worker_killer를 발견했습니다.

puma_worker_killer 소개 글에서는 메모리 누수를 찾는 것은 herculean effort가 필요하다고 하는데 엄청난 노력이 필요하다는 느낌이고 루비에서 메모리 누수를 찾는 관련 글들을 찾다 보면 이걸 내가 할 수 있나? 하더라도 시간 대비 가치 있는 건가? 싶더라고요. 서버 코드는 여러명이 수정하는데 이번에 누수지점 찾아서 고치더라도 또 발생할 여지가 있는 메모리 누수 지점을 찾는건 심각한 누수가 아니라면 노력대비 가치가 적다고 생각했습니다. 루비 메모리 누수 관련 글들도 보면 대부분 외부 젬이나 C 를 사용하는 코드쪽에서 발생하고 있었습니다.

주기적인 서버 재시작

puma_worker_killer 는 시간, 메모리 사용률에 따라 조건이 만족하면 worker를 재시작합니다. 다만 puma 웹서버를 worker + threads를 혼합해서 사용하는 클러스터 모드로 운영 중일 때만 가능합니다. thread 모드로만 실행하고 있다면 사용할 수 없죠.

사용하는 방법은 간단합니다. Gemfile에 puma_worker_killer를 추가하고 puma 설정 파일에 아래 내용을 추가만 하면 됩니다.

before_fork do
  require 'puma_worker_killer'
  PumaWorkerKiller.enable_rolling_restart # 기본 설정으로 6시간 마다 재시작
end

프로세스를 재시작하게 되면 누수되던 게 해결되지만, 서비스가 운영되는 도중에 재시작 한다는 것은 쉬운 것이 아닙니다. 사용자에게 서비스를 계속 제공하면서 서비스에 영향이 가지 않게 재시작 해야 하죠.

puma를 사용할 때 이미 이 부분에 대해 몇 가지 방법으로 대응하고 있을 거라고 생각하는데요. 일반적으로는 preload_app 모드를 사용하지 않고 prune_bundler를 이용해서 해결하거나, preload_app을 사용하는 경우 proxy에서 서버를 제외하는 방법을 사용하고 있을 겁니다. 회사에서는 preload_app 모드를 사용하지 않고 prune_bundler를 사용해서 worker가 재시작되더라도 서비스가 중단되지 않게 운영 중이라서 별다른 설정 없이 puma_worker_killer를 바로 적용했습니다.

예전에는 preload_app이 아닌 경우 puma_worker_killer를 사용하지 못했던 것 같지만, 현재 최신 버전에서는 사용 가능합니다. 하지만 puma의 설정 중 before_fork를 사용하므로 puma 버전을 2.13.0 이상을 사용해야 합니다.

효과

puma_worker_killer를 적용한 이후부터는 배포를 못 하는 오류는 발생하지 않고 있지만, 메모리를 90% 가까이 사용하고 있는 것은 마찬가지입니다. 주기적으로 worker가 재시작되고는 있지만 동시에 재시작되는 게 아니라 서로 시차를 두고 하나씩만 재시작되고 있어서 그 효과가 크게 나타나지는 않는 것으로 보입니다. 8개의 worker를 실행 중인데 8개 worker의 시작 시각을 보면 대략 2~3시간 차이가 납니다.

근본적으로 서버당 worker, thread를 줄이고 서버를 늘리거나 preload_app 을 사용해서 메모리를 적게 사용하려는 다른 노력이 필요할것 같습니다.

참고정보

게시글의 아마존, iTunes 링크들을 통해 구매를 하시면 제휴(Affiliate) 프로그램에 의해 저에게 일정 금액이 적립될 수 있습니다. ^_____^

Subscribe to Seapy Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe