1. 개선 전략
개선 전 테스트 분석 결과, 시스템은 비효율적인 다중 DB 조회와 부족한 커넥션 풀이라는 문제를 가지고 있었다. 이를 해결하기 위해 두 가지 핵심 전략을 수립했다.
1.1 캐시 도입
한 번의 주문 API 요청(reserve) 내에서 발생하는 3번의 DB 조회를 1번으로 줄이는 것을 목표로 했다.
-
회원 배송지 정보 (findDefaultByMemberNo)
시스템은 100만 명 이상의 유저를 수용하는 대규모 환경을 가정했다.
- 메모리 효율성
- 유저별 개인화 데이터는 카디널리티Cardinality)가 매우 크다.
- 이를 모든 WAS 인스턴스의 로컬 메모리에 분산 저장할 경우, 중복 데이터로 인한 메모리 점유율이 기하급수적으로 상승한다고 판단했다.
- 캐시 히트율 저하
- 로드밸런서가 요청을 분산하기 때문에, 특정 유저의 요청이 동일한 인스턴스로 들어온다는 보장이 없다.
- 결과적으로 각 인스턴스 내 로컬 캐시 히트율은 낮아지고 DB 조회 빈도는 크게 줄어들지 않는다.
- 결정
- 데이터 일관성을 보장하고 인스턴스 간 메모리 낭비를 방지하기 위해 중앙 집중형 저장소인 Redis를 선택했다.
-
상품 창고 정보 (findWarehousesForProduct)
창고 정보는 모든 유저가 공유하는 데이터이며, 카디널리티가 작다고 판단했다.
- 모든 주문 로직의 필수 단계인 창고 조회를 로컬 메모리에서 처리함으로써, Redis와 통신할 때 발생하는
네트워크 지연 시간과 직렬화/역직렬화 비용을 없앴다.
- 또한 이벤트 진행 중 창고 정보가 변경될 가능성은 극히 낮다고 봤기 때문에, TTL 설정만으로도 데이터 정합성 리스크를 충분히 관리할 수 있다고 판단했다.
-
상품 정보(findByProductNo)
선착순 이벤트의 특성상 수십만 명의 유저가 단 몇 개의 '인기 상품'에 집중될 것이라고 생각했다.
- 변동성
- 사실상 없음. 이벤트 도중에 상품 가격을 바꾸거나 이름을 바꿀 일은 없다. 사실상 상수다.
- 따라서 서버 간 데이터 불일치를 걱정할 필요가 없다.
- 모든 주문 로직의 필수 단계인 상품 정보 조회를 로컬 메모리에서 처리함으로써,
Redis와 통신할 때 발생하는
네트워크 지연 시간과 직렬화/역직렬화 비용을 없앴다.
1.2 리소스 튜닝
스레드가 너무 많아 발생하는 경합을 줄이고, 실제 일을 하는 커넥션을 늘렸다.
- HikariCP Max Size: 10 →
30 (동시 처리량 확보)
- 현재 DB가 작업을 처리하는 시간(
Usage Time)은 25ms로 매우 빠르다.
- 하지만 커넥션이 없어서 기다리는 시간(
Acquire Time)이 400ms 인 것을 확인했다.
- 단순히 pool을 3배 늘리면, 이론상 동시 처리량(Throughput)도 3배 가까이 늘어날 것으로 생각했다.
- 다만, app 서버 메모리(2GB)와 DB CPU Usage를 모니터링하여 적절한 수를 찾아야 할 것으로 보인다.
- Tomcat Max Threads: 200 →
100 (Context Switching 오버헤드 감소)
- 기존에는 DB I/O 대기 때문에 스레드 200개가 꽉 찼다.
- [Phase 2] 에서는 캐시 전략 도입으로 인해 DB I/O 횟수 자체가 줄어들고, 트랜잭션 처리 시간이 줄어들 것으로 예상된다.
- 이에 따라 처리 속도가 빨라지면, 더 적은 스레드로도 동일한 트래픽을 감당할 수 있다고 판단했다.
2. 부하 테스트 결과