Skip to content

f-lab-edu/Whiskey

Repository files navigation

🍸Whiskey(Whisky + Key)

위스키 애호가를 위한 리뷰, 구매 플랫폼

  • 다양한 검색 조건으로 위스키 탐색
  • 평점과 리뷰 기반 커뮤니티
  • 안전한 결제 시스템

🛠️기술스택

  • JAVA 21
  • Spring Boot, Spring Security
  • JPA
  • Docker
  • Gradle

프로젝트 목표

  • 객체지향 설계원칙을 적용하여 유지보수에 용이한 코드 구현
  • 서버 구축과 CI/CD 자동화를 통한 인프라와 배포 전반에 대한 이해
  • 트레이드 오프를 고려한 기술 선택

아키텍처 구조도

whiskey/
├── module-api (Controller, Security)
├── module-domain (Entity, Service, Repository)
└── module-common (API Response)

주요기능

  • 위스키 검색 & 필터링 (가격대, 도수, 지역 등)
  • 평점 & 리뷰 시스템
  • 결제 & 주문 관리
  • JWT 기반 인증

기술적 구현

Redis Sorted Set 기반 예약 만료 처리

  • 배경
    • 결제 예약 후 30분 이내 미결제 자동 취소 필요

    • 사용자가 예약만 하고 결제를 하지않으면 재고가 묶이는 문제 발생

    • 방안 A : DB 스케쥴러 (Spring @Scheduled + DB 폴링)
      ✅ 구현이 단순함
      ✅ 트랜잭션 처리 명확
      ❌ 설정한 시간마다 예약 테이블 스캔 -> DB 부하 발생!
      ❌ 최악의 경우, 시간 지연 발생 (1분으로 설정한 경우, 59초 지연 발생)

    • 방안 B : Quartz와 같은 외부 스케쥴러
      ✅ 정확한 시간에 실행 가능, 클러스터링 지원
      ✅ Cron 표현식으로 다양한 스케쥴링 지원
      ❌ 추가 의존성, 인프라 복잡도 증가
      ❌ 현재 단일 작업(예약 만료 체크)만 있으므로 오버 엔지니어링이라 판단

    • 방안 C : Redis Sorted Set (선택)
      ✅ 만료 시간을 score로 설정, O(logN) 조회
      ✅ DB 부하없는 빠른 처리 보장
      ❌ Redis 장애 발생 시 만료처리 중단

    • 최종결정

      • Redis Sorted Set에 만료시간을 score로 설정
      • 1분마다 현재 시간 이하 score를 가진 예약 조회 후 취소 처리
      • Redis 장애 대비 DB에도 만료시간 저장 (최악의 경우, DB 폴링으로 복구 가능)
    • 결과

      • DB 부하없이 안정적인 만료 처리
      • 평균 처리 지연 < 1분
      • Redis 메모리 사용량 : 예약 1만건 기준 ~2MB

이벤트 기반 평점 집계

  • 배경
    • 리뷰 작성시 위스키 평점을 실시간 업데이트

    • 평점은 자주 조회되므로 Redis 캐싱

    • 리뷰 저장과 Redis 업데이트를 같은 트랜잭션에서 처리하면?

      • Redis 실패시 전체 롤백? (사용자는 리뷰만 작성했는데 실패)
      • Redis 성공했는데 DB 롤백? (데이터 불일치)
    • 방안 A : 동기처리 (@Transactional 내부)
      ❌ Redis 장애시 리뷰 저장 실패
      ❌ 외부 시스템(Redis)이 트랜잭션에 영향을 줌

    • 방안 B : 비동기 처리 (@Async)
      ✅ 빠른 평점 반영
      ❌ 트랜잭션 커밋 전에 실행될 가능성이 있음
      ❌ DB 롤백되어도 평점은 계산됨 (데이터 불일치)

    • 방안 C : @TransactionalEventListener (선택)
      ✅ DB 커밋이 성공했을 때만 캐시 업데이트
      ✅ Redis 실패해도 리뷰 저장은 성공
      ❌ Redis 업데이트 실패하면 평점 반영 안됨

    • 최종결정

      • 리뷰 저장 트랜잭션과 Redis 업데이트 분리
      • 트랜잭션 커밋 후 이벤트 발행(AFTER_COMMIT)
      • Redis 실패시 다음 조회때 DB에서 재계산하여 캐시 갱신
    • 결과

      • 리뷰 작성 실패율 0% (Redis 장애와 무관)
      • 최악의 경우 평점 반영 지연 (사용자 경험에는 문제없음)

결제 API 트랜잭션 분리 + 재시도

  • 배경
    • Toss Payments API 호출이 네트워크 상태에 따라 지연됨

    • 주문 생성 트랜잭션 내부에서 결제 API 호출

    • 결제 API 대기 중 DB 커넥션 홀딩 -> 커넥션 풀 고갈 위험

    • 방안 A : 트랜잭션 타임아웃 설정
      ❌ 여전히 커넥션 점유 문제 발생

    • 방안 B : 외부 API를 트랜잭션 밖으로 분리 (선택)
      ✅ DB 커넥션 빠르게 반환
      ✅ 결제를 실패하면 보상 트랜잭션으로 주문 취소
      ❌ 구현 복잡도 증가 (3단계 과정 진행)

    • 최종결정

      • 주문 생성 -> 결제 API 호출 -> 주문 확정(재고 처리) 3단계로 분리
      • 결제 API 타임아웃 발생하면 Spring Retry로 최대 3회 재시도
      • 재시도 모두 실패시 주문을 실패상태로 확정하고 관리자에게 전달
    • 결과

      • DB 커넥션 점유시간 감소
      • 일시적 네트워크 오류 대응 가능

About

위스키 추천 서비스

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •