장기 미처리 취소 대기 건을 분석하다가 일부의 상태가 이상한 것을 봤는데, 연동 클레임은 성공인데 우리 취소 레코드는 요청 상태로 남아 있었다.
Table of contents
Open Table of contents
흐름의 단계 정의
코드를 뒤지기 전에 취소 요청이 들어와서 처리되기까지의 흐름을 단계로 먼저 그렸다.
① 수집이 클레임을 대기 상태로 저장
② 스케줄러가 대기 건을 큐로 전송
③ 컨슈머가 외부 API 호출
④ 성공하면 클레임을 success 로 마킹
클레임이 성공 상태라는 것은 네 번째 단계까지 정상적으로 갔다는 뜻이다. 단계를 먼저 정의해 두니 어디까지 갔는지가 상태값 하나로 확인됐다.
빠진 마지막 단계
여기에 다섯 번째 단계가 있어야 했는데 우리 취소 상태를 요청에서 완료로 전환하는 것이다.
컨슈머 코드를 보니 클레임만 성공으로 바꾸고 우리 취소 레코드는 건드리지 않은 채 끝나고 있었다. 그래서 외부 처리는 완료이고 클레임도 성공인데 우리 취소만 요청 상태로 영구히 남는다.
그 상태로 화면에는 취소 대기로 뜨고 입점사는 취소가 안 됐다며 문의를 넣는다. 실제로는 끝난 일인데 우리 기록만 안 끝난 것이라 어느 쪽 말도 틀리지 않았다.
재수집이 안 통하는 이유
재수집하면 상태가 갱신되지 않을까 싶어 수집 코드를 봤다.
if ($existingClaim) {
// $claim->status = 'ready_v2'; ← 주석 처리
return;
}
기존 레코드가 있으면 수정 분기로 들어가는데 거기서 상태를 되돌리는 로직이 주석 처리돼 있어 아무 효과가 없다. 게다가 클레임이 이미 성공이면 스케줄러가 대기 상태만 집으므로 그 건을 아예 안 집는다.
즉 재수집으로는 어떤 경우에도 안 풀리는 구조였다. 이걸 미리 확인하지 않았으면 재수집을 몇 번 돌려 보고 왜 안 되는지를 다시 조사했을 것이다.
수동 복구와 근본 수정
밀린 건들은 우리 쪽 기록만 손으로 맞췄는데 취소 상태와 취소 일시와 항목 상태 세 값을 갱신하는 것이다. 외부는 이미 끝났으므로 다시 보낼 것이 없다.
근본 수정은 컨슈머에 다섯 번째 단계를 추가하는 것이다. 수동 복구는 밀린 것만 치우고 그 뒤로 또 쌓이므로 컨슈머를 안 고치면 같은 복구를 계속 반복하게 된다.
책임 경계의 공백
왜 빠졌는지 짐작해 보면 책임 경계가 애매했기 때문인 것 같다. 컨슈머는 외부 호출과 클레임 관리가 자기 일이고 우리 취소 레코드는 다른 쪽 일이라고 봤을 수 있다.
문제는 그 다른 쪽이 존재하지 않았다는 점이고 결국 아무도 그 값을 안 바꾼다. 두 시스템에 걸친 흐름에서 마지막 단계가 누구 것인지 정해지지 않으면 그 단계가 통째로 빠지고 양쪽 다 자기 일은 다 했다고 본다.
흐름을 먼저 그린 것이 이 진단에서 유효했다. 단계를 정의하고 각 단계의 증거를 상태값에서 찾으면 빠진 데가 한눈에 드러난다. 코드부터 뒤졌으면 여기도 없고 저기도 없다는 식으로 한참을 헤맸을 것이다.
정리
- 외부 처리는 끝났는데 우리 상태만 안 바뀌는 경우가 있다
- 컨슈머가 자기 레코드만 바꾸고 상위 레코드를 안 건드리면 그렇게 된다
- 재수집은 기존 레코드를 만나면 다른 분기로 가서 효과가 없다
- 상태가 이미 최종이면 스케줄러가 그 건을 안 집는다
- 복구는 우리 기록만 맞추는 것이고 외부는 보낼 것이 없다
- 근본은 컨슈머에 전환 단계를 추가하는 것이다
- 마지막 단계의 주인이 안 정해지면 그 단계가 통째로 빠진다
- 단계를 정의하고 각 단계의 증거를 찾으면 빠진 데가 드러난다