끝난 작업을 착지시킨다는 건 넷이야. 커밋하고, 밀어 올리고, 자료 곁에 사는 기록에 도장 찍고, 이 일이 벌어졌다고 말하는 로그 항목을 쓰고. 하나씩 떼놓으면 특별할 게 없어. 중요한 건 그 사이에 다른 착지가 끼어들면 안 된다는 거야.
다른 세션이 네 커밋이랑 도장 사이에 착지를 끼워넣으면, 저장소랑 기록이 이제 서로 다른 역사를 서술하는데 둘 중 어느 것도 그렇다고 말하지 않아. 최악의 데이터 문제 부류지. 조용하고, 그럴듯하고, 몇 달 뒤에 상관없는 질문에 답하려던 사람한테 발견돼.
리스가 뭘 사주는지는 정확히 말해야 해. 넘겨 말하기 쉽거든. 리스는 다른 착지를 배제해. 네 단계를 원자적으로 만들어주진 않아. 그리고 그 차이가 한가한 얘기가 아닌 게, 네 프로세스가 밀어 올린 다음 기록 둘 중 어느 것도 쓰기 전에 죽으면 남 하나 없이도 똑같이 갈라진 역사가 나와. 직렬화는 트랜잭션성이 아냐. 남은 그 틈을 닫는 건 더 큰 잠금이 아니라 대조 패스야. 저장소랑 기록을 나란히 걸으면서 둘이 어긋난 자리를 보고하는 화면이지.
리스, 그리고 그게 잠금이 아닌 이유
기제는 그 네 단계 동안 대상 저장소에 대해 쥐는 리스야. 잠금 대신 리스라고 부르는 건 용어 까탈이 아냐. 중요한 성질에 이름을 붙이는 거야. 안 돌아오는 쥔 쪽을 떼어낼 수 있다는 성질.
잠금은 해제될 때까지 쥐어져. 그걸 쥔 채로 죽은 세션은 영구 장애를 만들었고, 복구는 사람이 낡은 잠금을 찾아서 깨도 되겠다고 판단하는 거야. 리스는 해제될 때까지, 아니면 누가 일부러 끊고 그 끊은 사실까지 기록될 때까지 쥐어지고, 그러면 "쥔 쪽이 안 돌아왔다"가 사건에서 지연으로 바뀌어. 그 쥔 쪽이 덮일 수 있는 노트북 위의 대화형 세션이라는 걸 감안하면, 이 구별은 이론이 아니라 하중을 받아.
검증도 이 동작의 일부야
결합 하나가 더 여기 속해. 착지는 이 작업 단위에 대해 기록된 통과 검증 없이는 진행을 거부해. 문서 속 권고가 아냐. 명령이 그 이벤트를 찾고 없으면 종료해.
무시 수단이 있고, 쓰면 그 사실이 기록에 적혀. 그 조합이 어디서든 겨냥할 모양이야. 탈출구는 있어야 해. 무시 수단 없는 가드는 애매한 시간에 처음 틀린 순간 제거되니까. 그리고 그걸 쓰는 게 절대 안 보이지 않아야 하고.
검증을 왜 착지 명령 안에 넣냐고 물을 수 있어. 별도 단계로 두고 사람이 순서를 지키면 되잖아. 그게 정확히 앞 트랙에서 삭아버린 그 배치야. 검증이 착지랑 별개면 건너뛰는 비용이 0 이고, 건너뛰는 비용이 0 인 단계는 결국 건너뛰어져. 순서를 지켜달라고 부탁하는 대신, 순서를 안 지키면 다음 걸 못 하게 만드는 거야.
서로 일치해야 하는 단계들을 쥔 리스 하나 아래로 묶고, 그 리스를 기록 남기고 끊을 수 있게 만들어. 묶는 게 기록 둘이 다른 이야기를 하는 걸 막고, 끊을 수 있다는 게 덮인 노트북 하나 때문에 누가 조사하러 올 때까지 나머지 전부의 작업이 멈추는 걸 막아.
Code
착지, 결합이 보이게 적은 것·python
def land(unit_id: int, note: str, force_unverified: bool = False):
# 1. TEETH: no green verification, no landing. The override is
# itself recorded - possible, never accidental.
if not has_passing_verification(unit_id) and not force_unverified:
raise SystemExit("no passing verification logged - run it first")
# 2. THE LEASE. Held across everything below. The holder is an
# interactive session on a laptop that can be closed mid-run,
# so recovery is an explicit break that LEAVES A RECORD - not a
# timer, which would also release a holder that is merely slow.
lease = acquire_lease(repo="content")
try:
base = choose_base(repo) # lesson 4 of this track
sha = scoped_commit(repo, base, # lesson 3 of this track
paths=pipeline["land_paths"], msg=note)
push(repo)
stamp_record(unit_id, sha, note) # lives WITH the material
write_log_entry(unit_id, "landed", commit=sha)
finally:
release_lease(lease)
# Why all four are inside: if another landing could interleave
# between `sha = ...` and `stamp_record(...)`, the repository and
# the record would describe different histories, and NEITHER of
# them would say so. Silent, plausible, found months later.
네 시스템에서 단계마다 다른 저장소에 쓰는 다단계 작업을 찾아봐. 데이터베이스랑 검색 색인, 저장소랑 티켓, 배포랑 공지. 단계 쌍마다 그 사이에서 작업이 멈췄을 때 독자가 뭐라고 결론 내릴지 적어. 그다음 그 중간 상태 중 뭘 견딜 수 있고 뭘 쥔 자원 하나 아래로 묶어야 하는지 정해.
Hint
위험한 중간 상태는 완료된 것처럼 보이는 쪽이야. 눈에 띄는 난장판을 남기는 반쯤 끝난 작업은 고쳐져. 저장소 둘을 각각 내부적으로는 일관되게, 서로는 모순되게 남기는 작업은 양쪽 다 몇 달 동안 믿어져.
Progress
Progress is local-only — sign in to sync across devices.