저희 팀은 쿠팡, 11번가, 무신사와 같은 국내 최상위 이커머스 플랫폼의 핵심 요구사항을 분석하여, 대규모 트래픽에서의 안정적인 성능 확보를 목표로 삼았습니다. 이를 위해 체계적인 3단계 고도화 과정을 거쳐 기능을 개발하였습니다.
- 기획 및 MVP 구축
- 초기 기획 단계부터 핵심 요구사항에 집중하여 MVP를 신속하게 구현했습니다.
- 단위/통합 테스트 및 1차 성능 개선
- 핵심 기능에 대해 단위/통합 테스트를 진행하였습니다.
- 검색 쿼리 튜닝 및 Spring Batch의 성능 개선을 이루었습니다.
- 선착순 쿠폰 발급, 주문/결제 로직에서 동시성 이슈를 방지하기 위한 다양한 전략을 수립하였습니다.
- 부하 테스트 및 2차 성능 개선
- 선착순 쿠폰 발급과 주문/결제 로직에 대한 부하 테스트를 진행하였습니다.
- 다양한 동시성 이슈와 병목 현상을 해결하였습니다.
- 사전에 수립한 전략들을 비교/분석하여 최적의 솔루션으로 성능 개선을 이루었습니다.
- 상품 검색: 다양한 조건(카테고리, 브랜드, 키워드, 가격) 및 정렬 기준(최신순, 관련도순)을 지원하는 상품 검색
- 검색어 자동완성: 사용자 검색 로그 기반 인기 검색어 Top 10 제안
- 선착순 쿠폰: 특정 상품에 대한 선착순 쿠폰 발급 및 다운로드
- 주문 및 결제: 상품 주문 시 카카오페이를 통한 결제. 주문 및 결제 내역 확인 가능
- 장바구니: 주문할 상품을 장바구니에 담기
- (판매자 전용) 상품 등록: 판매 상품 등록. 상품 이미지 등록 및 수정 시 이미지 순서 조정 가능
- (판매자 전용) 판매 상품 통계 조회: 판매 상품에 대한 주문량 및 매출 조회
- 고도화할 기능 기준으로 테스트 커버리지 80% 이상을 유지
- Service 계층과 Controller 계층을 중심으로 단위 테스트를 작성
- 단위 테스트에서 FakeRepository를 구현함으로써 Mockito를 이용하는 Mocking을 줄임
- GitHub Actions를 활용한 자동 테스트 및 빌드 파이프라인 구축
본 프로젝트는 다음과 같은 주요 기술적 문제들을 해결하고 성능을 개선하는 데 집중했습니다. 각 항목에 대한 자세한 내용은 링크된 Wiki 문서를 참고해주세요.
문제: 동시 사용자 5,000명 기준 API 응답 시간이 36.99초로 측정되었으며, 선착순 쿠폰 시스템 특성상 빠른 응답 속도가 서비스 품질에 치명적이라고 판단해 성능 개선 작업을 수행함.
해결 과정
- (1) 락 성능 측정 실험 (
ReentrantLockvsDB Pessimistic LockvsRedis Distributed Lock)- 3가지 락 방식에 대해 응답 시간 비교를 진행했으나, 시간 차이는 유의미하지 않았고 구조적인 개선이 필요하다고 판단함.
- (2) Redis + 이벤트 큐 기반 구조 적용
- Redis를 통해 재고 차감을 비동기 처리하고, 내부 이벤트 큐를 통해 DB와의 동기화를 수행함.
- Redis의 원자 연산으로 동시성 문제를 해결했으나, 이벤트 처리 지연으로 인해 DB 재고와의 불일치 문제가 발생함.
- (3) Redis Set 기반 재고 관리 구조로 전환
- 쿠폰 발급 시 사용자 ID를 Redis Set에 저장하여 중복 발급 방지와 재고 관리를 동시에 수행.
- 재고 판단 기준을 Redis Set의 크기로 변경하여 DB와의 불일치 문제를 제거함.
성과
- 구조 개선을 통해 병목이 제거되고, 응답 시간이 36.99초에서 1.87초로 단축되어 95% 이상의 성능 향상 달성
- Wiki: 선착순 쿠폰 성능 개선
- 문제: 결제 승인 API 호출 시 주문 로직에서 재고 관리에 대한 동시성 이슈 발생
- 해결 과정:
- (1) DB 락 적용 -> 데드락 문제 발생
- 데드락 해결 방법 중 예방/회피/회복을 시도함
- 방법1 - 예방: 격리수준을
SERIALIZABLE로 적용. 하지만 격리수준에 대한 잘못된 이해로 해당 방법은 잘못된 접근 방식이라는 것을 인식했고, 오히려 또다른 데드락 문제를 야기함 - 방법2 - 회피:
productId정렬 후 x-lock 획득하는 전략 시도. 하지만 트래픽이 집중되는 상황에서 특정 락에 대한 대기시간이 길어지면서 timeout 발생 - 방법3 - 회복:
DeadlockException발생 시 재시도하는 로직 추가. 트랜잭션 충돌이 자주 발생하지 않는 상황에서는 합리적인 선택임을 확인 - Wiki: DB 데드락 해결
- (2) Redis 분산 락
- 동시성 이슈는 원천적으로 차단할 수 있으나, DB 락보다 성능이 오히려 저하
- 이는 결제 승인 흐름 전체를 직렬 처리한 것에서 기인
- (3) Redis 재고 관리 (Lua Script 적용)
- 재고 차감을 MySQL에서 Redis로 이동
- Redis에서 재고 차감 시 Lua Script를 이용한 원자적 연산 적용
- 이를 통해 RDB 트랜잭션에 대한 부하는 감소시키면서, 재고 차감 로직을 Redis에서 빠른 속도로 실행할 수 있었기 때문에 유의미한 성능 개선을 이룸
- Wiki: 분산 락의 한계와 Redis 기반 재고 관리를 통한 성능 개선
- (1) DB 락 적용 -> 데드락 문제 발생
- 문제: 약 43만 건의 상품 데이터와 160만 건 이상의 연관 데이터를 대상으로 하는 검색 쿼리를 실행한 결과, 쿼리 실행 시간이 4736ms로 측정. 크게 4가지 원인이 있었음
- (1)
LIKE '%...%'조건으로 인해 B-트리 인덱스 활용 불가 - (2) 메인 쿼리 결과 수만큼 스칼라 서브쿼리가 반복 실행
- (3)
OR조건으로 인해 product 테이블이 풀 스캔된 후 JOIN됨 - (4) brand 및 category 테이블이 전체 행에 대해 JOIN됨
- (1)
- 해결 과정:
- Full-Text Index를 적용하여 키워드 검색 성능을 개선
- 상품 이미지 조회용 스칼라 서브쿼리를 JOIN으로 변경
- OR 조건을 사용하지 않고, UNION으로 조건 분리
- 불필요한 LEFT JOIN을 INNER JOIN으로 변경
- 결과: 검색 쿼리 실행 시간을 5.45ms까지 줄였으며, 기존 대비 868배 성능 개선
- Wiki: 상품 검색 쿼리 튜닝
- 문제: 하루 1,000만 건의 로그 데이터를 처리하는 Spring Batch 성능 테스트 결과, 실행 시간이 66.7시간으로 측정
- 해결 과정:
- Dirty Checking으로 인한 개별 업데이트 쿼리가 발생하는 문제를 JdbcBatchItemWriter를 도입하여 처리 성능을 개선하였지만, 여전히 Dirty Checking이 발생함
- 정확한 원인 파악 후, JPA 의존성을 제거하는 방향으로 문제 해결 시도. 실행 시간을 7.92시간으로 단축
- 이후 병렬 처리도 시도하였으나 데드락 문제가 발생하였고, 쓰기 성능보다 읽기 성능이 더 중요하다는 점을 인식하게 되어 구조적인 개선을 진행
- 로그 데이터의 특성을 기반으로 Batch 종료 시 마지막 처리 로그 ID(PK)를 Batch 메타 테이블에 저장하고, 이후 실행 시에는 해당 ID 이후부터 처리하도록 로직을 변경 (클러스터 인덱스 활용)
- 이를 통해 집계 쿼리를 적용하고, 전체 흐름을 단순화시킴
- 결과: Batch 실행 시간을 25초까지 단축
- Wiki: 자동완성 Batch 성능 개선
프로젝트 진행 중 겪었던 문제 해결 과정과 기술적 결정에 대한 더 자세한 내용은 아래 Wiki 페이지에서 확인하실 수 있습니다.
(Sellect Server Wiki )




