멱등성 — 두 번 눌러도 한 번만 결제되게 · 이체 중간에 죽어도 잔액은 보존된다 · 实验
이체 중간에 죽어도 잔액은 보존된다
목표
차감·증가·이체 기록을 원자적으로 묶고 동시 잔액 경쟁을 재현합니다.
왜 중요한가
송신 계좌에서는 돈을 빼고 수신 계좌에 더하기 전에 프로세스가 죽었다. 재시도는 이체 id가 없어서 다시 돈을 뺐다. 반대로 이체 기록만 먼저 커밋하면 재시도는 완료라고 판단해 입금이 영원히 누락된다. 금액 계산을 정수로 정확히 해도 트랜잭션 경계가 틀리면 결과가 잘못된다.
단계
1. /root/work/idem-ledger-transfer-lab/service.py에서 init_db(path)는 accounts(id TEXT PRIMARY KEY,balance INTEGER NOT NULL)와 transfers(id TEXT PRIMARY KEY,source TEXT NOT NULL,target TEXT NOT NULL,amount INTEGER NOT NULL)를 멱등 생성합니다.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/idem-ledger-transfer-labtest -e /root/work/idem-ledger-transfer-lab/service.py || cp /opt/fixtures/ten_labs/idem-ledger-transfer-lab/service.py /root/work/idem-ledger-transfer-lab/service.pycd /root/work/idem-ledger-transfer-lab2. /root/work/idem-ledger-transfer-lab/service.py에서 add_account(path, account_id, amount)는 bool 제외 0 이상 int만 허용하고 INSERT로 계좌를 만듭니다. 이미 있는 계좌는 sqlite3.IntegrityError로 거절하며 잔액은 보존합니다.
3. /root/work/idem-ledger-transfer-lab/service.py에서 balance(path, account_id)는 계좌 잔액을 반환하고 없으면 KeyError입니다.
4. /root/work/idem-ledger-transfer-lab/service.py에서 validate_transfer(source,target,amount)는 source!=target이고 amount가 bool 제외 양의 int이면 None, 아니면 ValueError입니다.
5. /root/work/idem-ledger-transfer-lab/service.py에서 transfer(path,tx_id,source,target,amount,fault=lambda:None)는 같은 id/내용이면 False, id 충돌·없는 계좌·부족한 잔액은 ValueError입니다. 새 이체는 차감→fault()→입금→transfers 삽입을 원자적으로 실행하고 True입니다.
6. /root/work/idem-ledger-transfer-lab/service.py에서 history(path)는 transfers를 id 오름차순의 (id,source,target,amount) 튜플 리스트로 반환합니다.
7. /root/work/idem-ledger-transfer-lab/service.py에서 total_balance(path)는 accounts.balance의 합계입니다. 계좌가 없으면 0입니다.
8. /root/work/idem-ledger-transfer-lab/service.py에서 compete(path,source,target,amount)는 서로 다른 id race-a/race-b로 같은 금액의 transfer를 두 스레드에서 실행합니다. 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)
- 한계: 실제 금융 원장의 회계·감사·법적 요구 사항을 대체하는 시스템이 아니다. 통화·소수 단위·수수료·다중 통화는 다루지 않고 금액은 최소 단위의 양의 정수다. 외부 결제 시스템 호출은 이 DB 트랜잭션에 포함되지 않으므로 별도 설계가 필요하다.
8个步骤
- 원장 테이블을 만든다
- 계좌를 한 번만 만든다
- 없는 계좌와 0원을 구분한다
- 이체 입력을 검증한다
- 세 쓰기를 한 트랜잭션으로 묶는다
- 이체 이력을 결정적으로 읽는다
- 보존량을 확인한다
- 동시 이체가 잔액을 넘지 않는다