부하테스트 결과 보고서.pptx.pdf
배경
- 서비스 환경
- 도메인 주도 설계에 기반한 모놀리식 아키텍처.
- 특정 도메인 API가 외부 도메인의 API를
RestClient로 호출하여 데이터를 조합하는 구조.
- 테스트를 진행하게된 계기
- 서비스 아키텍처 설계상 '설문 도메인'은 여러 기능의 데이터를 중계하는 핵심 데이터 허브 역할
- 사용자 증가 시 조회 트래픽이 설문 도메인에 집중되어 성능 병목 현상을 일으킬 가능성이 높다고 판단
- 시스템의 한계를 명확히 파악하고 대응하고자 부하 테스트를 최우선 과제로 선정하여 진행.
문제사항 & 고려사항
1차 부하 테스트(최대 100 VUser) 결과, 시스템이 부하를 전혀 감당하지 못하는 심각한 문제가 발견되었습니다.
-
매우 낮은 처리량: 처리량은 19.7 RPS에 그쳤고, 에러율은 **81.2%**에 달했습니다.
-
스레드 연쇄 대기: 문제의 핵심 원인은 톰캣 스레드 풀의 구조적 한계였습니다.
- 사용자 요청을 받은 스레드 A가 비즈니스 로직 처리 중, 외부 API를 호출하기 위해
RestClient를 사용합니다.
- 이
RestClient 호출은 톰캣 스레드 풀에서 새로운 스레드 B를 할당받아야 합니다.
- 하지만 부하 상황에서는 모든 스레드가 이미 사용자 요청을 처리하고 있어 여유 스레드가 없습니다.
- 결국 스레드 A는 스레드 B를 무한정 기다리다 타임아웃이 발생하며, 이 현상이 동시다발적으로 일어나 시스템 전체가 마비되었습니다.
-
자원 고갈: 스레드 풀 뿐만 아니라, 대기 시간이 길어지면서 DB 커넥션 풀까지 모두 고갈되는 2차 문제가 발생했습니다.
-
문제상황 흐름도

해결방안 1 및 한계 → 로컬 캐싱
가장 먼저, 외부 API 호출 자체를 줄이기 위해 로컬 캐시(@Cacheable)를 도입했습니다.
- 결과: 외부 API 호출이 획기적으로 줄면서 RPS는 100까지 달성하고 에러율은 0%로 떨어지는 즉각적인 효과를 보았습니다. 이를 통해 병목 지점이 외부 API 호출이었음을 명확히 확인했습니다.
- 한계 및 롤백 이유:
- 임시방편: 근본적인 '스레드 연쇄 대기' 문제를 해결한 것이 아니라, 단순히 호출 빈도를 줄여 문제를 회피한 것에 불과했습니다.
- 데이터 정합성: 데이터가 실시간으로 갱신되지 않았고, 만료 시점에는 캐시 스탬피드 현상이 발생할 수 있습니다.
- 확장성 부재: 다른 API에서도 동일한 문제가 발생할 수 있어, 모든 외부 호출에 캐시를 적용하는 것은 현실적이지 않았습니다.
- 결론: 캐싱은 문제의 원인을 증명했지만, 안정적인 해결책이 아니라고 판단하여 최종적으로 롤백했습니다.
해결방안 2 → 조회용 테이블 도입
외부 서비스에 대한 실시간 의존성 자체를 제거하기 위해, CQRS 패턴을 적용하여 조회 전용 데이터 저장소를 구축하기로 결정했습니다.
- 조회용 테이블 설계
- 설계: 외부 API 호출을 통해 가져오던 데이터를 미리 조합하여, 읽기 성능이 뛰어난 MongoDB에 조회 전용
survey_summaries 컬렉션을 구축했습니다.
- 기대 효과: API 요청 시 값비싼 외부 호출 대신, 내부 MongoDB를 조회하도록 변경하여 네트워크 지연 및 스레드 대기 문제를 원천적으로 제거합니다.
- 데이터 동기화 전략
- 비동기 처리: 설문 생성/수정 등 원본 데이터가 변경될 때,
@Async를 통해 즉시 조회용 테이블에 비동기적으로 데이터를 동기화합니다.
- 배치 처리: 참여자 수와 같이 실시간 정합성이 중요하지 않은 데이터는
@Scheduled를 이용해 5분마다 동기화하여 외부 API 서버의 부하를 최소화합니다.
- 한계
- 외부 API 호출을 스케줄링을 통해 진행하기 때문에 캐싱과 비슷하게 실시간성이 부족하다
- 외부 API를 호출하여 가져오는 데이터가 근사치만으로 충분하다고 고려되어 큰 이슈로 생각되지 않습니다.
- 원본 DB나 조회용 DB에서 장애 발생 시 처리 로직이 복잡해진다.
- 현재 이에 대한 대응은 부족한 상태로 추후에 회복 전략을 도입하여 해결할 수 있을 것으로 보고있습니다.