증상
*_done.json이 {id: true}만 기록해 무엇을 썼는지 남지 않는다. 나중에 불량이 발견됐을 때 완료 2천여 건 중 몇 건이 불량인지 로컬에서 셀 수 없다.
해법
done 파일에 최소 3필드: {id: {ts, len, src}} — 생성 본문 길이와 원본 출처만 있으면 사후 전수 검증이 로컬에서 끝난다.
★★ 접두 형식을 쓰면 판정 연산자가 바뀐다
값에 사유를 남기려고 접두 형식을 쓰는 건 좋다 — 무엇을 왜 그렇게 처리했는지가 남는다.
done[x] = "ok:1234567890" / "skip:정책제외" / "manual:검토필요" / "fail2"
그러면 판정은 반드시 startswith여야 한다.
done_ok = str(v).startswith(("ok", "skip", "manual"))
★ != "ok" 나 not in ("ok", "skip") 은 접두 형식에서 항상 통과한다.
"ok:1234567890" != "ok" 는 True다 → 완료된 건을 매 회차 재처리한다. 에러는 없고, 로그는 정상이고, 목록만 영원히 안 비운다.
더 안전한 형태 — 판정 대상과 설명을 분리한다
done[x] = {"state": "ok", "detail": "1234567890"}
값 하나에 두 정보를 담으면 읽는 쪽이 매번 파싱 규약을 지켜야 하고, 한 곳만 틀려도 샌다.
같은 연산자 위험의 반대 방향
| 값 체계 | 틀린 연산자 | 결과 |
|---|---|---|
"ok:123" | != "ok" | 완료를 미완료로 읽어 무한 재처리 |
임시저장 / 임시저장중 | == "임시저장" | 해당 건을 누락 |
★ 접두어를 공유하는 값 체계에서는 ==도 !=도 둘 다 위험하다. 한쪽은 빠뜨리고 한쪽은 다 통과시킨다.
★ 값 형식을 정할 때 판정 코드를 같이 정한다. 나중에 형식만 바꾸면 판정이 조용히 뒤집힌다.
★★ continue 전에 저장하지 않으면 판단이 사라진다
루프 안에서 상태를 메모리에만 바꾸고 continue 하면, 그 회차가 죽거나 다음 저장 전에 끊겼을 때 판단한 사실 자체가 없어진다. 다음 회차가 같은 건을 처음 보는 것처럼 다시 처리한다.
★ 상태를 바꾸면 그 자리에서 저장한다. 루프 끝 저장만 믿지 않는다.
이건 "실패도 기록해야 한다"와 한 겹 다르다 — 저기는 기록할 생각을 안 한 것이고, 여기는 기록했는데 안 쓰인 것이다.
★★ 네 형태가 전부 같은 증상을 낸다
같은 진행 파일 하나를 두고 네 가지 방식으로 틀렸다. 하루에 넷 다 밟았다.
| 형태 | 증상 | |
|---|---|---|
| ① | 값 형식을 바꾸고 판정부를 안 고침 | "ok:123" != "ok" 통과 → 완료를 재처리 |
| ② | == 로 판정 | 접두어를 공유하는 값을 누락 |
| ③ | 응답만 보고 ok | 재조회 검증 없음 → 완료 기록이 통째로 허수 |
| ④ | continue 전에 저장 안 함 | 판단이 사라져 무한 재처리 |
★ 넷 다 에러가 없고, 넷 다 목록이 안 빈다.
그래서 하나를 고쳐도 증상이 남으면 "안 고쳐졌다"가 아니라 "다른 형태가 또 있다"로 읽어야 한다.
규약 — 셋 다 필요하다
startswith — 접두 형식이면 ==·!= 둘 다 위험하다★ 셋 중 하나만 빠져도 목록은 안 빈다. 그리고 어느 게 빠졌는지는 증상으로 구분되지 않는다.
같이 밟는 것 — "처리 못 함"과 "처리 필요 없음"의 혼동
실패를 done에 안 남기면 무한 재시도 루프가 된다. 반대로 "이미 처리할 필요가 없어진 건"을 미처리로 분류하면 목록이 영원히 안 비고 배치가 20분마다 같은 일을 반복한다(로그만 385KB 쌓였다).
→ 실패도 기록하되, 두 상태를 구분해 기록한다.