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

만료 lease reclaim

~11 min · reclaim, atomicity, reconciliation, orphan-state

Level 0Empty Shelf
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"claim 이 건드린 건 뭐든, reclaim 이 되돌려야 해 — 전부, atomic 하게."

heartbeat 가 멈춘다

worker 가 전사 도중에 죽어. 정전이든 크래시든 누가 기계를 재시작했든. heartbeat 가 멈추고, lease 가 다 돼. 이제 control plane 이 치워야 하는데, 미묘하고 잔인한 버그가 사는 데가 바로 여기야.

뻔한 정리는 이래. lease 가 만료되면 job 을 queued 로 되돌려서 다른 worker 가 claim 하게. Recall 도 정확히 그렇게 하고, 새 claim 이 허락되기 전에 해서 stale lease 로 이중 claim 이 나지 않게 해. 근데 'job 을 queued 로 되돌리기' 는 이야기의 절반일 뿐이고, 빠진 나머지 절반이 사람을 무는 놈이야.

claim 이 둘을 건드렸으니, reclaim 도 그래야 해

claim 이 뭘 했는지 다시 떠올려 봐. job 을 queued 에서 leased 로 옮겼고, 동시에 그 job 의 batch item 도 queued 에서 transcribing 으로 옮겼어. 행이 두 개 바뀐 거야. 그러니까 reclaim 이 job 만 되돌리면 batch item 은 영영 'transcribing' 이라고 말한 채로 남아. 정작 전사하는 사람은 아무도 없는데. queue 자체는 기술적으로 맞아. job 은 돌아왔고 다른 worker 가 가져갔으니까. 근데 batch 의 진행 상황 화면은 영영 거짓말을 해. 끝나지 않는 item, 허구 위에서 계산된 ETA, 살아 있는 것처럼 보이지만 아닌 batch 를 노려보는 operator.

그래서 Recall 의 reclaim 은 job 그 batch item 을 둘 다 atomic 하게 queued 로 되돌려. 트랜잭션 하나, 행 두 개, 전부 아니면 전무. 이 단어가 중요해. 한 행을 되돌리고 다른 행 차례가 오기 전에 크래시하는 reclaim 은 자기가 치우려던 바로 그 orphan 을 다시 만들거든. 일반 규칙은 대칭이야. claim 이 바꾸는 상태는 뭐든 reclaim 이 atomic 한 한 단계로 되돌려야 해. 절반만 하는 reclaim 은 작은 해결책이 아니라, 정리인 척하는 새 불일치야.

그리고 틈새로 크래시한 걸 치유해

atomic reclaim 을 해도 틈이 하나 남아. 'job 완료' 와 'batch item 갱신' 사이의 좁은 창에 떨어지는 크래시. job 은 진짜로 끝났는데 그 item 은 여전히 transcribing 으로 읽혀. 그리고 그걸 고쳐줄 lease 만료도 없어. job 이 더는 leased 가 아니니까. 그래서 Recall 은 안전망을 하나 더 붙여. 시작할 때랑 job 을 claim 할 때 reconcile 을 돌려서, batch item 이 중간에 붕 뜬 완료 job 을 찾아 고쳐. 이게 reconciliation loop 의 본능이야. 모든 전이가 완벽하게 짝지어졌다고 믿는 대신, 주기적으로 현실과 기록이 주장하는 걸 맞춰보고 차이를 치유하는 거지. lease 는 예상한 크래시를 다루고, reconciliation 은 예상 못 한 크래시 창을 다뤄.

Code

두 행을 atomic 하게 reclaim, 그다음 크래시 창을 치유·python
# claim 이 두 가지를 변형했다:
#   job:  queued -> leased
#   item: queued -> transcribing

# 그러니 reclaim 은 둘 다, 한 트랜잭션에서 복원해야 한다.
with db.transaction():                 # 전부 아니면 전무
    job.status = 'queued'
    job.lease_token = None
    item.status = 'queued'             # <-- 이걸 잊으면 item 이
                                       #     worker 도 없이 영영
                                       #     'transcribing' 이라 함

# reclaim 은 새 claim 이 허락되기 전에 돌아서, 만료된
# lease 가 절대 이중 claim 되지 않는다.

# 'job 완료' 와 'item 갱신' 사이 크래시 창의 안전망:
# 시작 시와 claim 마다 reconcile.
for job in db.completed_jobs_with_item_in_transcribing():
    repair(job.batch_item)   # 현실 vs 기록 -> 틈 치유

External links

Exercise

네 시스템에서 claim 이나 lock 이나 checkout 을 찾아서, 그게 바꾸는 행과 field 를 전부 나열해 봐. 주 record 도, 다른 데 있는 status 와 counter 와 진행 field 도. 그다음 정리 경로를 읽고 확인해. 그걸 전부, 트랜잭션 하나 안에서 되돌려? 그리고 네 atomicity 가 못 덮는 크래시 창을 찾아서, 시작할 때 그걸 치유할 reconciliation pass 를 스케치해.
Hint
질문 두 개면 버그가 잡혀. 하나, claim 이 쓰는 걸 전부 나열하고 해제 경로가 그걸 다 atomic 하게 되돌리는지 확인해. 흔히 빠뜨리는 건 다른 테이블에 있는 진행/status 행이야. 둘, 같은 트랜잭션에 안 묶인 두 전이 사이에서 프로세스가 죽으면 어떻게 되는지 물어봐. 아무도 못 알아챌 것 같으면 현실과 기록을 맞춰보는 reconciliation pass 가 필요한 거야.

Progress

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

댓글 0

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

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