"'결제했을지도 모르는데 손에 아무것도 없다' 는 진짜 상태야. 이름을 줘."
대부분의 시스템이 모델링을 거부하는 상태
유료 제출은 작은 상태 기계를 지나: 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 청구) 접근권 가진 사람을 기다림 — 하는 게 돈을 정직하게 유지해. 기계가 알 수 없을 때, 맞는 수는 자신만만한 추측이 아냐; 사람이 해결할 수 있는 명시적 '난 몰라' 야.