프로젝트 '오디'는 약속시간 30분 전부터 친구들이 약속 시간 내에 도착할지, 지각할지 등의 도착예정정보를 알 수 있게 해주는 서비스입니다. 1년전, 우테코 미션으로 이 핵심로직에 성능테스트를 진행하였고, 몇 가지 리팩터링 사안을 발견하였습니다.
1년이 지난 지금, 팀원들은 모두 흩어졌지만 핵심로직을 개발한 사람으로서 그 사명을 다하고자 약 한달 간 실험 설계부터 리팩터링까지 진행해보았습니다.
이번 글은 총 6편의 리팩터링 시리즈의 첫번째 글입니다.
[오디 -폴링 로직 리팩터링]
1. Warm up Code로 CPU 스파이크 해결하기 feat) JIT Compiler
2. 계정 로드 밸런싱으로 Request Failed를 잡아보자
3. 트랜잭션에서 외부 API를 분리하여 응답 속도를 낮춰보자
0. 오디의 핵심 로직이란
오디의 핵심로직은 약속 30분 전부터 약속 참여자들의 도착예정정보(도착예정, 지각위기, 도착 등)을 추적하는 로직을 의미합니다.
![]() |
![]() |
| 30분 전부터 각자 현 위치 전송 | 10분간격으로 API 호출 갱신, 중간은 카운트 다운 값 반환 |
- 각 약속 참여자들의 현재위치를 약속 30분 전부터 10초 간격으로 폴링으로 서버에게 전송
- 서버는 10분 간격으로 참여자들의 위치 - 약속장소까지의 대중교통 소요시간 API를 호출하여 정확한 소요시간을 갱신
- API호출 10분 전까지는 이전 소요시간을 카운트다운하여 반환 (ex. 1시 30분 경 소요시간이 20분이었다면 1시 32분에는 18분 반환)
1. 실험 설계
1-1) 가정된 실험 상황 : 100명의 유저가 동시 약속
오디는 현재 81명의 유저가 존재합니다. 따라서 대용량 트래픽은 아니더라도 이 81명의 유저가 만들어낼 수 있는 가장 최악의 시나리오에 대응할만한 서버 환경을 갖추게 하고 싶었습니다.
따라서 다음과 같은 상황을 가정하였습니다.

- 100명의 유저가 동일 시간대에 약속 설정
- 장소 : 인천 논현역 → 중앙대학교 후문
- 약속 : 총 40개 (2명 약속 - 20개 / 3명 약속 - 20개)
약속을 40개의 참여자 2-3명로 잡은 이유는 약속 한개당 평균 약속 참여 수가 2.3명이라는 prod 환경과 유사한 약속 환경을 만들기 위함이었습니다.
1-2) 실험 환경 세팅

- Prod 환경과 동일한 서버 환경 세팅 (RDS, Elastic Cache, Ec2 -t3a.small)
- K6 를 Ec2를 따로 분리하여 30분간 10초 간격으로 호출하도록 설정
- 실제 Odsay, Google API 호출
여기서 K6를 로컬환경이 아닌 독립적인 Ec2환경으로 분리하는 것은 필수적으로 필요한데 Mac 내부에서 일정 트래픽 이상 발생하는 것이 감지되면 자체적으로 제한되는 케이스가 있기 때문입니다.
제한 이슈 사례 ref)
K6를 이용한 서버 성능 테스트 이슈
서버 성능 테스트를 위해 k6 셋팅을 하고 테스트로 요청을 하는 도중에 아래와 같은 에러가 발생했다.WARN[0000] Request Failed error="Get \"http://localhost:8080/test/ping\": read tcp 127.0.0.1:62761->127.0.0.1:8080: read:
san-tiger.tistory.com
2. 예상했던 문제점
2-1) 트랜잭션이 너무 길다
도착예정정보를 매핑하는 로직은 매우 복잡합니다. 이를 절차적으로 풀어보면 다음과 같은데요.

지금은 이 모든 과정이 하나의 트랜잭션으로 묶여있습니다. 특히 외부 API 호출 과정도 트랜잭션 내부에 포함되므로 100개 동시요청 시, 하나의 요청 당. DB Connection이 고갈될 것이라는 예측이 있습니다.
2-2) 느릴 것 같은 응답속도
마찬가지로 10분 간격으로 100개의 외부 API를 동시요청하는 과정에서 응답속도가 확 늘고, TPS가 확 줄어들 것 같다는 생각이 들었습니다.
또한 스레드풀 최적화를 하지 않았기 때문에 톰캣 디폴트 코어 스레드인 10개를 사용하고 있었습니다. 핵심로직은 CPU 연산이 상당하기 때문에 100개의 동시요청이 오면 10개를 제외한 나머지 90개는 대기하는 과정에서 발생하는 대기시간도 우려가 되었습니다.
---
3. 성능 테스트 결과
다음과 같이 실험설계를 하고 2-3회 정도 성능테스트를 실행해보았습니다.

생각보다 많이 심각했습니다.
3-1) 외부 API 호출시점인 10분 간격으로 응답시간이 13-15초 가량 걸린다

3-2) TPS는 10분 간격으로 10 -> 3.3으로 1/3토막이 난다

3-3) Odsay API를 사용하면 Request Fail 비율이 60%에 다다를때가 있다.
API 벤더 다중화를 통해 Odsay에서 실패하면 Google API를 호출하도록 하여 가용성을 확보했으나, 이상하게도 Odsay API를 최우선순위로 사용하면 60% 다다르는 API 요청 실패를 보일때가 있었습니다.

이에 호출 벤더 우선순위를 Google API를 최우선으로 바꾸어 호출해본 결과 Request Fail 문제는 없어졌으나 나머지 문제는 잔존했습니다.

3-4) CPU 사용률은 5%내외이나 메모리 사용률이 44%가량 된다.

4. 리팩터링 목표 설정
위의 결과를 바탕으로 현재 상황의 문제 인식과 리팩터링 목표를 설정해보았습니다.
4-1) 문제 인식
- Odsay API를 사용하면 Request Fail이 발생한다 -> 대체 왜?
- 외부 API 호출 시점이 되면 TPS가 1/3토막이 난다.
- 외부 API 호출 시점이 되면 응답 시간이 13초 이상 걸린다
4-2) 원하는 임계점과 응답속도 목표
- Request Fail : 0%
- CPU : 30% 이하 (t3a.small 에서 10초 간격으로 100개의 요청을 견디는 정도로 적절한 임계점이라고 생각)
- Memory: 50% 이하
- Request Duration
- 폴링 로직 시 30ms 이하
- 10분간격으로 API 100개 동시 호출 스파이크가 튈 때 : 200ms 이하
이러한 목표를 가지고 성능 테스트를 들어갔는데 이슈가 생겼습니다.
배포 직후, 성능 테스트를 시작할 때마다 EC2 CPU 사용률에 스파이크가 튀었습니다.

이 스파이크로 인해 평균 응답속도가 왜곡되는 현상이 반복되었고 스파이크 발생의 이유가 궁금해졌습니다.

다음 글에서는 해당 스파이크를 해결한 과정을 설명하고, 차례대로 이슈를 해결해나아간 과정을 소개합니다.
요약
- 1년만에 오디 핵심로직 리팩터링하기로 마음먹음
- prod와 같은 환경에서 100명 대상 40개 약속 시나리오로 폴링 로직 시뮬레이션
- Odsay API 사용하면 API 실패율이 60%를 찍음
- API 호출시점마다 응답속도는 10s가 넘어가고 TPS는 10 -> 3.3s로 줄어듦
- 생각보다 심각한 상황에 리팩터링 해보기로 마음먹음
[오디 -폴링 로직 리팩터링]
1. Warm up Code로 CPU 스파이크 해결하기 feat) JIT Compiler
2. 계정 로드 밸런싱으로 Request Failed를 잡아보자
'프로젝트 > 오디' 카테고리의 다른 글
| [오디 - 폴링 로직 리팩터링] 2. 계정 로드 밸런싱으로 Request Failed를 잡아보자 (0) | 2025.09.21 |
|---|---|
| [오디 - 폴링 로직 리팩터링] 1. Warm up Code로 CPU 스파이크 해결하기 feat) JIT Compiler (2) | 2025.09.21 |
| [오디] 회원 전체가 동시에 탈퇴하는 속도 단축하기 feat) 쿼리 최적화 (8) | 2025.08.16 |
| JsonDeserializer 커스텀으로 외부 API 에러 핸들링하기 (0) | 2024.12.22 |
| FCM 알림 비동기 + 이벤트 리스닝으로 리팩터링 하기 - 2편(테스트) (2) | 2024.12.03 |

