2021. 10. 6. 14:19ㆍProgramming/python
파이썬에서 가장 간편한 웹 서버로 flask를 주로 이용한다. flask를 이용하다가 조금 규모가 커지거나 로직이 필요한 작업이 생기면, 이때부터 async를 고려하게 되는데, 나 같은 경우에는 gnicorn을 사용함으로써 async 문제를 풀곤 했다.
그런데 gunicorn 사용 후 어느 순간부터 메모리가 차츰 차츰 올라가는 것을 발견했다.
'어.. 이거 왜 안떨어지지??'
메모리가 아주 느리지만 조금씩 올라가고 있는 것을 확인해 볼수가 있다.
엄청난 인고의 고통 속에 원인이 gunicorn이란 것을 밝혀냈다.
또한 구글에서 "gunicorn memory leak" 으로 검색해보면 나같이 메모리 릭으로 고생하고 있는 수많은 블로그들을 찾아볼 수가 있다.
원인은 gunicorn에서 생성한 worker 노드들이 메모리를 들고 일을 하다가 해제를 못하는 케이스가 발생해서 생기는 경우 인 것 같다.
그럼 이제 해결방법에 대해서도 알아 보자.
gunicorn 쪽에서는 왠지 이러한 문제를 알고 있었던 것 같다.
gunicorn 옵션 중에 max_requests란 옵션이 있는데, 해당 max_requests 까지 requests가 요청되면 worker 들을 restart 시키는 옵션이다. 처음에는 왜 이런 옵션이 필요하지?? 라고 생각을 했는데 이런 메모리릭을 고려해서 생긴 옵션이 아닐까 생각한다.
max_requests를 설정하고 어느 순간 requests가 도달하면, 모든 worker노드 들이 restart 되서 서비스 장애가 발생할 수도 있다.
그래서 max_requests는 max_requests_jitter와 함께 사용하곤 한다.
실제 gunicorn 코드를 살펴보면, max_requests_jitter는 아래의 방법 처럼 사용된다.
https://github.com/benoitc/gunicorn/blob/master/gunicorn/workers/base.py
if cfg.max_requests > 0:
jitter = randint(0, cfg.max_requests_jitter)
self.max_requests = cfg.max_requests + jitter
else:
self.max_requests = sys.maxsize
위 코드는 gunicorn의 worker 노드에 대한 코드 중 일부이다. 코드를 보면, max_requests 값에 0 ~ jitter 값 사이의 random 값을 추가해서 self.max_requests를 만드는 것을 확인할 수가 있다.
즉, 각 worker마다 max_requests + random 값의 max_requests를 각각 가질 수 있게 되고, 결과적으로 동시에 restart 되는 현상을 막을 수가 있다.
결과적으로 worker node를 restart 시킴으로써 메모리 릭을 방지할 수 있다. (먼가... 찜찜)
max_requests와 max_requests_jitter는 아래와 같이 사용하면 된다.
gunicorn --workers 64 --bind unix:/socket/gunicorn.sock --max-requests 36 --max-requests-jitter 36 wsgi:app