이번 글에서는 "등록 성공 시 문자 알림 기능"에 대해서 개선 내용을 작성하고자 합니다.
현재 TPS가 높지 않은데 앞으로 차츰차츰 높여가겠습니다. 화이팅!
등록을 성공한다면 문자 알림이 가도록 기능을 구현하였다.
하지만 모니터링 중 성능이 매우 낮은 문제를 발견하였고, 현재 코드에서 어떤 문제가 있는지 확인하고 개선하고자 한다.
- 문제 1. 아쉬운 TPS
좋고 나쁨의 기준을 세우기 어려워 커뮤니티를 운영하시는 선배 개발자분에게 여쭤보았다. 물론 이 기준을 충족하기 어려운 서버 성능 또는 많은 데이터를 넣어 생각보다 성능이 안 나올 수 있지만 그럼에도 불구하고 너무 낮다.
- 문제2. 부족한 DB Connection Pool
아래 지표에서 확인할 수 있듯이 DB connection Pool이 많이 부족하다. 이는 빠르게 쿼리가 일어나지 못하고 있어 기존 트랜잭션 처리 속도가 새로운 요청 속도를 못 따라가서 발생하는 문제이다. 따라서 트랜잭션의 범위를 최소화하고 코드 부분을 개선하고자 한다. 물론 커넥션 풀 자체를 늘릴 수도 있지만, 이 내용은 다음에 다루고자 한다.
|
|---|
|
|---|
현재는 트랜잭션 내에 "등록" 기능과 "알림" 기능이 포함되어 있다. 트랜잭션이 오래 유지되면 그만큼 커넥션 풀도 점유하게 되고, 락이 오래 유지되어 성능에 좋지 못할 것이다. 또한 알림 기능은 부가적인 기능으로 사용자 입장에서 "등록을 성공하는 것 > 알림이 오는 것"으로, 등록을 성공하는 것이 중요하다.
- 개선 방향
- 알림 기능을 트랜잭션과 분리
- 비동기 처리
- 예상되는 이점
- 트랜잭션의 주요 로직과 부가 기능을 분리
- 성능 향상 및 커넥션 풀 효율 증가
|
|---|
- Spring Event를 활용하여 알림 기능을 트랜잭션 범위 밖으로 빼 관심사 분리
- @TransactionlEventListener 사용하여 트랜잭션 커밋 이후 알림 실행하여 커넥션 점유 시간 최소화
- @Async 사용하여 비동기 처리 및 ThreadPoolTaskExecutor 직접 설정
|
|---|
|
|---|
- ThreadPoolTaskExecutor 설정
- 사용 및 설정 시 주의점
- ThreadPoolTaskExecutor를 설정하지 않으면 SimpleAsyncTaskExecutor가 사용된다.
이는 실행 시 ThreadPool에서 재사용하지 않고, 새로운 Thread를 생성한다.
이 때 Thread 생성 비용이 계속해서 발생하고 서버 리소스 낭비로 이어진다.- @Async는 스프링 AOP에 의해 동작한다.
따라서 같은 클래스에서 Async함수를 호출할 때는 프록시 객체가 아닌 직접 빈을 호출하기 때문에 비동기 작업이 의도대로 동작하지 않을 수 있다.- 설정 값
- CorePoolSize: 15개, 유지할 쓰레드 수
- MaxPoolSize: 100개, 생성 가능한 최대 쓰레드 수
- QueueCapacity: 50개, 작업 큐 길이
- RejectedExecutionHandler: DiscardPolicy, 오버되는 요청은 폐기
- 알림 기능은 사용자 편의를 위한 기능으로 비지니스상 필요한 기능이 아니다.
- ThreadPool 동작 순서
- CorePoolSize까지 쓰레드 개수 증가
- 나머지 요청은 Queue에 적재
- QueueCapacity보다 많은 요청 시 MaxPoolSize까지 쓰레드 개수 증가
- Queue도 꽉 차고 MaxPoolSize까지 쓰레드가 증가한 상태라면, RejectPolicy에 따라 추가 요청 거부
- 설정 의도
- 알림 기능은 이벤트에 저장을 성공했을 때 발송이 된다. 이는 갑작스럽게 트래픽이 몰리고, 평소에는 트래픽이 많이 없는 함수
- 따라서 평소에는 15개로 적은 쓰레드를 유지하다가 대기열을 적게 설정하여 트래픽이 몰리는 상황에는 빠르게 쓰레드를 확장할 수 있도록 설정
|
|---|
- 개선 전 테스트 시나리오
- 가상 유저: 최소 120명 ~ 최대 200명
- 0 ~ 1 min: 초당 50번 요청
- 1 ~ 2 min: 초당 100번 요청
- 2 ~ 3 min: 초당 0번 요청 (기존 들어온 요청 처리)
- DB 내 데이터
(시나리오에서는 새롭게 qrcode event를 생성하여 등록, 쿼리 속도와 연관있음)
- guestbook: 10,000,000 개 (천만 개)
- qrcode event: 1,000,000 개 (백만 개)
- 추가 조건
- 개선 전과 동일한 시나리오를 사용하려 했으나, 개선된 성능에 맞게 시나리오를 새로 구성
- 외부 API 비용, 1일 제한량 때문에 실제 문자를 보낼 수 없어 현재 Thread에서 0.5 sec 대기
|
|---|
|
|---|
|
|---|
개선 전후 성능 비교
- TPS: 10 ➡️ 43 (소수점 첫째 자리 반올림 적용)
- 평균 Response Time: 557ms ➡️ 177ms
- DB Connection Timeout: 1331 ➡️ 1
- 총 요청 성공 비율: 23% ➡️ 99.9%
- 성공 횟수 / 총 요청 횟수: 391 / 1722 ➡️ 5520 / 5521