출금 요청이 접수됐다는 화면이 뜨는데 며칠 뒤에도 처리가 안 되어 있었다. WithdrawService.request() 는 withdrawals 에 PENDING 행을 저장하고 바로 반환하고 있었다.
Table of contents
Open Table of contents
증상 — 만들고 끝낸 요청
화면에는 접수되었다는 문구가 나오지만 실제로 일어난 것은 행이 하나 생긴 것뿐이었다. 그 뒤에 무엇이 그것을 처리하는지 아무도 보고 있지 않았다.
만드는 쪽 코드에는 아무 문제가 없었다. 만드는 쪽만 보면 정상이라는 결론이 나오는 구조였다.
원인 — 처리하는 쪽이 멈춰 있었다
WithdrawWorker 가 안 돌고 있었고 언제부터인지도 알 수 없었다. PENDING 으로 남은 건을 세니 며칠치가 쌓여 있었다.
요청을 남겼다는 것과 처리됐다는 것은 다른 사실인데 화면 문구가 그 둘을 하나로 보이게 만들고 있었다. 그래서 오래 대기한 건을 세어 임계를 넘으면 알리게 했다.
조치 — 오래된 대기와 단계별 기준
감시를 어디에 둘지 두 가지 방법이 있었다. Kibana 의 로그 기반 임계 알림으로 하는 것과, 별도 MonitorJob 이 DB 를 직접 세는 것이다. 뒤쪽을 골랐다. 이유는 워커가 죽으면 로그도 안 나와서 로그 기반 알림은 조용해지기 때문이다. 대신 감시 서비스 하나를 더 운영하는 것을 감수했다.
대기 중인 것을 status 별로 묶고 MIN(created_at) 으로 가장 오래된 것의 시각을 함께 봤다. SENDING 상태에 하루 넘게 머문 건이 하나 있었는데 노드 전송 도중에 죽은 것이었다. 단계마다 정상적으로 머무는 시간이 다르므로 임계도 다르게 뒀다. 노드 응답을 기다리는 SENDING 은 길게 잡고 내부 처리 단계는 짧게 잡았다.
대기 건수와 워커 생존
워커가 worker_heartbeats 에 주기적으로 살아 있다는 표시를 남기고 일정 시간 없으면 죽은 것으로 보게 했다. 대기 건수로 아는 것보다 빨랐다.
대기 건수는 요청이 없으면 안 늘어나므로 새벽에 워커가 죽으면 아침까지 모른다. 쌓이는 것을 보는 것과 워커가 사는지 보는 것은 다른 신호라 둘 다 필요했다.
다시 살아났을 때
워커가 다시 뜨면 쌓인 것을 한꺼번에 처리해 노드로 몰려 나간다. 그래서 LIMIT 20 으로 한 번에 처리할 건수와 간격을 두고 밀린 것을 나눠 보내게 했다.
같은 구조가 다른 곳에도 있는지 찾아보니 일정산 요청과 알림 예약이 같은 모양이었다. 같은 방식으로 대기와 생존을 세니 알림 예약 쪽에서 예약만 되고 안 나가는 건이 나왔다.
정리
- 요청을 만든 것과 처리된 것은 다른 사실이다
- 화면 문구가 그 둘을 하나로 보이게 만들 수 있다
- 만드는 쪽만 보면 처리하는 쪽이 멈춰도 모른다
- 오래된
PENDING건을 세고 임계를 넘으면 알린다 - 단계마다 정상 체류 시간이 다르므로 임계도 다르게 둔다
- 대기가 쌓이는 것과
worker_heartbeats로 워커가 사는 것을 둘 다 본다 - 로그 기반 알림은 워커가 죽으면 함께 조용해진다
- 다시 살아났을 때 몰아서 나가지 않게 나눠 보낸다
- 같은 구조가 어디에 또 있는지 찾는다