"control plane 이 durably 받아들이기 전엔 계산이 안 끝나."
시스템에서 가장 중요한 한 문장
Recall 의 분산 설계는 전부 규칙 하나로 압축돼. executor 는 transcript 계산을 끝낼 수 있어. 함수가 return 했고, 파일이 디스크에 있고, 로그에도 성공이라고 찍혀. 근데 그 어느 것도 job 이 완료됐다는 뜻은 아냐. 완료는 일을 한 기계가 아니라 진실의 원천이 가진 속성이거든. job 은 control plane 이 결과를 받아들였다고 durably 기록했을 때, 오직 그때만 끝나.
왜 이렇게까지 엄격하냐고? 'executor 가 끝냈다' 와 'control plane 이 안다' 사이에는 네트워크가 있고, 네트워크는 실패하거든. executor 가 완벽한 결과를 계산하고 나서 보고하기 전에 연결을 잃을 수도 있어. 그걸 '완료' 라고 부르면 진실이랑 실제 일이 조용히 어긋나. executor 는 끝났다고 여기고, DB 는 job 이 아직 pending 이라고 여기고. 최악의 버그는 그 틈에서 살아.
끝내는 건 값싸고, 수락은 durable 해
그래서 executor 가 느끼는 로컬 '완료' 는 사실이 아니라 제안으로 다뤄. 결과를 계산하고, control plane 에 제출하고, control plane 이 검증한 다음 상태 전이를 한 번의 atomic DB write 로 커밋해. 그 커밋만이 완료야. return 한 함수는 소문이고, 커밋된 전이는 진실이야.
이 규칙 하나가 전부를 버티게 만들어. executor 가 계산을 끝내고 보고하기 전에 죽으면, control plane 은 수락을 본 적이 없으니까 lease 가 만료되고 job 이 queue 로 돌아가서 다시 돌아. control plane 이 커밋한 다음에 죽으면 job 은 진짜로 끝난 거라 다시 안 돌고. 중요한 순간은 durable 커밋 하나뿐이고, 그건 진실을 소유하는 호스트에서 일어나.
replay 는 안전해야 해
미묘한 경우가 하나 있어. executor 가 계산하고 control plane 이 커밋했는데, executor 로 돌아가는 확인(ack)이 중간에 유실되면? executor 는 성공한 걸 모르니까 재시도하고, 같은 결과가 두 번 도착해. 시스템은 그걸 무해하게 만들어야 해. Recall 은 identity 로 그걸 해결해. 이미 받아들인 결과를 다시 제출하면 중복을 만드는 대신 기존 record 를 돌려주거든. 수락이 idempotent 하니까 재시도는 언제나 안전해. 거기다 control plane 은 재시작할 때 크래시가 중간에 남겨둔 job 을 화해(reconcile) 시켜. 수락은 한 번, replay 는 안전하게, 시작할 때 치유하기. 이 세 가지가 불안정한 네트워크를 사이에 두고도 두 호스트가 서로 정직하게 남는 방법이야.