애플리케이션 성능 측정과 튜닝
아래 내용은 DevOps와 SE를 위한 리눅스 커널 이야기 12장 애플리케이션 성능 측정과 튜닝을 읽고 정리한 내용입니다.
Flask 서버 설치 및 실행
# pip 및 python3 설치
yum install -y python3 python3-pip
# 가상 환경 설정
python3 -m vnev myenv
# Activate
source myenv/bin/activate && cd /myenv
# flask 및 redis 설치
pip install flask redis
vi.app.py
애플리케이션 코드 작성
import redis
import time
from flask import Flask
app = Flask(__name__)
@app.route("/test/<key>")
def testApp(key):
r = redis.StrictRedis(host='172.31.37.40', port=6379, db=2)
r.set(key, time.time())
return r.get(key)
if __name__ == "__main__":
app.run(host="0.0.0.0")
코드 실행 방법 3가지
# 1. 내장 애플리케이션(기본) 사용
python app.py
# 2. sync 타입의 워커 4개 실행
gunicorn -w 4 -b 0.0.0.0:5000 app:app
# 3. async 타입의 워커 4개 실행 및 keepalive 설정
gunicorn -w 4 -b 0.0.0.0:5000 app:app --keep-alive 10 -k eventlet
처음에는 기본 내장 애플리케이션을 사용하기 위해 첫번째 방법으로 코드를 실행합니다.
응답 확인
[ec2-user@ip-172-31-43-234 ~]$ curl 127.0.0.1:5000/test/2
1696914740.9461732
정상적으로 코드를 실행하면 위와 같은 응답을 확인할 수 있습니다.
벤치마킹 테스트 도구 설치 및 실행
벤치마킹 테스트를 위해 siege 도구를 사용합니다.
sudo amazon-linux-extras install epel
yum install siege
wget http://download.joedog.org/siege/siege-latest.tar.gz
tar -zxvf siege-latest.tar.gz
./configure --prefix=/usr/local/siege
make;make install
cd /usr/local/siege/bin
# 성능 테스트 시작
# 30초 동안 100개ㅑ의 동시 요청
./siege -c 100 -b -t30s http://127.0.0.1:5000/test/1
Lifting the server siege...
Transactions: 12252 hits
Availability: 100.00 %
Elapsed time: 29.61 secs
Data transferred: 0.21 MB
Response time: 0.24 secs
Transaction rate: 413.78 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 99.58
Successful transactions: 12252
Failed transactions: 0
Longest transaction: 0.30
Shortest transaction: 0.02
Transaction rate는 초당 서버가 처리할 수 있는 트랜잭션(요청)의 수를 나타내며 위에서는 해당 서버가 초당 약 413.78개의 HTTP 요청을 처리할 수 있다는 것을 의미합니다. 지금 사용하고 있는 플라스크의 내장 애플리케이션 서버는 싱글 스레드로 동작하기 때문에 당연한 결과입니다. 그래서 다음으로는 플라스크의 내장 애플리케이션 서버를 사용하지 않고 gunicorn이라는 별도의 애플리케이션을 사용합니다.
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app
위 명령을 실행하면 마스터 프로세스 1개와 4개의 워커 프로세스가 실행되어 총 5개의 워커 프로세스가 실행됩니다.
성능 테스트
./siege -c 100 -b -t30s http://127.0.0.1:5000/test/1
Lifting the server siege...
Transactions: 12488 hits
Availability: 100.00 %
Elapsed time: 29.74 secs
Data transferred: 0.21 MB
Response time: 0.24 secs
Transaction rate: 419.91 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 99.38
Successful transactions: 12488
Failed transactions: 0
Longest transaction: 0.34
Shortest transaction: 0.02
Transaction rate가 큰 변화가 없습니다. CPU가 1개 1코어인 서버에서 gunicorn의 멀티 프로세 스 방식을 사용하는 것과 싱글 스레드로 실행하는 것은 성능상 큰 차이를 보이지 않는 것 같습니다. 따라서 위와 같은 성능 테스트 변화를 관측하기 위해서는 테스트 서버를 단일 코어로 생성할 것이 아니라 멀티 코어로 생성하는 것을 권장드립니다. 교재에서는 gunicorn으로 실행했을 때 성능이 확연히 좋아졌었는데 이는 gunicorn은 멀티 프로세스 모드로 동작시키기 때문이기도 하지만 그만큼 플라스크 기본 애플리케이션 서버의 성능이 좋지 않음을 의미한다고 합니다!
단일 코어 CPU에서는 한 번에 하나의 작업만 실행하므로 여러 프로세스가 있다고 한들 이들 프로세스는 CPU 코어에서 번갈아 가며 실행되어 단일 코어에서 멀티 프로세스 방식은 병렬 처리의 이점을 제대로 활용할 수 없습니다.
또한 멀티 프로세스 방식이 무조건 좋은 건 아닙니다. 여러 프로세스를 실행하면 운영 체제는 프로세스들 사이에서 컨텍스트 스위칭을 수행해야 하며 이러한 스위칭은 오버헤드를 발생시키고 많은 양의 컨텍스트 스위치는 곧 성능 저하를 일으킬 수 있습니다. 웹 서버의 경우 대부분의 작업이 I/O 바운드인데 네트워크 I/O나 디스크 I/O와 같은 작업에서 대기하는 시간(대기큐에서 대기하는 시간)이 CPU에서 실제로 작업을 수행하는 시간(준비큐에서 실행상태로 변경되어 실행되는 시간)보다 길 수 있습니다. 이러한 경우, 멀티 프로세스나 멀티 스레드 방식이 I/O 바운드 작업의 처리 속도를 향상 시킬 수 있습니다.