지원을 끝낸 코인의 입금 주소 20만 건을 해제해 달라는 요청을 받고 배치 엔드포인트가 있는지 찾았다.
Table of contents
Open Table of contents
증상 — 비슷한 이름 셋
경로 파일을 뒤졌다.
rg 'batch.*release' routes.php
후보는 셋이었다.
/wallet/batch/address-release
/wallet/batch/address-release-and-pending-v2
/wallet/v2/force-release
address-release 와 force-release 가 무엇이 다른지 경로만으로는 갈리지 않았다.
원인 — 이름으로 골랐다
두 번째가 맞아 보였다.
address-release-and-pending-v2
v2 가 붙어 새 지갑 연동용이고 release 까지 들어 있어 이것으로 하겠다고 했다.
그 자리에서 지적을 받았다. 실제 컨트롤러 본문을 확인하지 않았다는 것이었고 경로 파일만 봤을 뿐 그 경로가 어느 메서드를 부르는지도 안 봤다.
검증 — 셋을 다 읽었다
routes.php 가 가리키는 컨트롤러를 하나씩 열었다.
public function batchRelease(array $ids)
{
foreach ($ids as $id) {
$this->service->releaseAddressV1($id); // ← 옛 지갑 연동
}
}
첫 번째는 releaseAddressV1 을 부르는 옛 연동 전용이라 위탁 지갑 대상에는 적용되지 않는다.
두 번째인 batchReleaseAndPendingV2 가 내가 고른 것이었다.
public function batchReleaseAndPendingV2(array $ids)
{
foreach ($ids as $id) {
$this->service->releaseV2($id);
}
}
releaseV2 가 무엇을 하는지 그 아래까지 따라갔다.
public function releaseV2($id)
{
$result = $this->sendReleaseToCustody($id); // 위탁 지갑에 요청 발송
if ($result->isSuccess()) {
$this->afterRelease($id); // 후처리
}
}
sendReleaseToCustody 로 지갑 쪽에 요청을 보내고 그것이 성공해야 afterRelease 가 돈다.
세 번째 forceReleaseV2 는 달랐다.
public function forceReleaseV2($id)
{
// 지갑 호출 없음
$this->afterRelease($id); // 바로 후처리
}
forceReleaseV2 는 지갑 호출 없이 바로 afterRelease 로 간다.
판단 기준 — 외부에 없는 대상
이번 대상은 위탁 지갑 쪽에 이미 없는 주소들이었다. 지갑 서비스가 그 코인 지원을 끝내며 먼저 폐기한 것이다.
요청 발송 → "그런 주소 없음" → 실패
↓
후처리가 실행 안 됨
두 번째로 돌리면 sendReleaseToCustody 가 반드시 실패해서 아무것도 정리되지 않는다.
세 컨트롤러를 나란히 놓으니 갈렸다.
① 옛 연동 전용 → 대상 불일치
② 지갑 요청 필요 → 실패해서 후처리 안 됨
③ 지갑 호출 없음 → 적합
이름으로는 두 번째가 맞아 보이는데 두 번째 대신 세 번째를 골랐다. 이유는 지갑 쪽에 없는 주소에 대해 실제로 되는 것은 세 번째뿐이라서다.
제약 — 본문에서 함께 나온 상한
세 컨트롤러를 읽으면서 공통점도 봤다.
foreach ($ids as $id) { ... }
셋 다 foreach 동기 반복이고 $ids 크기에 상한이 없었다.
20만 건을 한 번에 보내면 요청이 시간 초과로 끊기거나 메모리가 넘친다. 그래서 배치를 나눠 보내도록 계획을 고쳤는데 이것도 본문을 읽어야 나오는 제약이었다.
재발 방지 — 본문까지 읽는 규칙
경로 파일 검색으로 아는 것에는 경계가 있었다.
[알 수 있는 것] 이런 엔드포인트가 있다
[알 수 없는 것] 뭘 하나 / 어떻게 하나 / 제약이 뭔가
release 라는 말이 셋에 다 들어 있을수록 위험한데 이름으로 고르게 되기 때문이다.
게다가 afterRelease 가 한 일은 되돌리기 어렵고 이번에는 20만 건이었다. 두 번째로 돌렸으면 전부 실패하고 왜 아무것도 안 됐는지 다시 조사했을 것이고 일부가 성공했으면 부분 처리 상태가 남았을 것이다.
그래서 엔드포인트를 고를 때 경로 파일만 보지 않고 컨트롤러 본문과 그것이 부르는 서비스 메서드까지 읽는 것을 규칙으로 적었다. 이름이 비슷한 것이 여럿이면 반드시 그렇게 한다.
정리
- 이름이 비슷한 엔드포인트가 완전히 다르게 동작할 수 있다
- 경로 파일 검색은 무엇이 있는지까지만 알려 준다
- 컨트롤러 본문과 그것이 부르는 서비스 메서드까지 읽는다
- 후처리가 지갑 요청 성공에 의존하면 대상이 없을 때 안 된다
- 지갑 쪽에 실재하지 않는 대상은 강제 경로만 적합하다
- 본문을 읽으면 건수 상한 같은 제약도 같이 나온다
- 되돌리기 어려운 작업일수록 고르는 단계에 시간을 쓴다
- 이름으로 고르는 것이 가장 흔한 실수다