인프라를 제외한 서버 수준에서 대량 트래픽 처리를 위한 셋팅 테스트
CPU 2~4 core 가정
locust 테스트 환경
gunicorn workers, threads는 저사양 instance라고 가정하고 셋팅
부하 전 API response time: 40~90ms
ERROR - connection reset by peer: gunicorn worker가 재실행되는 시점에 요청이 발생할 경우, 요청을 수신한 워커가 재시작 되면 발생 에러
[RUNTIME = 1m]
[RUNTIME = 2m]
workers, threads = 6,6
worker 별 max_requests에 달하는 요청이 들어왔을 worker가 재실행되면서 발생하는 에러는 피할 수 없음. 따라서 안정적인 응답(응답 실패율 0.01~0.1%)을 확보하기 위해서 예상되는 트래픽 수, 예상 부하 유지 시간, instance 사양에 따라 부하 테스트를 진행하면서 값을 조정해야함.
실제 서비스에서는 response 응답 시간, 메모리 소모량, I/O bound 등 여러 요소에 의해 더욱 복잡한 형태의 이슈가 생길 수 있기 때문에 지속적인 튜닝 방법에 대해 테스트하고 학습이 필요함.**
OS 설정
서버 튜닝 전, OS 셋팅에 대한 부분도 반드시 고려해야함
# 테스트에 사용된 설정
kern.maxfiles: 122880 -> 1000000
kern.maxfilesperproc: 61440 -> 65535
kern.ipc.somaxconn: 128 -> 4096
net.inet.tcp.msl: 15000 -> 15000
ulimit: 4096
Database 설정
부하 테스트는 간단히 GET을 이용해 테스트 했지만, 실제 API에서 복잡한 DML 쿼리를 사용
요청을 감당할 수 있도록 적절한 db 옵션(ex: postgresql의 max_connections) 설정 필요
db replication or sharding: slave(read-only) 디비 여러대 또는 테이블 분리를 통한 부하 분산
pgbouncer와 같은 connection pooler를 이용해 대량의 db 요청에 대한 커넥션 소켓 관리
# pgbouncer.ini
[databases]
navill_practice= host=127.0.0.1 port=5432 dbname=navill_practice
...
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /opt/homebrew/etc/pgbouncer/userlist.txt
pool_mode = transaction # pool 반납 조건(트랜잭션 단위냐 세션 단위냐 차이)
; 풀 크기
max_client_conn = 1000
default_pool_size = 400
; 세션 상태 정리
server_reset_query = DISCARD ALL # 세션 초기화 후 커넥션을 풀에 반납
...
logfile = /opt/homebrew/var/log/pgbouncer.log
pidfile = /opt/homebrew/var/run/pgbouncer.pid
# django settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
...
"DISABLE_SERVER_SIDE_CURSORS": True, # pgbouncer transaction mode일 때 필수
},
}
pgbouncer - pool_mode
인프라 구조 고려사항
CDN: static 데이터 캐싱
Message Queue(RabbitMQ, SQS, Kafka 등): 대량의 요청을 큐에 담아 부하를 감당할 량을 서버로 전달해서 처리
Cache(redis, memcached): 쿼리 결과, 데이터 결과를 캐싱해서 부하를 줄이는 방법
로드밸런싱(AWS ALB): 트래픽 분산
오토 스케일링(AWS ASG) - scale up/out을 이용한 처리량 확보