예약 버튼 하나 뒤에는 날짜별 희소 재고, 동시에 몰리는 결제, 늦게 도착하는 웹훅이 있습니다. 검색 결과를 정본으로 믿지 않고, 짧은 홀드·원자적 재고 판정·멱등 결제 확정·조정을 연결해 초과 판매를 막습니다.
운영자 직접 작성·기술 검토·최종 검토 2026.08.29
재고·홀드결제 확정장애·조정면접 답변진도 저장
⌂30초 핵심 요약
날짜별 total = available + held + confirmed를 유지하며 숙박 구간 전체를 원자 hold합니다. PSP timeout은 실패가 아닌 payment unknown으로 남기고, 지연 webhook과 expiry가 같은 hold version CAS로 경쟁하게 합니다.
Invariantheld + confirmed ≤ sellable
Checkoutidempotent hold + confirm
Unknown paymentquery + webhook + reconcile
DESIGN DECISION · 설계 판단
마지막 객실에서 hold 만료와 결제 성공이 경쟁할 때 누가 재고를 갖는가?
예시 검색800만/day
설계 가정
예시 피크 홀드약 84 QPS
계산 결과
예시 hold TTL10분
설계 가정
최종 선택
날짜 구간 원자 hold + versioned expiry/payment reconciliation
숙박일 전체 재고를 정렬 잠금이나 조건부 쓰기로 한 번에 hold합니다. PSP timeout은 payment_unknown으로 유지하고 webhook 성공과 expiry가 같은 hold version CAS로 경쟁하게 합니다.
선택 이유
여러 숙박일의 부분 홀드와 oversell을 막습니다.
timeout 뒤 중복 승인 없이 같은 payment intent를 조회할 수 있습니다.
결제와 만료가 동시에 와도 재고 이동은 한 번만 성공합니다.
포기한 대안
hold 없이 결제 성공 뒤 재고 차감
동시 결제가 모두 성공한 뒤 객실 부족이 드러나 환불·보상과 재고 불변식 위반이 발생할 수 있습니다.
감수한 단점
hot hotel/date의 lock wait와 retry가 늘어납니다.
hold TTL과 payment unknown이 일시적으로 판매 가능 재고를 줄입니다.
검색 cache는 최종 재고보다 오래될 수 있습니다.
01 · REQUIREMENTS
희소 재고와 외부 결제를 같은 성공으로 착각하지 않는다
# 요구사항
“객실이 보였다”는 것은 검색 snapshot이고, “객실이 확보됐다”는 것은 정본 재고의 상태 변화입니다. 예약 서비스는 날짜 구간 재고와 금전 결과를 각각 검증한 뒤 사용자에게 하나의 예약 상태로 보여 줍니다.
R1날짜 구간 재고
체크인부터 체크아웃 전날까지 모든 inventory day가 판매 가능한지 원자적으로 판정합니다.
R2짧은 hold
결제 시간만큼 객실을 보류하고, 명시적 만료 release로 유령 홀드를 회수합니다.
R3한 번의 업무 효과
재시도·중복 클릭·웹훅 재전송에도 hold, 예약, 결제 확정이 각각 한 번만 적용됩니다.
R4감사·복구
상태·수량·payment reference·운영자 조정의 before/after를 삭제하지 않고 남깁니다.
02 · HIGH-LEVEL DESIGN
검색, 날짜 재고, 홀드, 결제, 조정을 분리한다
# 아키텍처
최종 hold는 cache가 아니라 inventory coordinator의 조건부 갱신에서 결정합니다. 결제의 동기 응답·조회·webhook은 같은 payment intent를 보강하는 입력이고, 예약 확정은 단조 상태 전이와 이벤트 inbox를 통과해야 합니다.
호텔 예약의 판매·확정·복구 경로 SVG DIAGRAM · inventory truth is versioned
정합성 경계: 어떤 구현이든 commit 시점의 조건은 held_count + confirmed_count < sellable_count여야 합니다. 여러 숙박 날짜에서 하나라도 실패하면 전체 hold는 실패합니다. 검색 결과의 “잔여 1개”는 판매 권한이 아닙니다.
03 · REQUEST FLOW
재고를 먼저 확보하고, 모르는 결제 결과는 기다려 확인한다
# 요청 흐름
1견적 검증
호텔 현지 날짜, 인원, rate plan, 가격 version과 요청 fingerprint를 확인합니다.
2원자 hold
날짜 행을 정렬 순서로 검사·차감하고 hold, expiry, outbox를 함께 커밋합니다.
3PSP intent
동일 hold의 payment intent를 재사용합니다. timeout에는 재승인 대신 조회·webhook을 기다립니다.
expiry와 결제 성공은 state version CAS로 한쪽만 재고를 이동하게 한다. 이번 선택: 짧은 원자 hold로 재고를 보호하고 외부 결제의 불명확한 결과를 별도 상태로 조정한다. 깨지는 신호: 날짜별 재고 합이 달라지거나 같은 hold에서 release와 confirmed가 모두 성공한다. 다음 검증: 마지막 객실에 동시 요청·PSP timeout·지연 webhook을 주입해 oversell 0과 unknown 해소 시간을 측정한다.