"이 응답엔 이미 결제했어. 잃으면 다시 사는 거야. 그러니 뭘 하기 전에 먼저 저장해."
마지막 틈: 결제했는데, 보고를 못 해
happy path 를 마지막 단계까지 따라가 보자. executor 가 예약하고, 제출하고, provider 한테서 좋은 응답을 받았어. 돈은 이미 썼고. 여기가 핵심인데, 그 결과는 이제 진짜로 값이 나가. 얻는 데 돈이 들었으니까. 다음 할 일은 그 결과를 durable 하게 수락받으려고 control plane 에 건네는 거야. 마지막 틈이 바로 거기 있어. 하필 그 순간에 control plane 에 잠깐 못 닿으면? 네트워크가 깜빡하거나, control plane 이 재시작 중이거나, Wi-Fi 가 한 번 흔들리거나. executor 는 배달을 못 하는 결제된 응답을 손에 쥐고 있게 돼.
executor 가 그 응답을 떨궈버리면, 그러니까 크래시하거나 포기하거나 그냥 넘어가면 결과가 사라지고, 되찾는 방법은 다시 결제하는 것뿐이야. 이 트랙 내내 막아온 바로 그 실패가 결승선에서 다시 나타나는 거지. 예약 ledger 가 지출을 지켰다면, 이제는 뭔가가 이미 사버린 응답을 지켜야 해.
확인하기 전에 로컬에 저장해
Recall 의 답은 outbox 야. 성공한 provider 응답이 도착하는 즉시, executor 가 그걸 로컬 outbox 파일에 atomic 하게 써. control plane 에 완료를 알리려고 하기 전에. 로컬 사본이 디스크에 안전하게 놓인 다음에야 보고를 시도하는 거지. 이제 이 순서가 생존을 보장해.
- control plane 에 못 닿는다? 결제된 응답은 outbox 에 안전해. 연결이 돌아오면 executor 가 replay 하고, 두 번째 구매는 없어.
- executor 가 결제하고 보고하기 전에 크래시한다? 재시작하면 outbox 가 응답을 그대로 쥐고 있어서 바로 제출하면 돼.
- control plane 이 자기 ledger 에 응답을 durably 기록했다고 확인해준다? 그제야 executor 가 outbox 사본을 지워. 가치가 다른 데 안전하게 놓였으니 로컬 사본은 더 필요 없거든.
이건 store-and-forward 패턴이고, 예약을 거울에 비친 모양이야. 예약은 지출 전에 의도를 기록하고, outbox 는 지출 뒤에 결과를 보존해. 둘이 유료 호출을 양쪽에서 괄호로 묶어주니까, 어느 쪽에서 크래시가 나도 두 번 결제할 일이 없어.
두 번 결제 안 하는 것의 모양
한 발 물러서서 이 트랙 전체를 하나의 장치로 봐. 예약은 지출이 일어나기 전에 그걸 눈에 보이게 만들어. content-addressed identity 는 같은 구매를 알아보게 해서 한 번만 사게 하고. ambiguous 상태와 no-retry 규칙은 추측이 돈을 쓰는 자리에서 추측을 거부해. 그리고 outbox 는 이미 결제한 응답이 일시적인 outage 때문에 사라지지 않게 지켜. 조각마다 크래시 창을 하나씩 닫고, 다 합치면 'pay once' 가 희망에서 기계가 강제하는 불변식으로 바뀌어. 시스템 설계에서 돈을 일급 관심사로 다룬다는 게 이런 뜻이야.