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/Database
서론앞서 트랜잭션과 격리 수준, DB가 보장하는 동시성 제어를 정리해 보았다. 이번에는 실제 주문 로직에 어떻게 적용했는지를 정리해보고자 한다. 이번 주차 구현에서는 DB 레벨의 락 전략 학습이 목표였기 때문에 Redis와 같은 분산락은 고려하지 않고 낙관적 락과 비관적 락을 먼저 도입해 보기로 했다. 왜 락 전략이 필요하지?InnoDB의 MVCC와 Next-Key Lock 덕분에 기본적인 읽기 일관성이나 Phantom Read 방지는 잘 되지만 도메인 규칙까지 완전히 보장하기에는 부족하다고 느꼈다. 따라서 락 전략을 추가해 보기로 했다. 낙관적 락 vs 비관적 락두 방식을 간단하게 비교하면 아래와 같다.구분낙관적 락 (Optimistic Lock)비관적 락 (Pessimistic Lock)방식버전 ..
MySQL InnoDB와 트랜잭션
·
Study/Database
TL;DRInnoDB는 MVCC + Undo Log로 스냅샷 읽기를 제공하고, Next-Key Lock으로 신규 레코드 삽입까지 차단하여 Repeatable Read에서도 Phantom Read를 방지한다. 서론앞서 트랜잭션과 격리 수준을 살펴보면서 MySQL의 InnoDB의 기본 격리 수준이 Repeatable Read이고, 대부분의 동시성 문제를 방지할 수 있다고 확인했다. 특히 일반적으로 Repeatable Read에서 발생할 수 있는 Phantom Read 현상도 InnoDB에서는 방지된다고 한다. 어떻게 InnoDB는 내부적으로 그런 격리성을 보장하는 걸까? 그리고 다른 DB와는 어떻게 다를까? MVCC(Multi Version Concurrency Control)MySQL 이라고 해서 항상..
트랜잭션과 격리 수준
·
Study/Database
TL;DR트랜잭션은 작업을 모두 성공하거나 모두 실패시키는 작업 단위이며, ACID 속성으로 무결성을 보장한다.격리성(Isolation)은 동시에 실행되는 트랜잭션이 서로 간섭하지 않도록 하는 성질로 격리 수준에 따라 정합성과 성능이 달라진다.MySQL InnoDB는 기본적으로 `Repeatable Read` 를 사용해 대부분의 정합성 문제를 막지만, 경합이 심한 자원은 추가 락이나 더 높은 격리 수준이 필요할 수 있다. 서론이번 프로젝트에서 주문 로직을 구현하던 중 멱등성에 대해 고민하다가 예상치 못한 동시성 문제를 마주하게 되었다.- 사용자가 발급받은 쿠폰은 단 한 번만 사용할 수 있어야한다.- 재고 차감은 동시에 여러 사용자가 주문하더라도 일관성을 유지해야한다.애플리케이션 로직으로 최대한 방어하더..