Gunuicorn 기본 설정과 개념, 메모리 누수 방지

렁호·2023년 1월 8일
0
post-thumbnail

0. 개요

python 기반의 웹 어플리케이션을 운영한다면 WSGI라는 개념을 많이 들어봤을 것이다. 대표적으로 gunicorn, uwsgi 등을 사용하는데 그 중에서도 guicorn에 대해서 알아보자.

1. WSGI 란 ?

WSGI란 쉽게 말해 python 웹 어플리케이션과 웹 서버가 잘 소통하기 위한 인터페이스이다. 웹 서버에서 받은 http 프로토콜은 python 웹 어플리케이션 에서 사용하는 데이터 형태와 상이하기 때문에 문제가 발생한다. 이런 문제를 WSGI가 중간에서 번역 및 전달 업무를 해준다고 생각하면 된다.
WSGI 전신이 되는 CGI가 처음 도입 되었으며, 당시 매 요청 마다 스크립트 전체를 실행해야 되는 비효율적인 프로세스로 인해 WSGI 가 나왔고 현재는 비동기를 사용해 속도를 개선한 ASGI 도 사용 가능하다.
TMI로 요즘 핫한 FAST API의 빠른 속도가 ASGI를 사용하는 것이 한 몫 한다.

2. Gunicorn 설정

wsgi app 설정

gunicorn을 런해주기 위해서 wsgi 모듈과 맵핑해줄 필요가 있다.

$(MODULE_NAME):$(VARIABLE_NAME) 와 같이 설정해주면 되는데
django로 예를 들면 wsgi 모듈 경로를 첫번 째 인자에 입력하고 두번 째 인자에 wsgi 어플리케이션 객체 변수 이름을 입력해주면 된다.

gunicorn <모듈 이름>:<변수 이름>

bind

바인드 될 주소와 포트를 설정 할 수 있다.
default는 로컬 ip의 8000 포트이다.

--bind 0.0.0.0:443
or
--b 0.0.0.0:80

Process, Thread 설정

guniorn은 Master Process가 Worker Process를 Fork 하여 관리하고, Worker Process가 실제 요청을 받아 로직을 수행하는 역할을 담당 한다.
Worker Process의 개수와 클래스를 목적에 맞춰 설정하는 것이 중요하다.

공식 사이트에선 다음과 같이 2 * cpu 코어 개수 + 1 만큼 프로세스를 설정하는 것을 추천한다.

gthread worker를 사용할 때 thread 개수는 코어 개수 * (2 - 4) 의 값을 권장 한다.

설정은 아래와 같은 명령어로 가능하다.

--workers <워커프로세스 > -k <worker class> --threads=<스레드 >

로그

Gunicorn은 access, error 두 가지 로그를 제공한다.

주의할 점은 django 를 사용할 때에 Master 프로세스는 상관 없지만 django 로그 셋팅에 'disable_existing_loggers': False 을 해주지 않는다면 Worker 프로세스의 gunicorn 로그가 생성 되지 않는다.

자세한 내용은 장영석님 블로그 에 잘 정리된 글로 확인 가능하다.

로그 파일 경로 설정은 아래와 같이 가능하다.

--access-logfile /home/ubuntu/logs/gunicorn-access.log 
--error-logfile /home/ubuntu/logs/gunicorn-error.log 

최대 요청 (메모리) 관리

모니터링 과정에서 간혹 서버 메모리가 계속해서 올라가는 경우가 발생 됐다. 원인 파악 결과 api 요청을 처리할 때 마다 요청을 처리한 worker 프로세스에서 메모리 누수 현상이 발견 됐다.

근본적인 원인은 정확하진 않으나 django queryset 캐시가 요청이 끝난 후에도 릴리즈 되지 않고 일부분 계속 남아 쌓이는 걸로 추측 했다. (이 부분은 나중에 정확한 실험을 통해 입증 되면 수정 할 예정)

암튼 이래저래 검색을 해보니 django + gunicorn 운영 환경에서 꽤나 흔하게 발생하는 고질적인 문제인 듯 했다.

이럴 때 max-requests 설정을 통해 최대 요청 개수를 설정하고 최대 요청 개수에 도달 했을 때 도달한 프로세스만을 재시작하여 메모리를 리프레시 하는 방식으로 해결이 가능하다.
추가로 만약에 여러 프로세스가 동시에 max-request 한계치에 도달하여 재시작 되는 경우 요청을 처리하는 시간이 늦어지는 것을 방지 하기 위하여 max-requests-jitter 설정으로 프로세스 별로 max-requests 설정값에 (0 ~ jitter 설정 값)의 난수를 더 하여 각기다른 max requests 를 설정하도록 도와줄 수 있다.

근본적으로 메모리 누수를 막는 방법은 아니지만 메모리 누수 때문에 큰 문제가 생기는 것을 방지 할 수 있는 좋은 선택이다. 설정 값은 운영 상황에서 평균적으로 몇 번의 요청에 얼만큼의 누수가 일어나는지를 잘 고려하여 설정하면 된다.

명령어는 다음과 같다.

--max-requests <최대 요청 >
--max-requests-jitter <난수 최대값>

번외로 max-request를 사용하는 것 외에도 특정 api에서 메모리에 잔여 데이터가 계속 쌓이는 것이 확인된다면 python의 가비지 컬렉터 모듈을 사용하여 수동으로 메모리를 비워주는 것도 하나의 방법이 될 수 있다.

reload

reload 설정을 하게 되면 WorkingDirectory의 소스 코드가 변경 되었을 때 자동으로 gunicorn 서비스가 reload 된다.

본인은 로컬에서 매우 유용하게 사용하고 있는 설정이니 로컬 환경에서 빠른 실시간 테스트를 위해 꼭 적용해서 사용해보도록 하자

-reload

참조

https://docs.gunicorn.org/en/stable/index.html
https://blog.hwahae.co.kr/all/tech/tech-tech/5567
https://sgc109.github.io/2020/08/15/python-wsgi/#CGI

profile
식욕, 수면욕, 성욕 만땅

0개의 댓글