삼천여 건을 연동 해제해야 했는데 API가 있어서 배치로 돌리기로 하고 500건씩 청크로 나눠 curl 반복 실행했다.
Table of contents
Open Table of contents
청크마다 되읽은 이유
한꺼번에 안 돌리고 나눈 김에 매 청크마다 DB를 확인하게 했는데, API 응답만 보지 않고 status 컬럼을 SELECT COUNT(*)로 직접 세어 대조하는 절차다.
이 확인을 넣는 데 드는 비용은 WHERE status = 'unlinked' 조건을 단 쿼리 한 줄이었고 안 넣었으면 나중에 전체를 다시 대조해야 했을 것이다.
증상 — 여섯 번째 청크의 불일치
여섯 번째 청크에서 API는 500건 전부 성공이라고 했는데 DB에는 한 건이 status 그대로 남아 있었다.
그 한 건만 골라 다시 보내니 이번에도 성공이라는 응답이 왔다. DB를 다시 보니 상태가 그대로였고 이번에는 updated_at까지 함께 확인했다.
원인 — 갱신 시각이 가른 것
updated_at이 몇 달 전 값 그대로 멈춰 있었다. 처리됐다면 실행 시각으로 갱신됐을 텐데 과거 그대로라는 것은 이 레코드에 손도 안 댔다는 뜻이다.
status만 봤으면 조건이 안 맞아 상태가 유지된 것인지 아예 안 건드린 것인지 구분할 수 없었다. updated_at이 그 둘을 갈랐다. API가 어디선가 조용히 건너뛰고 성공 목록에 넣은 것이고 이것을 무음 스킵이라고 부르기로 했다.
API 내부에서 왜 건너뛰었는지는 우리 코드가 아니어서 끝내 확인하지 못했다. 조건이 안 맞아서일 수도 있고 예외를 삼켰을 수도 있는데, 원인은 몰라도 판별 방법만은 확정됐다.
판단 기준 — 무음 스킵의 분류
두 가지 방법이 있었는데 실패로 세서 재시도 대상에 넣는 것과 재시도 대상에서 빼고 별도 항목으로 두는 것이다. 뒤쪽을 골랐다. 이유는 재시도해도 같은 결과가 나오는 것을 두 번 확인했으므로 계속 재시도하면 무한 반복이 돼서다. 대신 잔여 목록을 사람이 따로 열어 봐야 하는 일을 감수했다.
이렇게 나누고 나니 시도한 수가 성공과 무음 스킵의 합으로 정확히 맞아떨어졌다. 무음 스킵을 실패로 세면 재시도가 필요한 한 건이 되고 그 재시도가 영원히 안 끝나며, 성공으로 세면 전건 성공인데 실제로는 한 건 모자란다. 별도 항목으로 둬야 검산이 맞는다.
이 패턴을 알고 나서 앞선 큰 실행 기록을 다시 보니 수만 건짜리 실행에서도 같은 것이 십여 건 있었다. 그때는 청크별 확인을 안 했거나 했어도 그냥 넘어갔던 것으로 보인다.
조치 — 청크 확인이 만든 절차
이 발견이 가능했던 조건은 청크마다 사후 확인을 넣었다는 것 하나뿐이었다. 한꺼번에 보내고 마지막에 한 번 확인했으면 총계에서 한 건 차이를 봐도 어느 건인지 찾기 어려웠을 것이다.
청크가 500건이라 대조할 범위가 좁았고 그래서 한 건을 바로 특정할 수 있었다. 절차로도 정리했는데 청크로 나누고 실행하고 DB를 확인하고 안 맞으면 미반영 건을 특정해 단건 재시도한 뒤 updated_at을 보고 안 바뀌면 무음 스킵으로 확정한다. 그리고 마지막에 시도한 수와 성공한 수와 무음 스킵의 수로 검산한다.
정리
- 일괄 API가 처리하지 않은 건도 성공 목록에 담을 수 있다
- 판정은 DB를 되읽어
status와updated_at을 함께 본다 status만 보면 조건 미충족과 미처리를 구분할 수 없다updated_at이 과거 그대로면 손도 안 댄 것이다- 재시도해도 같으면 무음 스킵으로 확정하고 별도 관리한다
- 실패로도 성공으로도 세지 않아야 검산이 맞는다
- 청크로 나누면 미반영 건을 바로 특정할 수 있다
- 패턴이 알려지면 과거 실행에서도 같은 것이 보인다