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

리스 하나 아래 동작 하나

~12 min · concurrency, leases, records, design

Level 0젖은 흙
0 XP0/38 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

뒤엉키면 안 되는 네 단계

끝난 작업을 착지시킨다는 건 넷이야. 커밋하고, 밀어 올리고, 자료 곁에 사는 기록에 도장 찍고, 이 일이 벌어졌다고 말하는 로그 항목을 쓰고. 하나씩 떼놓으면 특별할 게 없어. 중요한 건 그 사이에 다른 착지가 끼어들면 안 된다는 거야.

다른 세션이 네 커밋이랑 도장 사이에 착지를 끼워넣으면, 저장소랑 기록이 이제 서로 다른 역사를 서술하는데 둘 중 어느 것도 그렇다고 말하지 않아. 최악의 데이터 문제 부류지. 조용하고, 그럴듯하고, 몇 달 뒤에 상관없는 질문에 답하려던 사람한테 발견돼.

리스가 뭘 사주는지는 정확히 말해야 해. 넘겨 말하기 쉽거든. 리스는 다른 착지를 배제해. 네 단계를 원자적으로 만들어주진 않아. 그리고 그 차이가 한가한 얘기가 아닌 게, 네 프로세스가 밀어 올린 다음 기록 둘 중 어느 것도 쓰기 전에 죽으면 남 하나 없이도 똑같이 갈라진 역사가 나와. 직렬화는 트랜잭션성이 아냐. 남은 그 틈을 닫는 건 더 큰 잠금이 아니라 대조 패스야. 저장소랑 기록을 나란히 걸으면서 둘이 어긋난 자리를 보고하는 화면이지.

리스, 그리고 그게 잠금이 아닌 이유

기제는 그 네 단계 동안 대상 저장소에 대해 쥐는 리스야. 잠금 대신 리스라고 부르는 건 용어 까탈이 아냐. 중요한 성질에 이름을 붙이는 거야. 안 돌아오는 쥔 쪽을 떼어낼 수 있다는 성질.

잠금은 해제될 때까지 쥐어져. 그걸 쥔 채로 죽은 세션은 영구 장애를 만들었고, 복구는 사람이 낡은 잠금을 찾아서 깨도 되겠다고 판단하는 거야. 리스는 해제될 때까지, 아니면 누가 일부러 끊고 그 끊은 사실까지 기록될 때까지 쥐어지고, 그러면 "쥔 쪽이 안 돌아왔다"가 사건에서 지연으로 바뀌어. 그 쥔 쪽이 덮일 수 있는 노트북 위의 대화형 세션이라는 걸 감안하면, 이 구별은 이론이 아니라 하중을 받아.

검증도 이 동작의 일부야

결합 하나가 더 여기 속해. 착지는 이 작업 단위에 대해 기록된 통과 검증 없이는 진행을 거부해. 문서 속 권고가 아냐. 명령이 그 이벤트를 찾고 없으면 종료해.

무시 수단이 있고, 쓰면 그 사실이 기록에 적혀. 그 조합이 어디서든 겨냥할 모양이야. 탈출구는 있어야 해. 무시 수단 없는 가드는 애매한 시간에 처음 틀린 순간 제거되니까. 그리고 그걸 쓰는 게 절대 안 보이지 않아야 하고.

검증을 왜 착지 명령 안에 넣냐고 물을 수 있어. 별도 단계로 두고 사람이 순서를 지키면 되잖아. 그게 정확히 앞 트랙에서 삭아버린 그 배치야. 검증이 착지랑 별개면 건너뛰는 비용이 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.

External links

Exercise

네 시스템에서 단계마다 다른 저장소에 쓰는 다단계 작업을 찾아봐. 데이터베이스랑 검색 색인, 저장소랑 티켓, 배포랑 공지. 단계 쌍마다 그 사이에서 작업이 멈췄을 때 독자가 뭐라고 결론 내릴지 적어. 그다음 그 중간 상태 중 뭘 견딜 수 있고 뭘 쥔 자원 하나 아래로 묶어야 하는지 정해.
Hint
위험한 중간 상태는 완료된 것처럼 보이는 쪽이야. 눈에 띄는 난장판을 남기는 반쯤 끝난 작업은 고쳐져. 저장소 둘을 각각 내부적으로는 일관되게, 서로는 모순되게 남기는 작업은 양쪽 다 몇 달 동안 믿어져.

Progress

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

댓글 0

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

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