Spring Batch로 랭킹 집계 확장하기
·
Study/Architecture
저번에 만든 일간 랭킹 기능에 주간, 월간 랭킹을 추가하게 되었다. 일간 랭킹까지만 있을 때는 Kafka Consumer + Redis ZSET으로 충분했다.실시간 이벤트를 소비하고 점수를 바로 올리는 구조라 빠르고 직관적이었다.하지만 문제는 주간, 월간 랭킹을 일간 랭킹과 동일하게 가져갈 순 없었다는 것이다.Redis에만 데이터를 적재하려니 TTL을 길게 가져가야 해서 그만큼 비용과 관리 부담이 커졌고, 결국 영속성과 안정성이 보장하는 DB에 데이터를 저장하기로 했다. 이미 product_metrics 테이블에 일간 데이터가 쌓이고 있었기 때문에 이를 기반으로 주간/월간 집계를 만들기로 했다.그렇다면 그냥 호출 시마다 SUM / GROUP BY로 계산하면 되지 않을까?하지만 그렇게 하면 매번 대량 데이..
실시간 상품 랭킹? Redis ZSET만 쓰면 끝인 줄 알았는데요
·
Study/Architecture
이커머스 프로젝트를 진행하면서 실시간 상품 랭킹을 설계하게 되었다.사실 어지간한 이커머스에는 다 있는 기능이라 쉽게 끝날 거라 생각했지만… 역시나 고민할 부분이 정말 많았다. 왜 실시간 랭킹인가?사용자가 상품을 조회하고, 좋아요를 누르고, 구매한다.이때마다 바로바로 갱신되는 랭킹을 보여주고 싶었다.기존에는 단순히 DB 테이블에 데이터를 쌓고 조회하는 방식이었다.하지만 트래픽이 늘어난다고 생각하면 단순 집계 쿼리는 곧 심각한 병목으로 이어질 것 같았다.따라서 실시간 반영 + 빠른 조회를 만족시킬 수 있는 Redis를 선택했다. Redis ZSETRedis의 Sorted Set (ZSET) 은 score 기반으로 자동 정렬되는 자료구조다.랭킹도 점수를 기반으로 정렬하기 때문에 아주 적합하다고 생각했다..
내부 이벤트를 넘어 Kafka 기반 이벤트 파이프라인으로
·
Study/Architecture
내부 이벤트의 한계지난주에는 이벤트를 통해 결합도를 줄이는 구조를 만들었다. 주문은 주문만 알도록 하고, 결제나 쿠폰 사용 같은 후속 작업은 이벤트 핸들러가 이어받도록 분리했다.하지만 애플리케이션 내부 이벤트만으로는 해결할 수 없는 문제들이 남아있었다.신뢰성 부족: 예외가 나면 단순히 로그만 남고 이벤트 자체가 유실될 수 있음확장성 제약: 하나의 애플리케이션 안에서만 소비할 수 있어 별도의 서비스가 이벤트를 받을 수 없음결국 서비스 경계를 넘어 전달할 수 있는 이벤트 파이프라인이 필요했다. 외부 이벤트 브로커가 만족해야 하는 요구사항을 정리해 보면 다음과 같았다.At-Least-Once 전달 보장순서 보장재시도와 DLQ소비자 그룹 확장성이 모든 요구사항을 충족해주는 도구가 바로 Kafka였고, 그래서 이..
주문과 결제, 어디까지 한 트랜잭션으로 묶어야 할까?
·
Study/Architecture
저번 주차에 장애 대응 시스템을 구축하면서 분리했던 주문과 결제 API를 다시 하나로 합쳤다.PG가 아직 시뮬레이터이기 때문에 별도의 인증/인가 로직이 존재하지 않았고, 실제 유저가 주문을 하는 플로우를 생각해 봤을 때 주문과 결제를 하나의 API로 묶는 게 자연스럽다는 판단이 들었기 때문이다.그런데 막상 합치고 나니 여러 문제가 발생했다. 길어진 트랜잭션합친 구조는 대략 이런 모습이었다.한눈에 봐도 로직이 길어졌고, 주문이 너무 많은 책임을 떠안게 됐다.주문 { 주문 생성 쿠폰 조회 & 사용 재고 조회 & 차감 포인트 조회 & 차감 결제 요청 주문 정보 데이터 플랫폼 전송}결제 요청 { 주문 검증 결제 생성 PG 결제 요청 API 호출 결제 상태 변경 주문 상태 변경}결제 콜백 { 주문 검증 결제 ..
장애 대응 시스템 구축하기
·
Study/Architecture
이번 주에는 시뮬레이터지만 PG(Payment Gateway) 모듈을 결제 로직과 연동하는 작업을 진행했다.해당 시뮬레이터 중 결제요청 API의 스펙은 다음과 같다.요청 성공 확률: 60%요청 지연: 100ms ~ 500ms처리 지연: 1s ~ 5s처리 결과:성공 : 70%한도 초과 : 20%잘못된 카드 : 10%기존에는 사용자가 포인트를 충전하고 그 포인트로만 결제하는 구조였는데, 이제는 결제 시 포인트를 일부 사용하고 남은 금액은 PG를 통해 결제하는 방식으로 바꾸게 됐다.PG는 외부 시스템인만큼 통제할 수 없는 상황이 훨씬 많아져 고민할 지점이 너무 많아졌다. 단순히 호출만 성공하면 끝나는 게 아니라, 네트워크 지연, 응답 누락, 외부 장애 같은 수많은 변수를 생각해야 했다. 가장 중요한 건 PG가..
캐시 구조 개선
·
Study/Architecture
지난번 진행한 읽기 성능 개선 작업에서는 캐시 구조를 다음과 같이 설계했었다.브랜드 목록: 상품 많은 순 상위 5% (50개 브랜드)상품 목록: 각 상위 브랜드 별 최신순 상품 상위 100개TTL은 브랜드 목록에 1일, 상품 목록에 1시간을 설정했다. 하지만 브랜드나 상품이 수정/삭제될 때 별도의 캐시 갱신 로직이 존재하지 않아, 데이터 갱신 시 캐시를 어떻게 관리할 것인가 하는 문제가 남아있었다. 이 문제를 방치한다면 데이터 정합성이 맞지 않게 되니까 수정이 필요했다. 기존 캐시 구조조회 로직은 다음과 같은 흐름으로 구성되어 있었다.검색 조건이 캐시 조건에 부합하는지 → 브랜드 목록 캐시가 존재하는지 → 상품 목록 캐시가 존재하는지 → 없다면 DB 조회구현 당시 각 캐시는 단순히 List 형태로 ..
읽기 성능 개선 보고서
·
Study/Architecture
테스트 개요테스트 시나리오대상 기능: 읽기 병목 가능성이 높은 상품 목록 조회 API목표: 현 상태 기준 p95 응답 시간과 처리량 측정조건:brandId ∈ {1..5}sort ∈ {LATEST, PRICE_ASC, LIKES_DESC}page ∈ [0, 49], size=20데이터 준비:유저: 100,000 건브랜드: 1,000 건상품: 1,000,000 건데이터 기준은 아래 참고더보기트랜잭션 계산상품 목록 조회 기준 (행사 미실시 월 가정, 숫자 라운딩 적용)아래 값은 테스트 설계용 가정치이며 실제 트래픽과 다를 수 있음.MAU 출처: https://v.daum.net/v/20241212165321851 DAU 계산 근거: https://sendbird.com/ko/blog/monthly-activ..
멱등성을 고려한 좋아요 기능 설계
·
Study/Architecture
TL;DR좋아요 기능 개발 시 중복 요청으로 인한 문제를 방지하기 위해 DB와 애플리케이션 로직으로 멱등성을 구현했다.하지만 여전히 동시성 문제가 남아있어 캐싱, 메시지 큐, 분산락 등의 도입을 고민 중이다. 좋아요 기능과 중복 요청 문제사이드 프로젝트에서 좋아요 기능을 개발하면서 중복 요청 문제를 고민하게 되었다. 사용자가 버튼을 여러 번 누르거나 네트워크가 불안정하다면 요청이 반복될 가능성이 있기 때문이다.현재 좋아요 도메인은 좋아요 등록 시 LIKE 테이블에 userId 와 productId 로 등록하고, 취소 시에는 deletedAt 컬럼으로 soft delete 하는 방식을 사용하고 있었다. 하지만 단순히 DB에 유니크 제약 조건만 설정하면 중복 요청 발생 시 DB에서 예외가 발생하고, 이를 애..
Validation, 어디에 어떻게 두어야 할까?
·
Study/Architecture
TL;DR각 계층별로 역할과 책임을 분리해 유효성 검사를 수행하는 것이 유지보수성과 테스트 측면에서 더 나을 것이라 판단했고 이에 따라 DTO는 입력 형식, Domain은 도메인 규칙, Service는 비즈니스 로직 기반의 검증을 담당하도록 구조를 조정했다. 왜 validation 위치를 고민하게 되었는가최근 사이드 프로젝트를 새로 시작하면서 TDD를 적극적으로 적용해보기로 했다.레이어드 아키텍처를 기반으로 테스트 작성이 편하도록 책임을 나누고 의존성을 최소화하는 구조를 설계했다.그러다 평소처럼 service 에 유효성 검사를 작성하던 중 문득 이런 생각이 들었다.이렇게 서비스에 마구잡이로 validation을 몰아넣는 게 맞을까? 도메인에서는 어떤 검증을 맡는 게 더 적절할까?테스트와 설계를 함께 고민..