멱등성 — 두 번 눌러도 한 번만 결제되게 · 멈췄던 워커가 돌아와 결과를 덮어썼다 · 实验
멈췄던 워커가 돌아와 결과를 덮어썼다
목표
SQLite에서 소유권 임대와 증가하는 fencing token을 구현합니다.
왜 중요한가
워커 A가 일을 가져간 뒤 멈췄다. 임대가 만료되자 B가 같은 일을 가져가 완료했지만 뒤늦게 살아난 A도 완료 결과를 썼다. 임대 시간을 정하는 것만으로 오래된 작업자의 쓰기를 막을 수 없다. 저장 시점에도 소유자와 세대 번호를 확인해야 한다.
단계
1. /root/work/idem-fencing-lab/service.py에서 init_db(path)는 jobs(id TEXT PRIMARY KEY, owner TEXT, until REAL NOT NULL DEFAULT 0, fence INTEGER NOT NULL DEFAULT 0, result TEXT, done INTEGER NOT NULL DEFAULT 0)를 멱등 생성합니다.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/idem-fencing-labtest -e /root/work/idem-fencing-lab/service.py || cp /opt/fixtures/ten_labs/idem-fencing-lab/service.py /root/work/idem-fencing-lab/service.pycd /root/work/idem-fencing-lab2. /root/work/idem-fencing-lab/service.py에서 enqueue(path, job_id)는 없는 id를 기본 상태로 넣고 True, 이미 있으면 상태를 바꾸지 않고 False입니다.
3. /root/work/idem-fencing-lab/service.py에서 state(path, job_id)는 행을 id, owner, until, fence, result, done 키의 dict로 반환하고 없으면 None입니다.
4. /root/work/idem-fencing-lab/service.py에서 claim(path, job_id, owner, now, ttl)는 ttl<=0이면 ValueError입니다. 없는 작업·완료된 작업·until>now인 작업은 None입니다. 나머지는 owner와 until=now+ttl을 저장하고 fence를 1 올려 새 fence를 반환합니다.
5. /root/work/idem-fencing-lab/service.py에서 renew(path, job_id, owner, fence, now, ttl)는 ttl<=0이면 ValueError입니다. owner·fence가 일치하고 done=0, until>now일 때만 until=now+ttl로 바꾸어 True, 아니면 False입니다.
6. /root/work/idem-fencing-lab/service.py에서 complete(path, job_id, owner, fence, now, result)는 현재 임대(owner·fence 일치, done=0, until>now)일 때만 result와 done=1을 저장해 True입니다. 나머지는 False이며 기존 결과를 보존합니다.
7. /root/work/idem-fencing-lab/service.py에서 release(path, job_id, owner, fence)는 owner·fence가 같고 done=0인 행의 owner=NULL, until=0으로 바꾸고 True입니다. fence는 보존합니다. 나머지는 False입니다.
8. /root/work/idem-fencing-lab/service.py에서 claim_many(path, job_id, owners, now, ttl)는 ThreadPoolExecutor로 각 owner의 claim을 동시에 호출해 입력 순서의 반환값 리스트를 돌려줍니다. 새 작업은 딱 하나만 fence를 얻고 나머지는 None이어야 합니다.
참고
- 인터넷과 패키지 설치 없이 기존 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)
- 한계: 시계는 호출자가 주는 비감소 숫자이며 여러 서버 간 시계 동기화는 모델링하지 않는다. 업무의 외부 부작용까지 자동으로 fencing되는 것은 아니다. 저장소 외부 시스템에도 같은 토큰 검증이나 별도 멱등 처리가 필요하다. SQLite는 실제 잠금과 트랜잭션을 쓰지만 대규모 분산 큐의 처리량을 대표하지 않는다.
8个步骤
- 임대 상태 테이블을 만든다
- 최초 작업만 등록한다
- 현재 상태를 읽는다
- 만료된 임대를 원자적으로 가져간다
- 현재 세대만 임대를 갱신한다
- 오래된 완료를 거절한다
- 현재 워커만 임대를 반납한다
- 동시 claim에 승자는 하나다