멱등성 — 두 번 눌러도 한 번만 결제되게 · 마지막 재고를 두 사람이 예약했다 · 실습
마지막 재고를 두 사람이 예약했다
목표
재고 차감·예약 id·취소를 원자적으로 묶고 초과 판매를 막습니다.
왜 중요한가
재고가 하나 남았는데 두 요청이 동시에 조회해 모두 예약에 성공했다. 이후 같은 취소 메시지가 두 번 도착하자 재고가 원래보다 늘었다. 예약의 중복 제거와 취소의 중복 제거는 서로 다른 상태 전이이며, 남은 수량 검사도 차감과 같은 트랜잭션에 있어야 한다.
단계
1. /root/work/idem-stock-reservation-lab/service.py에서 quantity(value)는 bool 제외 양의 int만 반환하고 그 외 ValueError입니다.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/idem-stock-reservation-labtest -e /root/work/idem-stock-reservation-lab/service.py || cp /opt/fixtures/ten_labs/idem-stock-reservation-lab/service.py /root/work/idem-stock-reservation-lab/service.pycd /root/work/idem-stock-reservation-lab2. /root/work/idem-stock-reservation-lab/service.py에서 init_db(path)는 stock(sku TEXT PRIMARY KEY,available INTEGER NOT NULL)와 reservations(id TEXT PRIMARY KEY,sku TEXT NOT NULL,qty INTEGER NOT NULL,cancelled INTEGER NOT NULL DEFAULT 0)를 멱등 생성합니다.
3. /root/work/idem-stock-reservation-lab/service.py에서 add_stock(path,sku,amount)는 bool 제외 0 이상 int를 검사하고 새 상품만 INSERT합니다. 중복 상품은 IntegrityError입니다.
4. /root/work/idem-stock-reservation-lab/service.py에서 available(path,sku)는 저장된 수량을 반환하고 상품이 없으면 KeyError입니다.
5. /root/work/idem-stock-reservation-lab/service.py에서 reserve(path,rid,sku,qty,fault=lambda:None)는 수량을 검증합니다. 기존 예약 id는 같은 상품/수량이면 False, 다르면 ValueError입니다. 새 예약은 상품 존재와 재고를 확인해 차감→fault→예약 삽입 후 True입니다. 부족·없는 상품은 ValueError입니다.
6. /root/work/idem-stock-reservation-lab/service.py에서 cancel(path,rid)는 없는 예약·이미 취소된 예약이면 False입니다. active 예약이면 같은 트랜잭션에서 cancelled=1과 재고 반환을 수행해 True입니다.
7. /root/work/idem-stock-reservation-lab/service.py에서 reservation(path,rid)는 (sku,qty,cancelled) 튜플 또는 None을 반환합니다.
8. /root/work/idem-stock-reservation-lab/service.py에서 compete(path,sku)는 reserve를 서로 다른 id first/second, 수량 1로 두 스레드에서 실행합니다. ValueError만 False로 바꾼 입력 순서 결과 리스트를 반환합니다.
참고
- 인터넷과 패키지 설치 없이 기존 lab-dev 환경에서 수행합니다.
- 각 단계는 45초 채점 예산 안에서 실행됩니다. 실제 sleep이나 네트워크 호출을 추가하지 마세요.
- 채점은 제출 모듈을 새로 불러오고 독립 입력과 임시 DB로 검사합니다. 예상값을 상수로 반환하는 대신 계약을 구현하세요.
- [FastAPI 공식 문서](https://fastapi.tiangolo.com/) · [pytest 공식 문서](https://docs.pytest.org/en/stable/) · [Python sqlite3](https://docs.python.org/3/library/sqlite3.html)
- 한계: 결제 승인과 재고 예약을 하나의 분산 트랜잭션으로 묶는 실습은 아니다. 예약 만료와 결제 실패의 보상은 별도 흐름이다. 조회 시점의 재고 숫자를 믿지 않고 쓰는 트랜잭션 안에서 확인하는 원리를 작은 SQLite DB로 배운다.
8단계
- 수량을 정수 계약으로 고정한다
- 재고와 예약을 나누어 저장한다
- 상품 초기화를 재고 변경과 구별한다
- 남은 수량을 조회한다
- 예약과 재고 차감을 묶는다
- 취소도 한 번만 반영한다
- 예약 상태를 확인한다
- 마지막 재고 경쟁을 재현한다