Open heojae opened 3 years ago
gunicorn_test
- app.py
- wsgi.py
- client.py
- run_server.sh
- run_client.sh
app.py
import os
import time
from flask import Flask, request
app = Flask(__name__)
print("Start Server PID : {}".format(os.getpid()))
count = 0
sleep_time = 2
@app.route('/a/<name>')
def a_func(name):
global count
count += 1
# client_ip = request.remote_addr
# print("a/ request Process PID : {}, ip:{}, name : {}".format(os.getpid(), client_ip, name))
time.sleep(sleep_time)
print("a/ request Process PID : {}, count : {}".format(os.getpid(), count))
return name
@app.route('/b/<name>')
def b_func(name):
global count
count += 1
# client_ip = request.remote_addr
#print("b/ request Process PID : {}, ip:{}, name : {}".format(os.getpid(), client_ip, name))
time.sleep(sleep_time)
print("b/ request Process PID : {}, count : {}".format(os.getpid(), count))
return name
@app.route('/c/<name>')
def c_func(name):
global count
count += 1
# client_ip = request.remote_addr
# print("c/ request Process PID : {}, ip:{}, name : {}".format(os.getpid(), client_ip, name))
time.sleep(sleep_time)
print("c/ request Process PID : {}, count : {}".format(os.getpid(), count))
return name
client.py
import random
import time
import requests
all_request_count = 800
per_request_count = int(all_request_count / 16)
if __name__ == '__main__':
abc_list = ["a", "b", "c"]
for i in range(0, per_request_count):
one_of_abc = random.choice(abc_list)
start_time = time.time()
response = requests.get("http://127.0.0.1:5008/{}/{}".format(one_of_abc, one_of_abc))
end_time = time.time()
print("taken time : {}".format(end_time - start_time))
# print('11111')
wsgi.py
"""
gunicorn 을 실행시키기 위해서, 서버를 배포하기 위해서 필요한 파일입니다.
"""
from app import app as application
if __name__ == "__main__":
application.run()
run_client.sh
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
python client.py &
run_server.sh
gunicorn -k gthread --workers=1 --threads=4 --timeout 60 --bind 0.0.0.0:5000 wsgi
# 경우에 따라서, 수정함이 필요.
Gunicorn 이란?
gunicorn
이란python Server
를 배포하기위해서, 사용되는WSGI
중에 하나입니다.다양한
모드(sync, gthread, gevent, tornado, eventlet)
들을 통해 다양한 경우를 지원을 해준다는 장점이 있어서, 많이들 사용하는WSGI
입니다.https://docs.gunicorn.org/en/stable/
왜 실험을 하고 싶은 것인가?
예전에 딥러닝 모델을
API
로서, 올리는 과정에서gunicorn
을 활용하였습니다.그런데, 생각보다, 성능이 기대한 만큼 처리를 하지 못하고, 리퀘스트가 늘어나면 어느정도 에서는 엄청 느려진다는 것을 볼 수 있었습니다.
물론,
딥러닝연산
이 많은CPU 연산량
이 필요한 작업이 맞지만, 어느정도 한계가 느껴진다는 것을 느낄 수 있었고,process(worker)
를 늘릴수록, 기대한 만큼의 수치가 나오지 않는다는 것 또한 확인할 수 있었습니다.물론, 제가
Gunicorn
의 내부 알고리즘을 전체를 다 분석을 할 수도 없었고, 당시 할당받은 서버의 스펙내부에서 최대한 성능을 이끌어 내고 싶었기에,AB(Apache Benchmark)test
를 활용하여서, 가장 좋은 결과가 나오는 경우를 찾아내었지만,gunicorn
자체의 내부 알고리즘이process
와calculation time
이 늘어 날때마다, 어떻게 달라지는지는 확인하지 않았기에, 이를 대략적으로 확인하고 싶어 이를 실험하기로 하였습니다.실험전 알고들어 가야하는 부분
gthread 만 선택해서, 한 이유
https://docs.gunicorn.org/en/stable/settings.html#worker-class
sync
의 경우, 직관적으로 처리되고 있는 것을 확인할 수 있으나, 그만큼 연산량 + 메모리 추가로 인한응답 시간
이 많이 커지고 있다는 것을 확인할 수 있었음.CPU 연산량이 많은 딥러닝 API
에서는gthread
가 비교적 제일 좋은 선택지라고 생각하며,worker=1
일때에는,multi thread
를 해도, docs에 있는 설명과 비슷하게 리퀘스트가 균등하게 처리된느 것을 확인할 수 있었으나,worker
와thread
가 늘어날 수 록, 예상과는 맞지 않는 모습이 보여 아래와 같이 실험을 하였습니다.실험에 대한 설정
r
-> client가 request 날린 총합 개수, 기본값 800A, B, C -> 각 프로세스(=worker)당 리퀘스트를 받은 숫자를 큰수대로 나열한 것
client 16개
가 동시에 요청을 날리는 경우이다.worker = 2
worker = 3
실험 결과에 대한 정리
동시 request
의 수의 증가에 따라서, 비교적worker process
들이 공평하게request
를 처리하고 있는 것을 확인할 수 있습니다.request
처리시간이 감소함에 따라서, 비교적worker process
들이 공평하게request
를 처리하고 있는 것을 확인할 수 있습니다.동시 request 수가 적거나
,request 처리시간이 클경우
이 두가지 경우에 대해서는worker
xthread
의 수만큼 동시에request
를 처리하는 것이 아닌, 일종의 노는process
가 존재하여, 특정process
에 대해서request
가 몰리는 경우가 생길 수 있어,worker=1
일때에 비해서, 메모리 추가 양 대비, 성과가 별로일 수 있으니, 이를 참고하시면서, 개발해주시면 좋겠습니다.