시스템 디자인사례 연구 / 트랜잭션 / Hotel Reservation학습 로드맵
CASE STUDY · ADVANCED읽기 26분검토일 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 TTL 10분 설계 가정
최종 선택

날짜 구간 원자 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
호텔 예약 시스템 아키텍처사용자는 검색과 견적을 거쳐 예약 API에 홀드를 요청한다. 예약 API는 날짜별 재고와 홀드 레코드를 원자적으로 갱신한다. 결제 의도와 PSP 웹훅은 상태 전이 서비스를 지나 확정 예약과 조정 큐로 이어진다.Search / Quotecache · price versionReservation APIidempotency · holdInventory dayversion · conditional writePayment / PSPintent · signed webhookHold recordexpiry · quote snapshotStateinboxReconcileexception queue검색 cache는 후보를 빠르게 하지만, hold·confirm은 날짜별 정본 불변식을 다시 검사한다.
정합성 경계: 어떤 구현이든 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을 기다립니다.

4확정·회수

검증된 성공만 held→confirmed로 옮기고, 만료·실패·취소는 수량을 release합니다.

04 · TRADEOFFS

경합 제어의 선택은 재고 불변식을 대체하지 않는다

# 대안 비교

락, 버전 조건, queue, 다지역 writer 중 무엇을 선택해도 최종 판매 수량의 조건부 판정은 필요합니다. 특히 여러 숙박 날짜에 걸친 hold는 단일 key의 간단한 CAS보다 더 큰 원자성 범위를 요구합니다.

선택강점주의점권장 판단
낙관적 version 조건짧은 경합에서 lock wait가 작음인기 날짜 retry·thundering herd일반 inventory row의 기본 후보
정렬된 row lock다중 날짜 transaction을 이해하기 쉬움deadlock·긴 transaction 위험숙박일 상한이 작고 DB가 정본일 때
per-key queuehot hotel/date의 burst를 흡수queue 장애·대기 UX·처리 지연최종 조건부 write 앞의 완충 계층
다중 region writer지역 지연·가용성 개선concurrent sell의 oversell 조정명시적 buffer·보상 체계가 있을 때
hold 없이 결제 후 확정단계와 TTL이 적음결제 성공 후 객실 없음희소 객실에는 사용하지 않음
05 · FAILURE MODES

초과 판매, 유령 홀드, 알 수 없는 결제를 각각 복구한다

# 장애 9가지
동시 hold 경합

같은 날짜·객실 유형에 여러 checkout이 몰리면 낡은 count를 읽은 요청이 함께 성공할 수 있습니다.

대응 · commit 시 조건부 갱신, 정렬 lock, 날짜별 불변식 검사로 sold-out을 결정합니다.
재시도·중복 클릭

네트워크 재전송이나 두 탭이 동일 예약을 여러 hold로 만들 수 있습니다.

대응 · idempotency key와 request fingerprint로 최초 hold·응답을 재사용하고 mismatch는 거절합니다.
?PSP timeout

고객에게는 응답이 없지만 PSP에서 실제 승인됐을 수 있습니다.

대응 · 신규 결제를 만들지 않고 payment ID 조회·서명된 webhook으로 unknown 상태를 해소합니다.
webhook 재전송·역순

늦은 실패 이벤트가 이미 confirmed인 예약을 덮어쓰거나 확정이 반복될 수 있습니다.

대응 · event inbox dedupe, signature 검증, 단조 상태 전이와 payment reference unique 제약을 둡니다.
expiry worker 지연

실패한 checkout의 객실이 TTL 이후에도 held로 남아 판매 가능 수량을 막습니다.

대응 · scan·retry와 compare-and-set release, expired-hold age alarm으로 회수합니다.
결제 성공·만료 경합

만료 처리와 늦은 payment success가 같은 hold를 반대 상태로 바꾸려 합니다.

대응 · 상태 우선순위·외부 조회·운영 exception queue로 단일 결과를 결정합니다.
DB failover/부분 처리

inventory commit은 됐지만 notification이나 분석 이벤트만 누락될 수 있습니다.

대응 · transaction rollback과 outbox replay를 분리하고 정본 version으로 재생성합니다.
검색 cache stale

객실이 보이지만 hold 시점에는 이미 매진되어 고객 혼란이 생깁니다.

대응 · hold의 정본 재검사를 진실로 두고 cache age·quote mismatch를 관측합니다.
!수동 재고 변경

운영자 변경이 audit 없이 들어가면 음수 재고와 보상 책임을 설명할 수 없습니다.

대응 · 역할 분리, 승인 ticket, append-only adjustment와 inventory reconciliation을 사용합니다.
06 · OPERATIONS

PII, 정합성 지표, 결제 비용을 판매 흐름과 함께 운영한다

# 운영
보안·개인정보

투숙객 PII는 inventory와 분리하고 최소 권한·암호화·audit·삭제 보존 정책을 적용합니다. 카드 원문은 저장하지 않으며, webhook은 secret·서명·timestamp를 검증합니다.

PII / inventory / payment scope 분리
관측 가능성

날짜별 불변식, conditional failure, lock wait, hold→confirm, expiry lag, unknown payment age, webhook duplicate, outbox lag를 hotel·room type·day bucket으로 관측합니다.

held + confirmed ≤ sellable
비용 모델

달력 재고·감사·백업, hotspot retry/queue, cache purge, PSP 승인·조회·환불, reconciliation와 고객 지원이 함께 비용을 만듭니다. timeout 재승인은 비용 절감책이 아닙니다.

storage + contention + PSP + ops
면접 모드 · 추가 질문07:00
“프로모션 시작 1분에 특정 호텔의 토요일 Deluxe King 1개에 수천 명이 결제를 시도합니다. 초과 판매 없이 hold, 결제 timeout, 만료, webhook 재전송, hot key 처리와 운영자 조정을 어떤 순서로 설명하시겠습니까?”
날짜별 불변식정렬 lock / conditional writeidempotent holdpayment unknownexpiry + reconcile
답변 구조 보기 5단계
  1. 검색 결과와 최종 판매 재고를 분리하고 날짜별 total = available + held + confirmed를 정본으로 둔다.
  2. 숙박 구간 전체를 정렬 잠금 또는 조건부 쓰기로 한 번에 hold한다.
  3. 멱등 키와 quote version으로 재시도가 새 hold·payment intent를 만들지 않게 한다.
  4. PSP timeout은 payment_unknown으로 보존하고 조회·webhook·reconciliation으로 확정한다.
  5. expiry와 결제 성공은 state version CAS로 한쪽만 재고를 이동하게 한다. 이번 선택: 짧은 원자 hold로 재고를 보호하고 외부 결제의 불명확한 결과를 별도 상태로 조정한다. 깨지는 신호: 날짜별 재고 합이 달라지거나 같은 hold에서 release와 confirmed가 모두 성공한다. 다음 검증: 마지막 객실에 동시 요청·PSP timeout·지연 webhook을 주입해 oversell 0과 unknown 해소 시간을 측정한다.
INTERVIEW ↔ PRACTICE · 면접과 실무의 차이

날짜별 재고 불변식을 설명하고 payment unknown race를 운영하기

INTERVIEW · 면접

검색 cache가 아니라 날짜 구간 hold가 oversell을 막는 경계라고 답합니다.

  • total=available+held+confirmed 불변식을 제시합니다.
  • 구간 전체 hold와 멱등 payment intent를 설명합니다.
  • expiry와 webhook의 version CAS를 답합니다.
PRACTICE · 실무

마지막 객실에서 timeout과 지연 webhook을 동시에 재현합니다.

  • date별 lock wait·hold age·inventory sum을 관측합니다.
  • release와 confirmed가 함께 성공하지 않는지 검사합니다.
  • PSP 명세와 hold·예약 원장을 복구 뒤 대사합니다.
SOURCES

공식·1차 출처와 설계 가정

LEARNING ROADMAP다음 시스템 디자인 주제 보기
EDITORIAL NOTES

작성·검토·참고 자료

콘텐츠 원칙
이 문서는 독립적으로 재작성한 한국어 학습 자료입니다. 사실과 학습용 설계 가정을 구분합니다.
작성·기술 검수
프로젝트 운영자가 직접 작성·기술 검토했습니다. 주제별 공식·1차 자료와 명시적인 설계 가정을 확인하며, 독립적인 제3자 검수가 아닙니다. 경력 범위와 검수 원칙 보기
최종 검토
예상 학습 시간
26분

사실 오류·출처 정정은 문의·정정 페이지로 알려 주세요.