트래픽이 많은 상황에서 사용자 응답 개선과 한정된 리소스를 효율적으로 사용하는 방법을 찾기 위한 이커머스 프로젝트
기술
- Spring Boot, Spring Data JPA, Security
- Spring Cloud Gateway, Eureka
- Kafka, Kafka Streams
- MySQL, MongoDB
- Redis, Redisson, Redis stream
- OpenFeign
- Resilience4J CircuitBreaker + Retry
기능
- 회원 관련 API
- JwtAuthenticationFilter 및 UserPrincipal로 사용자 인증
- 주문 관련 API
- 상품 관련 API
- 쿠폰 관련 API
- 이슈 발생
- 마이크로서비스가 API Gateway 역으로 호출 (내부망 → DMZ 호출)
- API Gateway 역할 모호
- 경로 증가 → 응답 속도 지연
- 해결
- 마이크로서비스 간 직접 통신 (OpenFeign, Kafka 활용)
- Service Discovery(Eureka)를 통해 다른 마이크로서비스 주소 조회
- 성과
- API Gateway는 클라이언트 요청을 적절한 서비스로 전달하는 역할만 수행
- 포스팅 DMZ 영역의 API Gateway와 내부 Microservices 분리
- 이슈 발생
- 특정 서비스가 다른 서비스의 도메인까지 고려해야 하는 문제 발생
- 해결
서비스 진입점인 API Gateway에서API 조합해 응답(파생 데이터) 생성- Webflux 구현으로 유지보수 힘듦 → API composer 서비스로 이동
- 응답 데이터 Empty
Mono발생 시 중요도가 낮은 데이터는 제외하고 응답
- 새로운 문제 발생
- 요청마다 여러 서비스 호출(네트워크 비용 증가) 및 인메모리 조인 발생 → CQRS 패턴으로 해결
- 포스팅 API Composition Pattern
- 이슈 발생
- API 조합 시 여러 서비스 호출로 네트워크 비용 증가
- 해결
API GatewayAPI composer에서 생성한 파생 데이터 → MongoDB에 캐싱- 원천 데이터 변경 시 기능에 따라 파생 데이터 수정 or 제거
- 성과
- 요청마다 인메모리 조인 발생하지 않음
- 캐싱된 데이터 사용 → 네트워크 비용 절감
- 포스팅 Command and Query Responsibility Segregation (CQRS) pattern
- 이슈 발생
- 한정된 DB 커넥션을 요청마다 사용
- 해결
- 카프카 스트림즈 윈도우 합계 토폴로지 설계
- 여러 요청의 집계 결과를 DB 일괄 반영해 DB I/O 최소화
- Tumbling Window 사용 → 이벤트 중복 집계 방지
- Suppress operator 사용 → window 최종 결과만 emit
- 새로운 문제 발생
- drop 되는 이벤트 발생 → 정합성 깨짐 (적용 실패)
- 포스팅 Kafka Streams Aggregations - Window Results 컨트롤하기
- 이슈 발생
- DB에 insert 되지 못한 요청의 결과 이벤트가 먼저 도착 → DB 업데이트 불가
- 원인
- DB와 Kafka 간 처리 속도 차이
- 해결
- Transactional Outbox 패턴 적용
- DB insert 된 데이터만 Kafka Event 발생
- 새로운 문제 발생
- DB insert 작업과 Kafka Event 발행 작업이 직렬화 되어 TPS 저하 → KStream-KTable Join 해결 시도
- 포스팅 Transactional Outbox Pattern으로 선형성 보장하기
- 이슈 발생
- Transactional Outbox 패턴 적용 시 DB insert 작업과 Kafka Event 발행 작업 직렬화 → TPS 저하
- 해결
- KStream-KTable Join으로 최종 결과를 DB insert (DB I/O 감소)
- 성과
- Transactional Outbox 패턴 대비 처리 속도 83% 개선 (10.2s → 1.7s)
- 새로운 문제 발생
- 데이터 실시간 접근 어려움
- 내부 상태 관리 필요 → Tombstone 레코드 설정해 해결
- 포스팅 KStream-KTable Join 적용 실패기 — 성능 83% 개선
- 이슈 발생
- Redis 트랜잭션 내 여러 명령어 각각 Redis 서버로 전송 → 네트워크 비용 증가
- 원인
- Redis 트랜잭션 구현 시 SessionCallback 사용
- 해결
- Lua script로 여러 작업 명령어 일괄 전송 및 원자적 처리 → 네트워크 비용 절감
- 성과
- SessionCallback 대비 처리 속도 82% 개선 (2.73s -> 0.495s)
- 포스팅 쿠폰 발급을 위한 Redis Streams + Lua Script 적용기
- 이슈 발생
- DB 락 획득 과정에서 병목 현상 발생
- 원인
- DB 락 획득을 위해 DB 커넥션 필요
- 다른 세션이 먼저 DB 락 획득한 경우, DB 커넥션 점유한 상태에서 DB 락 해제 대기
- 해결
- Redisson(Distributed Lock) 획득한 트랜잭션만 DB 접근
- 트랜잭션보다 Redisson Lease time 먼저 종료되는 상황 대비 → Optimistic Lock 추가 사용해 데이터 정합성 유지
- 성과
- DB 부하 감소
- 이슈 발생
- 이미 다운된, 응답 지연이 발생하는 서비스 재호출 → 응답 지연 가능성 높음
- 해결
- Resilience4J CircuitBreaker 모듈 추가해 해당 API 상태 확인 → API Gateway 설정, OpenFeign 설정
- Retry 모듈에 Exponential Backoff and Jitter 전략 설정 (재시도로 인한 네트워크 혼잡 방지)
- 포스팅