"'결제했을지도 모르는데 손에 아무것도 없다' 는 진짜 상태야. 이름을 줘."
대부분의 시스템이 모델링을 거부하는 상태
유료 제출은 작은 상태 기계를 지나가. preparing → submitting → completed. 깔끔해 보이지. 근데 대부분의 설계가 없는 척하는 갈림길이 하나 있고, 하필 그게 돈이 드는 놈이야. executor 가 submitting 에 있다고 해보자. 요청은 보냈는데 응답이 오기 전에 연결이 죽었어. 지금 상태가 뭐야?
진짜 몰라. provider 가 요청을 받아서 전사를 돌리고 청구까지 했는데 응답만 집에 못 온 걸 수도 있고, 요청이 아예 도착도 안 한 걸 수도 있어. Recall 이 서 있는 자리에서 그 두 세계는 구별이 안 되는데, 재정적 결과는 정반대야. 성공인 척하면 결과를 잃고, 깔끔한 실패인 척하면 이중 청구가 날 수 있는 재시도를 부르고. 정직한 답은 이거야. 이건 세 번째 상태고, 자기 이름이 필요해.
추측하지 말고 이름 지어
Recall 은 그걸 ambiguous 라고 부르고, 해결될 때까지 terminal 인 진짜 상태로 모델링해. 규칙은 이래.
completed는 새 호출 없이 저장된 응답을 돌려줘. 이미 결제가 끝난 안전한 경로야.- executor 의 outbox 에 provider 응답이 저장돼 있는
submitting은 그 저장된 응답으로 완료돼(다음 레슨). 새로 청구되는 건 없고. - 저장된 응답이 없는
submitting은ambiguous야. 청구됐을 수도 있고 아닐 수도 있는데, 손에 결과는 없는 상태.
그리고 결정적 규칙: ambiguous 호출은 절대 자동 재시도 안 해. blind 재시도는 진짜 돈을 건 동전 던지기거든. 앞면이면 필요한 재시도였고, 뒷면이면 방금 두 번 결제한 거야. 그래서 Recall 은 거기서 멈추고 불확실성을 사람한테 넘겨. 사람이 provider 의 실제 청구 기록을 확인해서 두 세계 중 어느 쪽이 진짜인지 알아내고, 호출을 명시적으로 해결해. 청구 안 됨(다시 해도 안전), 청구됐는데 결과는 잃음(손실 인정, 공짜로 다시 안 함), 청구됐고 진짜로 다시 결제해야 함(별도의 명시적 승인). 추측이 돈을 쓰는 자리에서는 기계가 추측을 거부하는 거야.
왜 일급 상태가 최선의 추측을 이겨
코드 분기를 줄이려고 모호함을 성공이나 실패 한쪽으로 접어버리고 싶은 유혹이 늘 있어. 그 깔끔함이 정확히 버그고. '아마 실패했겠지, 재시도하자' 로 접은 불확실성은 조용히 이중 지출을 승인해. '아마 괜찮겠지' 로 접은 불확실성은 조용히 데이터를 잃고. ambiguous 상태에 자기 이름을 주고 그대로 모델링하는 게 돈을 정직하게 지키는 방법이야. 한쪽에 세워두고, 재시도하지 않고, 기계한테는 없는 ground truth(provider 청구 기록) 를 볼 수 있는 사람을 기다리는 거지. 기계가 알 수 없을 때 맞는 수는 자신만만한 추측이 아냐. 사람이 해결할 수 있는 명시적인 '난 몰라' 야.