古いワーカーによる結果の上書きを防ぐ
한국어 원문으로 표시합니다.
목표
SQLite에서 소유권 임대와 증가하는 fencing token을 구현합니다.
왜 중요한가
워커 A가 일을 가져간 뒤 멈췄다. 임대가 만료되자 B가 같은 일을 가져가 완료했지만 뒤늦게 살아난 A도 완료 결과를 썼다. 임대 시간을 정하는 것만으로 오래된 작업자의 쓰기를 막을 수 없다. 저장 시점에도 소유자와 세대 번호를 확인해야 한다.
단계
/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-lab
test -e /root/work/idem-fencing-lab/service.py || cp /opt/fixtures/ten_labs/idem-fencing-lab/service.py /root/work/idem-fencing-lab/service.py
cd /root/work/idem-fencing-lab
-
/root/work/idem-fencing-lab/service.py에서 enqueue(path, job_id)는 없는 id를 기본 상태로 넣고 True, 이미 있으면 상태를 바꾸지 않고 False입니다. -
/root/work/idem-fencing-lab/service.py에서 state(path, job_id)는 행을 id, owner, until, fence, result, done 키의 dict로 반환하고 없으면 None입니다. -
/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를 반환합니다. -
/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입니다. -
/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이며 기존 결과를 보존합니다. -
/root/work/idem-fencing-lab/service.py에서 release(path, job_id, owner, fence)는 owner·fence가 같고 done=0인 행의 owner=NULL, until=0으로 바꾸고 True입니다. fence는 보존합니다. 나머지는 False입니다. -
/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 공식 문서 · pytest 공식 문서 · Python sqlite3
- 한계: 시계는 호출자가 주는 비감소 숫자이며 여러 서버 간 시계 동기화는 모델링하지 않는다. 업무의 외부 부작용까지 자동으로 fencing되는 것은 아니다. 저장소 외부 시스템에도 같은 토큰 검증이나 별도 멱등 처리가 필요하다. SQLite는 실제 잠금과 트랜잭션을 쓰지만 대규모 분산 큐의 처리량을 대표하지 않는다.
임대 상태 테이블을 만든다
/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-lab
test -e /root/work/idem-fencing-lab/service.py || cp /opt/fixtures/ten_labs/idem-fencing-lab/service.py /root/work/idem-fencing-lab/service.py
cd /root/work/idem-fencing-lab
워커 재시작 시 세대 번호를 초기화하면 오래된 토큰이 다시 유효해집니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/01-contract.sh로 확인하세요.
최초 작업만 등록한다
/root/work/idem-fencing-lab/service.py에서 enqueue(path, job_id)는 없는 id를 기본 상태로 넣고 True, 이미 있으면 상태를 바꾸지 않고 False입니다.
재등록이 진행 중인 임대를 초기화하지 않게 INSERT OR IGNORE를 사용합니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/02-contract.sh로 확인하세요.
현재 상태를 읽는다
/root/work/idem-fencing-lab/service.py에서 state(path, job_id)는 행을 id, owner, until, fence, result, done 키의 dict로 반환하고 없으면 None입니다.
별도 DB 연결에서 저장 상태를 읽어 프로세스 안의 캐시와 구별합니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/03-contract.sh로 확인하세요.
만료된 임대를 원자적으로 가져간다
/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를 반환합니다.
읽기와 갱신은 하나의 BEGIN IMMEDIATE 안에서 수행합니다. 경계 now==until은 재할당 가능합니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/04-contract.sh로 확인하세요.
현재 세대만 임대를 갱신한다
/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입니다.
이미 만료된 소유권을 renew로 되살리면 새 워커와 충돌합니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/05-contract.sh로 확인하세요.
오래된 완료를 거절한다
/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이며 기존 결과를 보존합니다.
완료 쓰기에도 임대 검사가 있어야 늦게 돌아온 워커를 막습니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/06-contract.sh로 확인하세요.
현재 워커만 임대를 반납한다
/root/work/idem-fencing-lab/service.py에서 release(path, job_id, owner, fence)는 owner·fence가 같고 done=0인 행의 owner=NULL, until=0으로 바꾸고 True입니다. fence는 보존합니다. 나머지는 False입니다.
반납 때 fence까지 초기화하면 과거 토큰 번호를 재사용하게 됩니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/07-contract.sh로 확인하세요.
동시 claim에 승자는 하나다
/root/work/idem-fencing-lab/service.py에서 claim_many(path, job_id, owners, now, ttl)는 ThreadPoolExecutor로 각 owner의 claim을 동시에 호출해 입력 순서의 반환값 리스트를 돌려줍니다. 새 작업은 딱 하나만 fence를 얻고 나머지는 None이어야 합니다.
사전 조회 후 연결을 닫고 갱신하지 마세요. 잠금이 두 연산을 함께 보호해야 합니다.
저장 후 bash /opt/lab/checks/idem-fencing-lab/08-contract.sh로 확인하세요.