출금 한 건이 사흘째 대기 상태로 멈춰 있다는 이야기가 들어왔다. 실패한 것도 아니고 끝난 것도 아닌 채였다.
Table of contents
Open Table of contents
증상 — 사흘째 대기
출금 처리 워커의 매핑 함수를 열었다.
private function mapSignature($withdrawal)
{
$sig = $this->findSignature($withdrawal->custody_id);
if (!$sig) {
return ['result' => 'pending'];
}
...
}
findSignature 가 빈 값을 내면 그대로 대기를 반환한다.
이 건은 custody_signatures 쪽 대응 항목이 애초에 없는 상태였다. $sig 가 나중에 생기기를 기다리는 구조라서 끝이 없었다.
원인 — 시도 횟수가 안 늘었다
대기로 빠지는 자리를 더 봤다.
if ($result['result'] === 'pending') {
$withdrawal->status = 'pending';
$this->withdrawals->save($withdrawal);
return; // 시도 횟수 증가 없음
}
$withdrawal->status 만 pending 으로 바꾸고 시도 횟수는 건드리지 않은 채 빠져나간다.
보통은 시도가 쌓이면 한도를 넘어 실패로 빠지고 그때 알림이 나가거나 목록에 잡힌다. 여기서는 한도에 닿을 길이 없으니 아무 신호 없이 영원히 대기였다.
전체 흐름 — 왜 없는지의 경위
대응 항목이 왜 없는지 이력을 역추적했다.
1. 출금 요청 → 위탁 지갑에 서명 요청 생성
2. 그 요청이 사용자 취소로 지갑 쪽에서 폐기됨
3. 사용자가 같은 금액으로 출금을 다시 요청
4. 우리 쪽이 옛 custody_id 를 재사용 → 서명 레코드가 없음
5. 매핑 실패 → 영원히 대기
두 번째 줄의 사용자 취소 가 관건이었다.
서명 요청이 폐기되면 거기에 딸린 custody_signatures 행도 함께 정리된다. 그 뒤에 재요청이 옛 custody_id 를 물고 들어오면 새 요청에는 서명이 없는 상태로 넘어온다.
검증 — 같은 패턴을 세었다
이 건 하나만인지 확인했다.
대기 상태로 방치 몇 건
오류 상태 수백 건
대기 쪽에 수개월 방치된 것들이 섞여 있었다.
오류 는 그래도 집계에 잡히는데 대기 는 아무 데도 안 잡힌다. 정체된 건을 만나면 그 항목에 대응하는 레코드가 있는지부터 본다는 순서를 그때 정했다.
SELECT COUNT(*) FROM custody_signatures WHERE custody_id = ?;
COUNT(*) 가 0이면 이 유형이다.
선택지 — 세 가지 해결 방향
수동 해결 방향을 셋으로 정리했다. 후보는 원인 쪽 정리와 상태 전환과 레코드 생성이다.
① 지갑 쪽에서 요청을 새로 발급 원인 쪽을 정리한다
② 상태를 오류로 강제 전환 대기에서 빼서 목록에 잡히게 한다
③ 수동으로 서명 레코드 생성 없는 것을 만들어 진행시킨다
① 이 가장 깨끗하지만 위탁 지갑 관리 화면에서 해야 한다.
③ 이 가장 위험했다. 서명이 없는 데에는 이유가 있는데 그 이유를 모른 채로 만들면 보내지 않은 것을 보낸 것처럼 기록하는 셈이다.
대응 — 보이게 만드는 것이 먼저
② 를 골랐다. 이유는 ① 은 지갑 쪽 협조가 필요하고 ③ 은 없는 데이터를 만드는 것인데 ② 는 우리 안에서 되고 보이게 만들어서다.
① 은 지갑 쪽 협조가 필요
③ 은 없는 데이터를 만드는 것
② 는 우리 안에서 되고, 보이게 만든다
대기 는 보이지 않는 것이 문제라서 오류 로 바꾸면 최소한 집계에 잡힌다.
대기 라는 말은 곧 될 것이라는 뜻으로 읽힌다. 될 조건이 영영 안 생기는 건이 그 상태로 남아 있으면 그 표시 자체가 거짓 신호다.
근본은 따로 적어 두었다.
대응 레코드 없음
↓
시도 횟수 증가
↓
한도 초과 시 오류로 전환
곧 될 대기와 영영 안 될 대기를 시도 횟수로 가르는 것이다.
몇 번 시도해도 안 되면 그것은 대기가 아니라 문제다. 그 구분이 코드에 없으면 상태 이름이 실제 상황과 어긋난 채로 남는다.
정리
- 대응 레코드가 없으면 대기 상태로 영원히 멈출 수 있다
- 기다림에 끝이 없는 구조가 만들어진다
- 시도 횟수가 안 늘면 한도로 못 빠지고 신호가 없다
- 오류는 집계에 잡히지만 대기는 안 잡힌다
- 이력을 역추적하면 왜 없는지 시나리오가 나온다
- 정체되면 대응 레코드 존재 여부를 먼저 확인한다
- 해결은 원인 정리와 오류 전환과 수동 생성 셋이다
- 없는 레코드를 만드는 것이 가장 위험하다
- 오류 전환이 현실적이고 최소한 보이게 된다
- 대기는 곧 될 것이라는 뜻이라 영영 안 되면 거짓 신호다
- 근본은 시도 횟수를 늘려 한도로 빠지게 하는 것이다