본문 바로가기
C.W.K.
Stream
Lesson 05 of 05 · published

outbox

~10 min · outbox, store-and-forward, durability, replay

Level 0Empty Shelf
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"이 응답엔 이미 결제했어. 잃으면 다시 사는 거야. 그러니 뭘 하기 전에 먼저 저장해."

마지막 틈: 결제했는데, 보고를 못 해

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' 가 희망에서 기계가 강제하는 불변식으로 바뀌어. 시스템 설계에서 돈을 일급 관심사로 다룬다는 게 이런 뜻이야.

Code

확인 전에 결제된 응답을 outbox 에 저장·python
response = provider.transcribe(chunk)   # 돈 이미 씀

# 1. 결제된 결과를 먼저 로컬에 보호 — atomic write.
outbox.save_atomic(submission_id, response)

# 2. 이제서야 control plane 에 건네려 시도.
try:
    control_plane.complete(submission_id, response)
except Unreachable:
    pass  # 괜찮음: outbox 가 쥐고 있음; 나중에 replay, 재구매 없음

# 재연결 / 재시작 시: outbox 에 남은 걸 replay.
for pending in outbox.list():
    control_plane.complete(pending.id, pending.response)

# control plane 이 ledger 에 durable 하다고 확인 -> 그때 삭제.
outbox.remove(submission_id)

External links

Exercise

네 일에서, 비싸거나 다시 만들기 힘든 걸 얻자마자 어딘가로 보내는 데를 찾아봐. DB 에 저장하거나, 서비스에 POST 하거나, enqueue 하거나. 그리고 물어봐. 그 전송이 실패하면 비싼 결과가 사라져? 그러면 outbox 를 설계해. 결과를 먼저 durable 한 로컬 저장소에 쓰고, 배달을 시도하고, 실패하면 replay 하고, 목적지가 수신을 확인한 뒤에야 로컬 사본을 지우는 거야. 그렇게 해서 없앤 재구매 비용도 적어보고.
Hint
취약한 모양은 '비싼 걸 계산하거나 사고, 그다음 보내고, 전송이 실패하면 사라짐' 이야. 중간에 durable 한 로컬 hop 을 하나 넣어. 보내기 전에 결과를 persist 해두면 실패한 배달이 재구매가 아니라 replay 가 돼. 로컬 사본은 수신이 확인됐을 때만 지우고. 그 확인이 곧 가치가 다른 데 안전하게 놓였다는 신호니까.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.