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