체크인부터 체크아웃 전날까지 모든 inventory day가 판매 가능한지 원자적으로 판정합니다.
호텔 예약
시스템 설계
예약 버튼 하나 뒤에는 날짜별 희소 재고, 동시에 몰리는 결제, 늦게 도착하는 웹훅이 있습니다. 검색 결과를 정본으로 믿지 않고, 짧은 홀드·원자적 재고 판정·멱등 결제 확정·조정을 연결해 초과 판매를 막습니다.
재고의 진실은 날짜별 inventory row에 둡니다. hold는 모든 숙박일을 원자적으로 검사해 짧게 차감하고, 결제 성공만 confirmed로 이동합니다. timeout·웹훅 재전송·만료 경합은 상태 전이, event inbox, payment 조회와 조정으로 풀며, 검색 cache는 최종 판매 권한이 아닙니다.
희소 재고와 외부 결제를 같은 성공으로 착각하지 않는다
“객실이 보였다”는 것은 검색 snapshot이고, “객실이 확보됐다”는 것은 정본 재고의 상태 변화입니다. 예약 서비스는 날짜 구간 재고와 금전 결과를 각각 검증한 뒤 사용자에게 하나의 예약 상태로 보여 줍니다.
결제 시간만큼 객실을 보류하고, 명시적 만료 release로 유령 홀드를 회수합니다.
재시도·중복 클릭·웹훅 재전송에도 hold, 예약, 결제 확정이 각각 한 번만 적용됩니다.
상태·수량·payment reference·운영자 조정의 before/after를 삭제하지 않고 남깁니다.
검색, 날짜 재고, 홀드, 결제, 조정을 분리한다
최종 hold는 cache가 아니라 inventory coordinator의 조건부 갱신에서 결정합니다. 결제의 동기 응답·조회·webhook은 같은 payment intent를 보강하는 입력이고, 예약 확정은 단조 상태 전이와 이벤트 inbox를 통과해야 합니다.
held_count + confirmed_count < sellable_count여야 합니다. 여러 숙박 날짜에서 하나라도 실패하면 전체 hold는 실패합니다. 검색 결과의 “잔여 1개”는 판매 권한이 아닙니다.재고를 먼저 확보하고, 모르는 결제 결과는 기다려 확인한다
호텔 현지 날짜, 인원, rate plan, 가격 version과 요청 fingerprint를 확인합니다.
날짜 행을 정렬 순서로 검사·차감하고 hold, expiry, outbox를 함께 커밋합니다.
동일 hold의 payment intent를 재사용합니다. timeout에는 재승인 대신 조회·webhook을 기다립니다.
검증된 성공만 held→confirmed로 옮기고, 만료·실패·취소는 수량을 release합니다.
경합 제어의 선택은 재고 불변식을 대체하지 않는다
락, 버전 조건, queue, 다지역 writer 중 무엇을 선택해도 최종 판매 수량의 조건부 판정은 필요합니다. 특히 여러 숙박 날짜에 걸친 hold는 단일 key의 간단한 CAS보다 더 큰 원자성 범위를 요구합니다.
| 선택 | 강점 | 주의점 | 권장 판단 |
|---|---|---|---|
| 낙관적 version 조건 | 짧은 경합에서 lock wait가 작음 | 인기 날짜 retry·thundering herd | 일반 inventory row의 기본 후보 |
| 정렬된 row lock | 다중 날짜 transaction을 이해하기 쉬움 | deadlock·긴 transaction 위험 | 숙박일 상한이 작고 DB가 정본일 때 |
| per-key queue | hot hotel/date의 burst를 흡수 | queue 장애·대기 UX·처리 지연 | 최종 조건부 write 앞의 완충 계층 |
| 다중 region writer | 지역 지연·가용성 개선 | concurrent sell의 oversell 조정 | 명시적 buffer·보상 체계가 있을 때 |
| hold 없이 결제 후 확정 | 단계와 TTL이 적음 | 결제 성공 후 객실 없음 | 희소 객실에는 사용하지 않음 |
초과 판매, 유령 홀드, 알 수 없는 결제를 각각 복구한다
같은 날짜·객실 유형에 여러 checkout이 몰리면 낡은 count를 읽은 요청이 함께 성공할 수 있습니다.
네트워크 재전송이나 두 탭이 동일 예약을 여러 hold로 만들 수 있습니다.
고객에게는 응답이 없지만 PSP에서 실제 승인됐을 수 있습니다.
늦은 실패 이벤트가 이미 confirmed인 예약을 덮어쓰거나 확정이 반복될 수 있습니다.
실패한 checkout의 객실이 TTL 이후에도 held로 남아 판매 가능 수량을 막습니다.
만료 처리와 늦은 payment success가 같은 hold를 반대 상태로 바꾸려 합니다.
inventory commit은 됐지만 notification이나 분석 이벤트만 누락될 수 있습니다.
객실이 보이지만 hold 시점에는 이미 매진되어 고객 혼란이 생깁니다.
운영자 변경이 audit 없이 들어가면 음수 재고와 보상 책임을 설명할 수 없습니다.
PII, 정합성 지표, 결제 비용을 판매 흐름과 함께 운영한다
투숙객 PII는 inventory와 분리하고 최소 권한·암호화·audit·삭제 보존 정책을 적용합니다. 카드 원문은 저장하지 않으며, webhook은 secret·서명·timestamp를 검증합니다.
날짜별 불변식, conditional failure, lock wait, hold→confirm, expiry lag, unknown payment age, webhook duplicate, outbox lag를 hotel·room type·day bucket으로 관측합니다.
달력 재고·감사·백업, hotspot retry/queue, cache purge, PSP 승인·조회·환불, reconciliation와 고객 지원이 함께 비용을 만듭니다. timeout 재승인은 비용 절감책이 아닙니다.
공식·1차 출처와 설계 가정
- Stripe API — Idempotent requests: POST 재시도의 멱등 키 동작과 보존 경계.
- Stripe Docs — Webhooks: webhook 서명 검증과 endpoint 처리 지침.
- PostgreSQL — Transaction Isolation: isolation과 concurrent transaction의 가시성·재시도 고려.
- PostgreSQL — Explicit Locking: row-level lock과 lock 순서·deadlock 고려.
- Amazon DynamoDB — Condition expressions: 조건을 만족할 때만 수행하는 write의 공식 예시.
- PCI SSC — PCI DSS v4.0.1: 결제 계정 데이터 보호·접근 통제 기준 문서.
- 이 페이지의 수요·QPS·TTL·SLO·상태 우선순위·비용은 학습용 설계 가정/제안이며, 실제 호텔 계약·법률·PSP 정책·부하 시험으로 재검증해야 합니다.
작성·검토·참고 자료
참고 자료
- Stripe API — Idempotent requests — POST 요청 재시도에서 멱등 키의 동작과 보존 경계를 확인했다.
- Stripe Docs — Webhooks — webhook 서명 검증, 재전송과 endpoint 처리 지침을 확인했다.
- PostgreSQL — Transaction Isolation — 격리 수준과 동시 transaction의 가시성·재시도 고려를 확인했다.
- PostgreSQL — Explicit Locking — row-level lock과 deadlock 회피를 위한 lock 순서 고려를 확인했다.
- Amazon DynamoDB — Condition expressions — 조건을 만족할 때만 write를 수행하는 조건식의 공식 예시를 확인했다.
- PCI SSC — PCI DSS v4.0.1 — 결제 계정 데이터 보호와 접근 통제의 기준 문서를 확인했다. 이 문서의 수요, QPS, TTL, cache 정책, shard 기준, 상태 우선순위와 비용은 학습용 **설계 가정/제안**이다. 실제 서비스에서는 호텔 계약, 지역 법률, 결제사 규칙, 부하·장애 시험과 운영 역량으로 재검증해야 한다.
사실 오류·출처 정정은 문의·정정 페이지로 알려 주세요.