주문을 취소했는데 외부 거래소의 헤지 주문은 안 취소됐다는 문의를 받았다. orders 를 보니 status = 'CANCELED' 라 우리 쪽은 정상으로 보였다.
Table of contents
Open Table of contents
증상 — 정상으로 보이던 내부 상태
이 시스템은 내부 상태를 담는 orders 층과 외부 거래소와 주고받은 기록을 담는 hedge_sync_log 층으로 나뉘어 있었다. 내부 상태는 회원이 취소를 누른 시점에 바뀌고 외부 전송은 그 뒤에 ActiveMQ 를 거쳐 일어난다.
hedge_sync_log 를 조회하니 전송이 FAILED 인 채로 멈춰 있었다. 우리 쪽은 취소 완료이고 외부에는 안 간 어긋난 상태였다.
원인 — 한쪽만 보면 반대 결론이 난다
orders 만 보면 정상이고 hedge_sync_log 만 보면 실패다. 한쪽만 보고 답하면 어느 쪽을 봤느냐에 따라 정반대 결론이 나오고 둘 다 근거가 있어 보인다.
문의를 받고 orders 만 조회한 채로 정상이라고 답할 뻔했다. 두 층을 함께 봐야 내부는 됐고 외부는 안 됐다는 사실이 나온다.
판단 기준 — 지연과 누락의 구분
같은 조사에서 본 다른 건은 hedge_sync_log 자체가 없었다. 체결 통보 수집 배치가 아직 안 돈 것이었고 몇 시간 뒤에 확인하니 기록이 생기고 DONE 으로 완료돼 있었다.
기록이 없다는 관측이 누락일 수도 지연일 수도 있으므로 그 둘을 갈라야 했다. 회신하기 전에 한 번 더 조회하니 답이 완료됐다는 내용으로 바뀌었다.
비슷한 두 건을 나란히 놓기
이 조사가 수월했던 것은 두 건의 조건이 거의 같았기 때문이다. 마켓과 status 와 수량과 updated_at 이 같고 갈린 것이 연동 층뿐이었다.
조건이 달랐으면 무엇 때문에 갈렸는지 알 수 없다. 비슷한 두 건을 나란히 놓는 것이 이런 조사의 절반이었다.
조치 — 당장 조치와 근본 대응
당장 조치는 재전송이었다. 두 가지 방법이 있었다. 관리 화면에서 POST /hedge/{id}/resend 를 손으로 누르는 것과, FAILED 건을 retry_count 상한까지 자동으로 다시 보내는 잡을 두는 것이다. 뒤쪽을 골랐다. 이유는 손으로 누르는 것은 문의가 온 건만 고치고 안 온 건은 그대로 남기기 때문이다. 대신 상대 거래소가 이미 처리한 취소를 두 번 보내는 경우가 생겨 client_order_id 로 멱등하게 만들어야 하는 것을 감수했다.
그것만으로는 다음에 또 생긴다. 수집 배치의 지연 한계와 FAILED 가 오래 지속될 때의 알림을 따로 적었는데, 셋째가 없어서 이틀 가까이 오류 상태였는데 아무도 몰랐다. 당장 조치와 근본 대응을 나눠 적으면 급한 것을 처리하면서도 다음 것을 놓치지 않는다.
정리
- 시스템이 여러 층이면 한쪽만 보고 결론 내면 반대 답이 나온다
orders가 바뀐 것과 외부에 반영된 것은 다른 사실이다- 둘 다 근거가 있어 보이므로 어느 쪽을 봤는지가 답을 정한다
hedge_sync_log가 없는 것이 누락일 수도 지연일 수도 있다- 비슷한 두 건을 나란히 놓으면 갈린 지점이 선명해진다
- 비동기 시스템에서는 회신 직전에 다시 조회한다
- 당장 조치와 근본 대응을 나눠 적는다
FAILED가 오래 지속될 때의 알림이 없으면 아무도 모른다