개발 기간: 2024.10.21 ~ 2024.11.22
All In Auction 은 사용자가 실시간으로 물품을 경매할 수 있는 Java Spring 기반의 중고 경매 사이트입니다. 사용자는 경매 등록, 입찰, 실시간 알림을 통해 다른 사용자와의 경쟁을 경험할 수 있으며, 대규모 트래픽에도 안정적인 입찰 및 쿠폰 발급이 가능하도록 설계되었습니다.
-
경매 물품 등록
판매자가 경매에물품을 등록하여 시작 가격과 경매 만료 시간을 설정합니다. -
입찰자 입찰 생성
입찰자는 관심 있는 경매 물품에입찰 금액을 입력하여 입찰을 시작합니다. -
최고 입찰자 갱신
각 입찰 시 기존 최고 입찰 금액과 비교하여 더 높은 금액이 제시되면 해당 입찰자가최고 입찰자로 갱신됩니다. -
경매 종료 및 낙찰
정해진 만료 시간이 지나면 경매가 종료되며, 최고 입찰자가최종 낙찰자로 확정됩니다.
7. API 명세
8. 기술적 의사결정
- 경매 입찰 로직에서 읽기와 쓰기 작업이 단일 트랜잭션에서 수행되면서 데이터 처리량이 낮고 지연이 발생.
- 높은 동시 요청 시 경합(lock contention)과 데이터베이스 부하 증가.
- CQRS 패턴을 도입하여 읽기 작업과 쓰기 작업을 분리.
- 쓰기 작업은 마스터 데이터베이스에 집중하고, 읽기 작업은 읽기 전용 데이터베이스에서 처리.
시나리오: 유저 1000명이 동시에 경매 한 건에 입찰 요청.
도입 전 성능 테스트 결과
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 1000 | 8561 | 2506 | 13292 | 3014.49 | 0.20% | 61.4/sec | 37.14 | 25.83 | 619.5 |
도입 후 성능 테스트 결과
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 1000 | 4599 | 23 | 8235 | 2076.24 | 0.10% | 89.0/sec | 50.92 | 37.44 | 585.6 |
- 평균 응답 시간: 8561ms → 4599ms (약
46% 감소) - 오류율: 0.20% → 0.10% (절반 감소)
- 처리량: 61.4 요청/초 → 89.0 요청/초 (약
45% 증가)
- 기존 모놀리식 아키텍처에서 OpenFeign을 사용한 서비스 간 통신의 응답 시간이 느림.
- JSON 데이터 직렬화로 인해 데이터 전송에 불필요한 오버헤드 발생.
- gRPC를 도입해 Protobuf 기반 데이터 직렬화로 전송 속도 개선.
- OpenFeign에서 gRPC로 전환하여 서비스 간 통신 최적화.
시나리오: 유저 1000명이 동시에 경매 한 건에 입찰 요청.
OpenFeign 성능 테스트 결과
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 1000 | 8208 | 79 | 14911 | 3899.81 | 0.00% | 56.3/sec | 33.86 | 23.62 | 615.8 |
gRPC 성능 테스트 결과
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 1000 | 4428 | 14 | 8169 | 2254.94 | 0.00% | 89.5/sec | 51.38 | 37.73 | 585.8 |
- 평균 응답 시간: 8208ms → 4428ms (약
45% 감소) - 처리량: 56.3 요청/초 → 89.5 요청/초 (약
59% 증가)
- 여러 사용자가 동시에 쿠폰 발급 요청 시 중복 발급과 데이터 불일치 발생.
- Redis에서 쿠폰 수량을 처리하는 작업이 원자적으로 수행되지 않음.
- Redisson으로 분산 락 구현하여 동시성 문제 해결.
- Lua 스크립트를 사용해 Redis에서 쿠폰 수량 조회와 업데이트를 원자적으로 처리.
시나리오: 1000개의 수량을 가진 쿠폰에 대해 10000명이 발급 요청
- 도입 전: 동시 요청으로 인해 데이터 불일치 문제 발생, 요청 실패율 증가.
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 10000 | 14385 | 13 | 21409 | 6561.68 | 90.01% | 333.6/sec | 285.48 | 116.58 | 868.6 |
- 도입 후: 동시성 제어에 성공하여 정확한 쿠폰 수량 관리, 요청 실패율 0%.
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 10000 | 1792 | 6 | 7142 | 1980.98 | 0% | 743.2/sec | 725.82 | 234.51 | 1000.1 |
- 동시 요청 처리: 정확한 쿠폰 발급 수량 유지.
- 성공률 증가: 동시 요청에서도 발급 성공률 100% 보장.
- 평균 응답 시간: 14385ms → 1792ms (약
87.5% 감소) - 오류율: 90.01% → 0% (완전 제거)
- 처리량: 333.6 요청/초 → 743.2 요청/초 (약
122.8% 증가)
- JPAItemWriter의 Dirty Checking으로 단건 처리 발생하며 빈번한 Database 통신으로 인한 성능 저하.
- JPAItemReader로 offset 기반 페이징 한계로 인해 데이터가 많아질수록 조회 속도 저하.
- Reader에서 모든 컬럼 조회하며 불필요한 데이터 과다 조회.
- 작은 chunk size로 인해 트랜잭션 커밋, I/O이 빈번하게 일어나 성능 저하.
- Writer를 JDBCBatchItemWriter로 변경하여 청크 기반 처리로 대량 데이터 처리 성능 향상.
- Reader를 JDBCPagingItemReader로 변경하여 no-offset pagination으로 쿼리 성능 개선.
- Reader에서 필요한 컬럼만 조회하도록 Projection 적용하여 쿼리 최적화.
- 테스트를 통해 적정한 chunk 크기를 찾아 100에서 500으로 조정.
- 순서 보장이 불필요한 작업이므로 Single Thread에서 Multi Thread로 변경.
- 도입 전: 만료된 쿠폰 처리 시간 10시간 20분.
- 1차 개선 - Reader, Writer 변경, Chunk 크기 최적화: 처리 시간 31분 40초로 단축.
- 2차 개선 - Multi Thread로 변경: 처리 시간 19분 34초로 단축.
- 처리 속도 1차 개선: 37240분 → 1900분 (약
94.90% 단축). - 처리 속도 2차 개선: 1900분 → 1174분 (약
38.20% 단축). - 총 실행 시간 단축: 37240분 → 1174분 (약
98.77% 단축).
- MSA 환경 전환으로 인해 서비스 간 의존성이 증가.
- 하나의 서비스가 장애를 일으키면 이를 호출하는 다른 서비스들도 무한 대기 상태에 빠지거나 장애가 전파.
- 장애 서비스로의 요청이 계속 쌓여 CPU, 메모리 등 리소스가 고갈되며 전체 성능 저하.
- 실패한 서비스 호출로 인해 응답 시간 늘어나며 사용자 경험 악화.
- Resilience4J를 활용해 서비스 연쇄 장애 방지 및 MSA 환경에서의 안정적인 서비스 구축.
- 임계치 초과 시 호출을 차단(OPEN) 하고, 문제가 있는 서비스로의 추가 요청 차단.
- Fallback 메서드를 통한 서비스 대체 동작 구현 및 서비스의 완전한 장애 방지.
- 기존 SQL의
LIKE %___%방식으로 Full Table Scan 발생. - 검색어와 완전히 일치하지 않으면 결과 반환 불가.
- Elasticsearch 도입하여 역 인덱스(Inverted Index) 기반 검색 구현.
- Nori Analyzer로 한국어 형태소 분석 및 검색 정확도 향상.
- Fuzziness 활성화로 오타 허용 및 부분 일치 검색 가능.
시나리오: 1000명이 300만 건의 상품을 검색.
도입 전 성능 테스트 결과
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 1000 | 78241 | 2933 | 154075 | 43697.74 | 10.20% | 6.4/sec | 15.82 | 2.52 | 2544.0 |
도입 후 성능 테스트 결과
| 구분 | 표본 수 | 평균 (ms) | 최소값 (ms) | 최대값 (ms) | 표준편차 | 오류 % | 초당 요청 수 | 수신 KB/초 | 전송 KB/초 | 평균 바이트 수 |
|---|---|---|---|---|---|---|---|---|---|---|
| HTTP 요청 | 1000 | 403 | 6 | 799 | 248.18 | 0.00% | 340.8/sec | 743.57 | 137.49 | 2234.0 |
- 평균 응답 시간: 78241ms → 403ms (약
98% 감소). - 처리량: 6.4 요청/초 → 340.8 요청/초 (약
53배 증가).
- Kafka 프로듀서에
Transactional ID를 부여, 메시지 발송과 서비스 단의 데이터베이스 트랜잭션을 하나의 작업 단위로 묶음. - 이를 통해 데이터 손실 및 중복 처리 방지 가능. 메시지의 Exactly Once 전송 보장.
acks=all옵션을 활성화하여 모든 replication의 broker가 메시지를 정상적으로 저장했음을 확인한 후에만 메시지가 처리되도록 설정.- 이를 통해 메시지 손실 가능성을 줄이고 강한 내구성(durability) 보장.
- 예외로 인한 소비 실패 메시지들의 누락 방지를 위해 Custom ErrorHandler 구현, 예외 상황 세부적으로 관리.
- 실패한 메시지를 별도의 Dead Letter Topic(DLT) 에 저장하도록 구성하여 이후 관리자나 별도 프로세스를 통해 문제 추적 및 재처리 가능하도록 설정.
- 실시간 문제 모니터링과 안정성 유지를 위해 Slack API 연동, 예외 케이스 발생 시 관리자 채널로 알림 발송하도록 설정.
- Kafka Topic의 파티션 개수를 입찰 요청량과 소비자의 처리 속도에 맞춰 적절히 설정.
- Consumer 인스턴스와 병렬 처리를 고려하여 계산, 이를 통해 로드 밸런싱을 극대화하고 메시지 처리 지연을 최소화.















