티켓팅 서비스는 특정 시간에 대량의 트래픽이 몰리는 특성을 가지고 있어, 성능 테스트가 필수적이라 생각했다.
locust는 어떤 동작(Task)으로 서버에 부하를 가할 것인지 python 코드로 커스텀하는 것이 가능하고 재사용성이 높다는 장점이 있기 때문에 채택하게 되었다.
locust를 통해 기능 테스트와 부하 테스트를 동시에 진행하였으며, 각 요청들의 평균 응답시간과 초당 요청 횟수(RPS)를 쉽게 살펴볼 수 있었습니다.
대규모 트래픽 테스트의 현실적 한계
대규모 트레픽이라 함은, 초당 1,000개의 HTTP요청이 들어오는 경우를 의미한다곤 하지만 현실적으로 테스트를 해보기에는 불가능하다 생각하여, 작게나마 테스트를 진행해보고 문제를 해결해보자 라는 생각으로 접근하였습니다.
그런데 최대 500명으로 테스트 했음에도 컴퓨터 렉이 걸리고 데이터베이스 락 문제를 겪으며 시스템이 버티기 힘든 모습을보니, 실제 대규모 트래픽 상황에서는 얼마나 더 어려울지 막막했습니다.
[문제 1] 회원가입 & 로그인 시 데이터 베이스 락
1. 첫번째 테스트
- 최대 유저: 50명
- 증가 폭: 5명
- 결과: 모든 요청을 정상적으로 처리하였습니다.
2. 두번째 테스트
- 최대 유저: 500명
- 증가 폭: 5명
- 결과: 회원가입 및 로그인에서 데이터베이스 락이 발생하며 실패율이 80%이상 발생하였다.
데이터베이스 락의 원인
데이터 베이스는 여러 작업이 동시에 실행도리 때, 데이터의 일관성을 보장하기 위해 데이터 베이스가 락을 걸게된다.
[방안 1] CONN_MAX_AGE 설정
기본적으로 장고는 CONN_MAX_AGE값이 0으로 설정되어 있어, 매 요청마다 데이터베이스 연결을 새로 열고 닫기 때문에 트래픽이 많아질 경우 락이 걸린다.
CONN_MAX의 기본값을 변경하여 연결을 재사용 하도록 설정하여 보았다. 처음에는 잘 되는듯 하였으나, Postgresql의 기본 max_connections 값(100) 때문에 아래와 같은 에러가 발생하였다.
django.db.utils.OperationalError: connection to server failed: FATAL: sorry, too many clients already
동시 요청이 많아질 경우 max_connections 값을 늘릴순 있지만, 이는 대규모 트래픽 상황에 적절하지 않다고 판단하였다.
[방안 2] Redis + Celery를 사용
Redis와 Celery를 이용하여 회원가입 및 로그인 작업을 비동기 처리로 전환하였습니다.
아래의 이미지와 같이 모든 요청이 정상적으로 처리된것을 확인할 수 있습니다.
[문제 2] 남은 좌석 조회 및 예매
회원가입과 로그인 문제가 해결된 후, 좌석을 조회하고 남은 좌석에 대해 예약 비동기로 처리하려고 했으나 두가지 문제가 발생하였습니다.
1. 데이터베이스 락
2. 좌석 전체 목록 조회에 시간을 과도하게 지연
문제를 처음 직면했을때 여러 생각이 들었는데..
"데이터 베이스 락은 비동기로 처리하면 해결 되었던 것 같은데, 왜 또 발생하지?" 라는 의문이 가장 먼저 들었고,
그리고 좌석 전체 목록을 조회하는데에는 N+1문제와 캐싱으로 해결하면 될것 같다는 생각이 들었습니다.
[방안 1] N+1, 캐싱
좌석 조회 시 발생하는 N+1 문제를 해결하고 캐싱을 도입한 결과, 조회시간이 131ms -> 4ms로 대폭 단축되었습니다.
[해결 전]
[해결 후]
[방안 2] Kafka
여러 방안을 찾아 헤매다가 Kafka라는 분산 메시징 시스템을 찾게 되었다.
- 좌석 예약 요청을 Kafka 메시지 큐에 전달해 비동기로 처리
- Kafka Consumer를 통해 각 요청을 순차적으로 처리해 데이터베이스 락 문제 해소
아래의 이미지가 Kafka를 이용하여 테스트한 결과이다.
- 최대 유저: 500명
- 증가 폭: 5명
- 결과: 예약 및 좌석 검색에서는 성공률 100프로를 보여주었고, 회원가입 및 로그인 부분에서 실패를 하여 실패율 6프로 라는 결과가 나왔다.
앞서 비동기로 처리했던 회원가입 역시 카프카를 이용해서 처리를 해주었다.
'Django > DRF' 카테고리의 다른 글
@property (0) | 2025.01.11 |
---|---|
[DRF] 쿼리 성능 향상 (Feat. ORM 최적화 기법) (0) | 2024.10.29 |
[Django] 테스트 : pytest-django, factory-boy, facker (2) | 2024.10.21 |
[Django] 읽기 전용 데이터베이스 설정 및 테스트 (0) | 2024.10.11 |
[Django] 랜덤 객체를 가지고 오는 방법 (1) | 2024.09.13 |