얼쑤에서 분석을 다시 시작할 수 있는 경우는 기존 녹음을 유지한 서버 오류나 시간 초과입니다. 다른 상태에서 요청이 들어오면 서비스는 먼저 거절합니다. 그 검사를 통과한 뒤에도 DB 갱신은 조건부로 실행합니다. 서비스가 상태를 읽은 뒤 다른 요청이 그 행을 바꿀 수 있기 때문입니다.
단위 테스트의 가짜 저장소도 실제 DB의 조건을 비슷하게 구현했습니다. 서비스의 재시도 가능 여부 검사를 일부러 항상 참으로 바꿔 봤는데, 당시 테스트 여덟 개가 모두 통과했습니다. 서비스는 거절했어야 할 요청을 저장소에 넘겼지만, 가짜 저장소가 대신 거절해서 바깥에서 본 결과와 행 상태는 같았습니다.
응답은 같아도 저장소 호출은 달랐다
이 사례에서 서비스와 저장소는 같은 상태를 보지만 책임이 다릅니다. 서비스의 검사는 현재 상태로 요청을 받아들일지 결정하고, 저장소의 조건은 읽기 이후 상태가 바뀌었을 때도 잘못 쓰지 않도록 막습니다. 대역에 조건을 구현한 덕분에 경합을 재현할 수 있었지만, 최종 응답만 보면 어느 쪽에서 거절했는지 드러나지 않았습니다.
가짜 저장소가 갱신 메서드를 호출받을 때마다 횟수를 기록하게 했습니다. 현재 테스트는 새 녹음이 필요한 실패, 이미 끝난 세션, 아직 업로드 전인 세션 등 거절 입력 여섯 가지에서 결과와 행 상태를 확인한 뒤 startRetryCalls가 0회라고 단언합니다. 진행 중인 시도를 그대로 돌려주는 별도 테스트에도 같은 단언이 있습니다. 실제 MySQL 검사는 조건부 갱신이 막아야 할 실패 사유와 시도 번호를 따로 다룹니다.
조기 거절을 제거해 회귀 검증
11월 9일에 RetryPracticeAnalysisTests를 JDK 21과 Gradle 9.8.2로 다시 실행했습니다. 정상 구현에서는 14개가 모두 통과했습니다. 임시 사본에서는 재시도 루프에 들어갈 때 실행하는 조기 거절 한 줄만 빼고 같은 테스트를 실행했습니다. 가짜 저장소와 루프가 끝난 뒤의 결과 판정은 그대로 두었습니다.
| 검사 입력 | 정상 구현의 갱신 호출 | 조기 거절을 제거한 구현 |
|---|---|---|
| 재시도할 수 없는 상태 6가지 | 0회 | 합계 18회 |
| 분석이 진행 중인 상태 1가지 | 0회 | 3회 |
변형에서는 14개 중 2개가 실패했습니다. 두 테스트는 서비스가 돌려준 결과와 가짜 저장소의 상태까지는 기대값을 만족했습니다. 그 다음에 실행한 호출 횟수 단언에서 실패했습니다. 재시도 루프가 최대 세 번 돌아가면서, 서비스에서 걸러야 할 입력을 가짜 저장소에 매번 넘긴 결과입니다. 이 실행은 저장소 대역을 사용한 단위 테스트이며, 실제 MySQL에서 쓰기나 잠금이 발생했는지는 별도 통합 검사의 범위입니다.

댓글
댓글과 반응은 GitHub 계정으로 로그인해 남길 수 있어요.