Loopers 12

"같은 주문이 두 번 결제됐습니다" 💸 - Kafka로 배운 분산 시스템의 잔혹한 현실

TL;DR :주문 이벤트가 다인스턴스에서 동시에 전송되며 중복 결제가 발생했다. 설정(acks=all, idempotence=true)만으로는 애플리케이션 레벨 경합을 막을 수 없었다.나는 Outbox + DB 원자적 선점 + 동기 전송(.get())으로 "처음부터 안전"을 선택했다.토픽은 max.in.flight=5, Consumer는 별도 테이블 기반 멱등성, DLT는 Table 우선으로 운영했다.결과적으로 유실/중복/순서 문제를 "설계로 산" 뒤, 성능 최적화를 진행할 수 있었다. 그날, 같은 주문이 두 번 결제되었다 😨[ERROR] Duplicate payment detectedpaymentId: 92134, orderId: 55120, amount: 39000Original: 2024-03-0..

Loopers 2025.09.05

🚨 “그냥 @EventListener면 끝?” — 이벤트, 언제·왜·어떻게 사용할 것인가 ⚙️

TL;DR“관심사 분리”용으로 가볍게 시작했다가, 도메인 사실을 안전하게 전파하고 추적 가능한 메타데이터를 더해 운영/관측성까지 챙기는 구조로 진화.핵심 선택은 AFTER_COMMIT + @Async + Envelope + Bridge + Policy/Sender 분리.이번 과제에서 데이터 플랫폼 싱크 = 알림톡이었고, 도메인 이벤트(PaymentCaptured)가 **봉투(Envelope)**로 표준화되어 알림 정책 → 전송자를 통해 **단일 싱크(알림톡)**로 흘러가게 설계했다. 1) 처음 생각: “@EventListener 붙이면 관심사 분리 완료”처음엔 정말 이렇게 시작했다. 도메인 로직(주문/결제) 뒤에 알림/집계/로그를 리스너로 뽑아내면 끝이라 믿었음.[Controller/Service] →..

Loopers 2025.08.29

Resilience와 보상 트랜잭션: 장애에 대응하는 방법

이번 주는 외부 결제(PG) 연동 안정성을 주제로,Resilience4j로 장애를 제어 가능한 범위로 줄이고,보상 트랜잭션으로 외부 자원과 로컬 DB의 정합성을 맞추며,Spring Events로 도메인 사건을 분리하는 방식을 학습했다.1. Resilience4j — 회복탄력성을 위한 첫 번째 생명선Resilience4j란 무엇인가?자바 기반 회복탄력성(Resilience) 라이브러리외부 시스템 호출 실패나 지연을 서비스 전반으로 확산되지 않도록 제어하는 다양한 패턴 제공왜 필요한가?네트워크·외부 API는 언제든 장애가 발생할 수 있다.단순 try-catch는 개별 호출만 잡아줄 뿐, 서비스 전체 안정성은 지켜주지 못한다.핵심은 “장애를 없애는 게 아니라, 장애를 제어 가능한 형태로 축소하는 것”.주요 동..

Loopers 2025.08.24

PG가 터져도 우리 서비스는 멀쩡해야 한다 🔥

PG가 터져도 우리 서비스는 멀쩡해야 한다 🔥Resilience4j로 결제 시스템 장애 방어선 구축한 썰TL;DRPG 시뮬레이터 연동하다가 외부 시스템 장애로 전체 서비스 마비 경험. Resilience4j의 Circuit Breaker, Retry, Bulkhead, Timeout 패턴으로 방어선 구축하고, 이벤트 기반 보상 트랜잭션과 스케줄러 복구로 완전체 만든 실전기. "PG 하나 죽어도 주문은 계속 받아야 한다"🚨 사건의 발단: PG 하나가 터지니까 전체가 다운됐다이번 6주차 과제에서 PG 시뮬레이터를 붙이면서 처음으로 "외부 의존성의 무서움"을 체감했습니다.PG 시뮬레이터 스펙 (현실적으로 잔인함)📊 PG 시뮬레이터 장애 시나리오- 요청 성공 확률: 60% (40%는 그냥 실패)- 요청 지..

Loopers 2025.08.22

인덱스를 걸었는데… 더 느려졌다? 🤯

인덱스를 걸었는데… 더 느려졌다? 🤯10만 건 이후, 인덱스의 배신과 선택의 이유TL;DR10만 건을 넘기면 평균보다 p95·p99 tail latency가 먼저 무너진다.WHERE 컬럼만 인덱싱하면 정렬을 못 받아 filesort로 샌다.정렬 컬럼 + 타이브레이커(id) 를 포함한 전용 인덱스로 정렬을 인덱스로 해결해야 한다.좋아요순은 인덱스만으론 부족. 드라이빙 테이블을 product_likes로 전환해야 한다.1) 증상: 평균은 멀쩡한데 p95가 터진다데이터 규모: 약 10만 건문제 구간:deep 페이지네이션(OFFSET 수백~수천)브랜드 필터 + 정렬 조합현상: 평균은 괜찮아 보여도 p95·p99가 급격히 악화원인들은 겹친다: 정렬 미지원, 카디널리티, 옵티마이저 선택, OFFSET 자체 비용2..

Loopers 2025.08.15

락은 왜 느리고, MVCC는 무엇을 바꾸었나

1) 락은 왜 느려질까 → 그래서 MVCC가 나왔다락의 본질: “같은 리소스를 만지는 트랜잭션을 순서로 묶는다.”순서를 강제하면 안전은 올라가지만 대기(Blocking) 가 생긴다.읽기까지 줄 세우면: 쓰기 충돌이 많을수록 읽기도 연쇄적으로 대기 → TPS 하락, 지연 증가, 데드락 위험 상승.해결 철학: “읽기는 줄 세우지 말자.” → MVCC (Multi-Version Concurrency Control)같은 행의 여러 버전을 보관하고, 각 트랜잭션은 자기 시점의 스냅샷만 읽는다.→ 읽기는 락 없이 병렬, 쓰기끼리만 충돌 처리.2) MVCC, 도대체 뭔데 이렇게 빠르지?Undo Log + TRX_ID: InnoDB는 변경 전 값을 Undo Log에 보관하고, 각 레코드에 내부 트랜잭션 ID를 붙인다...

Loopers 2025.08.10

동시성 제어, 도메인 분리를 삼킨 괴물

도메인이 협력할 때 진짜 문제는 시작된다이번 Loopers 과제의 요구사항은 동시성 관리였다.사용자는 상품을 주문할 수 있어야 한다.주문 시 사용자의 포인트가 차감되고, 쿠폰을 사용하면 할인이 적용되며, 상품 재고도 줄어야 한다.단순한 흐름 같지만, 이걸 도메인 책임을 분리하면서 트랜잭션 정합성까지 보장하려면 이야기가 달라진다.1. 할인 정책은 주문(Order)의 책임이 아니다왜 이게 문제였는가?처음엔 Order 도메인에서 할인 금액 계산까지 전부 처리하고 있었다.public static Order create(UserId userId, List items, UserCoupon coupon) { validate(userId, items); OrderAmount originalAmount = O..

Loopers 2025.08.08

설계는 정답이 없다고 했지만, 그래도 너무 어렵잖아

🧭 시작은 단순했습니다Loopers 과제 Round 3에서는상품, 브랜드, 좋아요, 주문, 결제 도메인을 구현해야 했습니다.기능만 보면 단순한 CRUD처럼 보였지만,실제로는 도메인 간 협력, 구조 확장, 책임 분리 등설계 측면에서 많은 고민이 필요했습니다.📦 1. 상품 조회 – 정렬 기준이 늘어날수록 구조는 어떻게 바뀔까?처음엔 이렇게 생각했습니다:“상품 리스트를 가져오고, 브랜드/좋아요 정보를 붙이면 되지!”이 방식의 장점은 도메인 경계가 명확하다는 점이었습니다.Product는 ProductService에서Brand와 Like는 각각의 서비스에서 분리되어 처리하지만 정렬 기준이 다양해지면서 문제가 생겼습니다.예를 들어:좋아요 순 정렬브랜드 인기순 정렬주문/리뷰 기반 정렬 가능성기존 구조에서는 정렬을..

Loopers 2025.08.01

Loopers WIL – 2주차

1. 멱등성 – GET만 멱등하면 되는 게 아니었다처음엔 단순하게 생각했다.“GET은 멱등하고, POST는 멱등하지 않다.”하지만 좋아요 기능을 설계하면서 이 생각은 깨졌다.좋아요는 POST 요청이지만, 사용자가 100번을 눌러도 결과는 한 번만 반영되어야 한다.→ 이건 POST라도 비즈니스적으로는 멱등해야 한다.그제야 알게 됐다.HTTP 메서드의 특성만 따지는 게 아니라,**“이 요청이 시스템 상태를 얼마나 예측 가능하게 유지시켜주는가”**가 진짜 기준이라는 걸.이건 단순한 사용성 이슈를 넘어서 아래와 같은 시스템 설계로 이어진다:💥 동시성 제어🔁 재시도(Retry) 대응🔐 API 설계 시 신뢰성 확보결국 멱등성은 단순히 HTTP 개념이 아니라,서버의 신뢰성과 안정성을 지탱하는 설계 원칙이었다.?..

Loopers 2025.07.24

Loopers요구사항 정의서 없이 만드는 개발은 결국 돌고 돌아 제자리로 돌아온다

이번 주 루퍼스를 하며 가장 크게 느낀 건 이거였다.요구사항 정의서 없이 만드는 개발은 결국 돌고 돌아 제자리로 돌아온다.지금까지는 대부분 기획이 완료된 상태에서 기능만 구현해왔다.기획자가 기능 흐름을 정리해주면, 나는 필요한 객체를 떠올리고, DB 구조를 짠 다음 곧장 코딩에 들어갔다.그게 익숙했고, 딱히 문제라고 느끼지도 않았다.그런데 이번 과제는 달랐다.기획서 없이, 요구사항 정의부터 전부 직접 해야 하는 과제였다.🧱 전형적인 커머스니까 쉬울 줄 알았다주제는 커머스 서비스.로그인, 상품, 장바구니, 주문, 결제, 포인트 충전 등 익숙한 기능들이라처음엔 “쉬운 과제겠지” 싶었다. 그래서 초반엔 가볍게 접근했다.“로그인한 사용자만 접근 가능”, “재고 초과 시 주문 불가”, “없는 상품이면 에러” 같..

Loopers 2025.07.24