멱등성 — 두 번 눌러도 한 번만 결제되게 · 가져오기 작업을 실패한 다음 행부터 이어간다 · 실습
가져오기 작업을 실패한 다음 행부터 이어간다
목표
원본 지문과 체크포인트를 결합해 다른 파일로 잘못 이어받는 일을 막습니다.
왜 중요한가
수천 행을 가져오던 작업이 중간에 죽었다. 운영자가 파일을 바꿔 같은 작업 id로 다시 실행하자 앞부분은 옛 파일, 뒷부분은 새 파일인 결과가 생겼다. 처리한 행 번호만 저장하면 입력의 정체성을 확인할 수 없다. 원본 바이트의 지문과 마지막으로 커밋한 위치를 함께 보존해야 한다.
단계
1. /root/work/idem-batch-checkpoint-lab/service.py에서 parse_rows(raw)는 JSON 배열 bytes를 읽습니다. 각 항목은 id(비어 있지 않은 str)와 value(bool 제외 int)를 가지며 id 중복은 금지합니다. 위반은 ValueError입니다. {id,value}만 가진 행 리스트를 반환합니다.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/idem-batch-checkpoint-labtest -e /root/work/idem-batch-checkpoint-lab/service.py || cp /opt/fixtures/ten_labs/idem-batch-checkpoint-lab/service.py /root/work/idem-batch-checkpoint-lab/service.pycd /root/work/idem-batch-checkpoint-lab2. /root/work/idem-batch-checkpoint-lab/service.py에서 source_digest(raw)는 bytes에 SHA-256을 적용한 hex 문자열입니다. JSON을 정규화하지 않습니다.
3. /root/work/idem-batch-checkpoint-lab/service.py에서 init_db(path)는 imports(id TEXT PRIMARY KEY,digest TEXT NOT NULL,next_index INTEGER NOT NULL)와 items(batch TEXT NOT NULL,id TEXT NOT NULL,value INTEGER NOT NULL,PRIMARY KEY(batch,id))를 멱등 생성합니다.
4. /root/work/idem-batch-checkpoint-lab/service.py에서 begin(path,batch,digest)는 새 작업이면 next_index=0을 저장해 0, 기존 같은 지문이면 next_index, 다른 지문이면 ValueError입니다.
5. /root/work/idem-batch-checkpoint-lab/service.py에서 apply_chunk(path,batch,start,rows,fault=lambda index:None)는 현재 next_index==start일 때만 실행하며 아니면 ValueError입니다. rows를 순서대로 items에 넣고 각 삽입 후 fault(전체인덱스)를 호출합니다. 전부 성공하면 next_index=start+len(rows)를 저장해 반환합니다.
6. /root/work/idem-batch-checkpoint-lab/service.py에서 checkpoint(path,batch)는 next_index, 없는 작업은 None입니다.
7. /root/work/idem-batch-checkpoint-lab/service.py에서 values(path,batch)는 해당 batch의 (id,value) 튜플을 id 오름차순으로 반환합니다.
8. /root/work/idem-batch-checkpoint-lab/service.py에서 import_all(path,batch,raw,size=2,fault=lambda index:None)는 bool 제외 양의 int size를 검증하고 parse_rows·source_digest·begin을 사용합니다. 남은 행을 size씩 apply_chunk로 처리하고 최종 checkpoint를 반환합니다.
참고
- 인터넷과 패키지 설치 없이 기존 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)
- 한계: 입력 전체를 메모리에 읽는 작은 데이터용 실습이다. 대용량 파일을 스트리밍 파싱하는 엔진으로 과장하지 않는다. 원본 공백만 달라도 바이트 지문이 달라 재개를 거절한다는 보수적 계약이다. 외부 API 부작용은 이 DB 트랜잭션에 들어가지 않는다.
8단계
- 입력 행의 계약을 확인한다
- 원본 바이트의 지문을 고정한다
- 입력과 체크포인트를 저장한다
- 다른 원본으로 재개하지 못하게 한다
- 배치와 위치를 원자적으로 커밋한다
- 현재 위치를 조회한다
- 작업별 결과를 분리한다
- 중간 실패 뒤 안전하게 이어간다