포스트

[TIL] 경매 서비스 Outbox 패턴 도입

For the English version of this post, see here.
[TIL] 경매 서비스 Outbox 패턴 도입

가장 큰 그림 — 우리가 만드는 것

농수산물을 실시간 경매로 파는 서비스이다. 판매자가 ‘완도 전복 5kg’을 올리고, 정해진 시간에 사람들이 실시간으로 입찰 경쟁을 하닥, 가장 높은 가격을 부른 사람이 낙찰받아 결제하는 것.

MSA — 왜 서비스를 여러 개로 나누는지

하나의 거대한 프로그램으로 만들 수 있지만, 기능별로 작은 서비스 여러 개로 나눴다.

왜냐면 — 한 덩어리면 한 군데가 고장나면 다 멈추고, 6명이 같은 코드를 건드려 충돌나는 것을 방지하기 위해, 서비스를 나누어서 각자 자기 서비스만 맡아 따로 개발/배포할 수 있도록 한다.

이렇게 구현할 때의 단점은 — 서비스끼리 데이터를 직접 못 본다는 것이다. 경매 서비스는 결제 서비스의 DB를 직접 들여다볼 수 없다. 그래서 서로 ‘대화’하는 방법이 필요한데 그게 다음 개념이다.

서비스끼리 대화하는 두 가지 방법

방법 A — 직접 물어보기 (FeignClient, 동기)

‘지금 당장 답이 필요해’ 할 때 사용한다. 경매 서비스가 입찰 서비스한테 ‘이 경매 최고가가 얼마야?’ 하고 전화를 거는 것과 같다. 답이 올 떄까지 기다리는데, 우리 설계에서는 이걸 폴백(비상용)으로만 사용하기로 하였다.

방법 B — 게시판에 글 남기기 (Kafka, 비동기)

‘알려는 주는데 답을 기다릴 필요는 없어’ 할 때 사용한다. 경매 서비스가 ‘123번 경매 낙찰됐어요! 낙찰자는 홍길동!’이라는 이벤트(쪽지)를 Kafka라는 게시판에 붙인다. 그러면 그게 필요한 서비스들(주문, 알림)이 각자 와서 읽고 알아서 처리한다.

Kafka가 좋은 점 — 글을 게시판에 남겨두니까, 받는 서비스가 잠깐 죽어 있어도 살아나면 그 글을 읽을 수 있다. 전화(A)는 상대가 안 받으면 그냥 끊기지만, 게시판(B)은 글이 남아있다.

경매 서비스에서는 주로 ‘낙찰됐어요’ (AUCTION_WON), ‘유찰됐어요’ (AUCTION_FAILED)를 게시판에 올리는 게 핵심 역할이다.

경매 서비스가 실제로 하는 일 — 상태머신

경매 한 건은 상태가 차례로 변한다. 이걸 상태머신이라고 부르며 신호등이 빨강 → 초록 → 노란 순으로만 바뀌듯, 경매도 정해진 순서로만 바뀌어야 한다.

  • READY: 경매방 만들어짐, 아직 시작 전

  • PROGRESS: 경매 진행 중, 입찰 받는 중

  • RESULT_PENDING: 시간 끝남, 결과 정리하는 중

  • WON: 낙찰자 정해짐, 결제 기다리는 중

  • SUCCESS: 결제까지 완료, 끝

  • FAIL: 아무도 입찰 안 함(유찰) 또는 낙찰자가 돈 안 냄

경매 서비스의 핵심 일은 — 이 상태를 시간에 맞춰 자동으로 넘기는 것이다. ‘시작 시간 됐으니 READY → PROGRESS’, ‘끝날 시간 됐으니 정리하자’ 이런 걸 사람이 아니라 프로그램이 자동으로 한다.

스케줄러 — 시간 맞춰 자동으로 일하는 알람시계

경매는 ‘오후 2시 시작, 3시 마감’처럼 시간이 정해져있다. 근데 누가 2시 정각에 ‘시작’ 버튼을 눌러줄 순 없다. 따라서 스케줄러라는 게 자동 알람시계 역할을 한다. ‘2시 되면 이 경매 시작 시켜’, ‘3시 되면 마감해’ 하고 미리 등록해두면, 그 시간에 스케줄러가 알아서 깨어나 상태를 바꿔준다.

까다로운 문제 세 가지

문제 1 — 서버가 여러 대면 알람이 중복으로 울린다. (→ ShedLock)

손님이 몰리면 경매 서버를 2~3대로 늘린다. 근데 그러면 알람시계(스케줄러)도 2~3개가 된다. 3시 마감 때 3대가 동시에 ‘마감’하고 깨어나서, 한 경매를 세 번 마감 처리하는 사고가 난다. 그래서 ‘이번 마감은 내가 할게’ 하고 깃발을 먼저 잡은 한 대만 일하게 막는 장치가 ShedLock이다. (분산 락)

문제 2 — 마감 시각이 자꾸 바뀌어요 (→ Anti-Sniping)

경매 끝나기 직전에 누가 슬쩍 입찰하고 가버리면, 다른 사람은 대응할 시간이 없어 불공평하다. 그래서 끝나기 30초 안에 입찰이 들어오면 마감을 1분 더 늘린다. 단, 이 ‘시간 늘리기’는 입찰 서비스가 하고, 경매 서비스에서는 ‘늘어났대’하는 연락을 받아 알람시계를 다시 맞추는 역할을 한다.

문제 3 — 상태는 바꿨는데 게시판 글이 안 올라가면? (→ Outbox)

‘낙찰됐어요’라고 DB에 상태를 WON으로 바꾸고, Kafka 게시판에 글을 올려야 하는데 — DB는 됐는데 게시판 올리다 장애가 나면? 경매는 낙찰됐는데 주문 서비스는 그걸 모르는 사고가 난다. 그래서 상태 변경과 ‘올릴 글’을 한 묶음으로 DB에 같이 저장해두고, 별도 직원(Relay)이 그 글을 꺼내 게시판에 확실히 올려주는 방식이 Outbox이다. ‘둘 다 되거나, 둘 다 안 되거나’를 보장하는 것이다.

전체를 한 문장으로

경매 서비스는 — 경매방을 만들고(CRUD), 시간 맞춰 자동으로 경매를 열고 닫고(스케줄러), 낙찰자를 정해서(상태머신), ‘낙찰됐어요’를 게시판에 안전하게 올리는(Kafka + Outbox) 일을 한다. 그 과정에서 서버 여러 대 충돌(ShedLock), 막판 입찰 연장(Anti-Sniping) 같은 까다로운 상황을 처리한다.

댓글

궁금한 점, 피드백, 오류 제보를 남겨 주세요.