재시작된 스코어러가 등급을 게시하기 전에 프래그먼트 에포크를 불변하게 만들기
요약
본 글은 분산 시스템 환경에서 등급(scoring)을 계산하는 과정의 데이터 무결성 문제를 깊이 있게 다룹니다. 특히, 워커 재시작과 지연된 프래그먼트가 혼합될 때 발생하는 '에포크 불변성' 유지가 핵심 과제입니다. 단순히 시도 ID로 키를 지정하는 것만으로는 부족하며, 등급은 오직 하나의 에포크에서 온 프래그먼트로 구성되어야 함을 강조합니다.
핵심 포인트
- 스코어러 재시작 환경에서의 데이터 무결성 확보가 중요함.
- 단순한 시도 ID 기반 로그로는 충분하지 않으며, 에포크 단위의 불변성이 필요함.
- 지연된 프래그먼트와 활성 에포크 간의 혼합을 방지하는 프로토콜 설계가 핵심임.
- 이 문제는 분산 시스템 아키텍처 및 데이터 흐름 제약 조건에 대한 심층적인 검토를 요구함.
저는 이 리뷰를 위해 구축했던 그레이딩-레인(grading-lane) 테스트 케이스를 다시 실행해 봅니다. 왜냐하면 녹색 대시보드가 거짓된 등급을 숨길 수 있기 때문입니다. 한 워커가 프래그먼트 하나를 작성했고, 평가 서버가 재시작되었으며, 새로운 워커가 또 다른 프래그먼트 하나를 작성했습니다. 첫 번째 에포크의 지연된 프래그먼트 둘이 도착했고, 퍼블리셔는 어쨌든 그것을 추가했습니다. 이런 혼합된 점수를 카나리 게이트(canary gate) 안에서 신뢰하시겠습니까, 아니면 레인을 중단시키시겠습니까?
일반적인 구현 방식은 각 부분 점수(partial score)를 시도 ID(attempt id)로만 키가 지정된 추가 전용 로그(append-only log)로 처리합니다. 이 키만으로는 충분하지 않습니다. 왜냐하면 재시작이 여전히 같은 시도를 공유하는 새로운 생성을 만드기 때문입니다. 제가 원하는 불변성(invariant)은 아이디엠포턴트 쓰기(idempotent writes)보다 더 좁습니다: 등급은 오직 하나의 에포크에서 온 프래그먼트로만 구성될 수 있어야 합니다. 두 개의 에포크가 혼합된다면, 퍼블리셔는 지연된 프래그먼트를 거부해야 할까요, 아니면 등급을 보정해야 할까요?
제가 조용히 확장하지 않을 가정들
저는 여기서 아키텍처를 검토하고 있으며, 측정 가능한 프로덕션 장애나 지연 시간을 주장하는 것은 아닙니다. 하나의 시도 ID, 많은 프래그먼트, 최소 한 번 전달(at-least-once delivery), 그리고 실행 도중에 재시작할 수 있는 스코어러가 있다고 가정합니다. 무료 모델 호출은 원격이며, 재시작과 겹칠 만큼 충분히 느리고, 로컬 결정론적 함수가 아니라고 가정합니다. 현재 제품 약관을 다시 확인해야 합니다. 왜냐하면 저는 하드웨어, 지속 시간 또는 영구성에 대한 약속을 지어내지 않을 것이기 때문입니다.
공개: 이 기사는 MonkeyCode의 제품 아웃리치(product outreach)의 일부로 준비되었습니다. 저는 이 오픈 소스 프로젝트의 무료 모델 액세스와 무료 서버를 테스트 케이스의 원격 스코어링 평면으로 사용할 것입니다. 아웃리치 브리프에는 천만 토큰의 무료 할당량이 명시되어 있는데, 제가 직접 측정한 것은 아닙니다. 팬아웃(fan-out)을 크기 조정하기 전에 현재 문서에서 그 수치를 확인했습니까, 아니면 스크린샷을 기반으로 계획하고 있습니까?
제약 조건, 그리고 데이터 흐름
저는 이 검토를 한 번의 시도 스트림(attempt stream), 하나의 게시자(publisher), 그리고 하나의 원격 스코어링 워커(remote scoring worker)로 제한합니다. 여러 시도에 걸친 공정성, 의미론적 모델 품질, 다중 지역 합의는 모두 이 검토 범위 밖에 있습니다. 재시작은 프래그먼트 순서를 변경하거나, 시퀀스 번호를 중복시키거나, 클린 취소(clean cancel) 없이 워커를 드롭할 수 있습니다. 게시자만이 캐나리 게이트에 최종 등급을 보이게 할 수 있는 유일한 구성 요소입니다.
시도 저장소(attempt store)는 에포크를 발행하고, 큐(queue)는 이 에포크를 모든 프래그먼트에 담으며, 워커는 이를 반향해야 합니다. 무료 서버(free server)가 모델 호출을 실행할 수는 있지만, 충돌 후 새로운 에포크를 선택해서는 안 됩니다. 오래된 에포크를 가진 지연 프래그먼트는 활성 에포크 내의 중복과는 다른 실패 영역입니다. 시도 ID로만 키를 지정하는 쓰기(write)가 이 두 영역을 하나의 조용한 추가(silent append)로 붕괴시키는 이유를 아십니까?
sequenceDiagram
participant Store as Attempt store
participant Queue as Fragment queue
...
게시 전에 필요한 번호화된 프로토콜
저는 이 프로토콜이 지루하기를 원합니다. 왜냐하면 지루한 울타리(fence)만이 검토자들이 실제로 테스트할 수 있기 때문입니다. 다이어그램에만 존재하는 울타리는 주입하는 첫 번째 재시작 워커를 견디지 못할 것입니다. 그래서 저는 게시자 내부에 확인 절차를 넣었고, 큐 메시지 자체에도 에포크를 넣었습니다. 단일 게시자 검사만으로 이미 정상 경로(happy path)를 커버한다면 왜 두 곳 모두에 검사를 유지해야 할까요?
- 시도가 승인되면 새로운 에포크를 발행하고, 첫 번째 모델 호출이 큐에 추가되기 전에 해당 에포크를 영속화합니다.
- 워커가 내보내는 모든 프래그먼트(재시도 포함)에 시도 ID, 에포크, 그리고 시퀀스 번호를 스탬프 찍습니다.
- 도착하는 프래그먼트 중 에포크가 해당 시도에 대해 현재 저장된 열린 에포크와 같지 않은 것은 거부합니다.
- 반복되는 에포크 및 시퀀스 쌍은 두 번째 샘플로 추가하는 것이 아니라, 중복 승인(duplicate acknowledgement)으로 처리합니다.
- 예상되는 시퀀스 세트가 완전하고 등급 내부의 에포크 집합 크기가 1일 때만 발행합니다.
- 재시작 시에는 저장된 에포크를 증가시키고 이전 에포크에 속하는 모든 미발행 프래그먼트는 폐기합니다.
- 손실을 리트라이 카운터 내부에 숨기는 대신, 측면 로그(side log)에서 거부 사항을 노출하여 정지 게이트가 이를 확인할 수 있게 합니다.
펜스가 마법처럼 지워주지 못하는 실패 영역들
저는 하나의 재시도 정책이 이들을 뒤섞어 버릴 것이기 때문에 레인을 다섯 개의 실패 영역으로 분할했습니다. 모델 평면은 토큰을 이미 소모한 후에도 시간 초과될 수 있으며, 그 낭비는 잘못된 에포크와 같은 문제가 아닙니다. 워커 프로세스는 재시작되거나 선점(preempted)될 수 있는데, 이것이 바로 이 펜스가 실제로 목표로 하는 영역입니다. 큐는 순서를 바꾸거나 중복할 수 있고, 게이트가 펜스가 실행되기 전에 발행하는 경우에도 여전히 등급을 읽을 수 있습니다.
다음 박스를 그릴 때 놓치지 말아야 할 지독한 중첩 부분이 있습니다. 에포크 1의 좀비 워커는 에포크 2가 열린 후에도 모델 호출 안에 남아있을 수 있습니다. 이 펜스는 그 워커의 늦은 프래그먼트를 거부할 수는 있지만, 이미 프로세스를 떠난 호출 자체를 되돌릴 수는 없습니다. 이것이 등급의 정확성 버그인가요, 아니면 발행자 검사 내부에 숨겨서는 안 될 비용 누수(cost leak)일까요?
평면에 신뢰하기 전에 실행해 볼 수 있는 고정 장치(fixture)
이 다음 블록은 공유 서버에서 수집한 벤치마크가 아니라 실행되지 않은 로컬 fixture입니다. 이 상태 기계(state machine)를 원격 워커에 적용하기 전에 노트북에서 먼저 실행해 보시기 바랍니다. 어설션(asserts)은 속성을 인코딩하며, 녹색 출력은 단순히 fixture가 유지되었다는 의미일 뿐, 벤더 플레인(vendor plane)이 지속 가능하다는 것을 의미하지 않습니다. 만약 한 줄이라도 실패하면, 어떤 모델을 워커 뒤에 배치할지 논쟁하기 전에 이 울타리(fence)를 고치십시오.
#!/usr/bin/env python3
'''Local epoch-fence fixture. Not a product benchmark.'''
...
python3 epoch_fence.py
python3 -m py_compile epoch_fence.py
저는 이 명령이 단일 속성 라인을 출력할 것으로 예상하며, 후반부 프래그먼트(late fragment)가 rows가 아닌 rejected에 위치할 것으로 예상합니다. 만약 여러분의 래퍼(wrapper)가 원격 워커로 아웃소싱된다면, 이 프로세스를 퍼블리셔(publisher)로 유지하고 워커는 프래그먼트를 제안하는 역할만 하도록 해야 합니다. 서버가 시작하기에 여유롭다는 이유만으로 워커에게 직접 게시하도록 허용하시겠습니까, 아니면 울타리를 로컬에 유지하시겠습니까? 자유로운 시작은 단지 가용성 편의일 뿐이며, 에포크 소유권(epoch ownership)을 할당하는 것은 아닙니다.
주입된 실패, 분모, 그리고 수용 규칙
규모에 대해 이야기하기 전에 네 가지 순서로 주입합니다. 왜냐하면 규모는 혼합을 숨길 뿐이기 때문입니다. 첫째, 시퀀스 제로를 전달하고, 재시작하며, 새로운 시퀀스 제로를 전달한 다음, 오래된 시퀀스 원(sequence one)을 지연해서 전달하십시오. 둘째, 동일한 에포크와 시퀀스를 두 번 전달하고, 두 번째 로우 대신 중복 ack를 요구하십시오. 셋째, 예상되는 시퀀스 세트가 완료되기 전에 게시하고, 짧은 등급(short grade) 대신 불완전(incomplete)을 요구하십시오.
넷째, 범프(bump)를 가로질러 좀비 프래그먼트를 비행 상태로 남겨두고, 그 본문이 더 최신처럼 보여도 거부(reject)를 요구하십시오. 분모는 이 주입된 테스트 스위트 내에서 게시 상태에 도달한 등급의 개수입니다. 분자는 그 게시된 등급들 중 하나 이상의 프래그먼트 에포크를 포함하는 등급의 개수입니다. 불완전한 시도는 분모에 포함되지 않으며, 그렇지 않으면 충돌이 완벽한 점수로 보일 것입니다.
수락 규칙은 거부 로그에 존재하는 모든 오래된 에포크(stale-epoch) 프래그먼트의 분자(numerator)가 0인 경우입니다. 주입된 스위트 내에서 한 번이라도 혼합 등급이 게시된다면 저는 검토를 통과시키지 않을 것입니다. 또한, 지연 전송 후 거부 로그에서 오래된 프래그먼트가 누락되는 경우에도 통과시키지 않을 것입니다. 분모(denominator)가 혼합했던 유일한 시도를 조용히 떨어뜨린다면, 녹색 게이트(green gate)는 무슨 소용이 있겠습니까?
검토에서 작성할 트레이드오프
| 선택 | 지연 시간 영향 (Latency effect) | 정확성 (Correctness) | 비용 영향 (Cost effect) | 여전히 깨지는 것 (What still breaks) |
|---|---|---|---|---|
| 오래된 에포크 거부 (Reject a stale epoch) | 단방향 로그 쓰기 (One side-log write) | 단일 에포크 등급 유지 (Keeps a single-epoch grade) | 이미 사용한 호출 제거 (Drops already spent calls) | 증식 후 좀비 지출 (Zombie spend after the bump) |
| ... |
저는 이 레인(lane)에 대해 거부(reject) 행을 취할 것이며, 보상은 나중 검토까지 미룰 것입니다. 재실행(Replay)은 여기에서 잘못된 기본값입니다. 왜냐하면 오래된 프래그먼트를 재실행하는 방식이 바로 혼합 등급(mix)이 만들어지기 때문입니다. 지연된 본문(late body)이 새로운 본문보다 더 좋아 보인다고 해서 이것이 엄격하게 느껴져야 할까요? 엄격하게 느껴져야 합니다. 왜냐하면 '더 좋아 보인다'는 것은 검토에서 방어할 수 있는 에포크 체크가 아니기 때문입니다.
다음에 변경하고 싶은 것
다음 변경 사항은 프래그먼트뿐만 아니라 모델 호출 시에도 워커(worker)가 제시해야 하는 펜싱 토큰(fencing token)입니다. 이 토큰 없이는 에포크 2가 열려 있는 동안 에포크 1이 여전히 원격 호출을 지출할 수 있습니다. 또한, 거부 로그를 재시도 카운터(retry counter)와 분리하여 백프레셔(backpressure)가 숨겨진 루프가 아니라 눈에 보이는 깊이가 되도록 할 것입니다. 이 장치(fixture)가 재정렬 상태에서 분자 0을 유지할 때까지는 두 번째 퍼블리셔(publisher)를 추가하지 않을 것입니다.
누가 이 패턴을 그대로 두어야 하는가
만약 사이드 이펙트(side effect)가 결제, 이메일 또는 이 등급 책(grade book)이 소유하지 않은 모든 원장(ledger)이라면 이 펜스(fence)를 사용하지 마십시오. 에포크를 인큐(enqueue)하기 전에 영속화할 수 없다면 사용하지 마십시오. 왜냐하면 주조되었지만 손실된 에포크는 혼합 등급을 재창조하기 때문입니다. 명시된 토큰 할당량을 처리량 SLO로 간주하지 말고, 로컬 장치(local fixture)를 건너뛰지 마십시오. 모델 출력의 의미론적 판단이 필요한 팀은 여전히 별도의 평가(eval)가 필요합니다. 왜냐하면 이 프로토콜은 프래그먼트 멤버십만을 보호하기 때문입니다.
열어두고 싶은 질문
원격 작업자는 현재의 무료 모델(free-model)과 무료 서버(free-server) 제공을 먼저 확인하면 재시작 주입(restart injection)을 호스팅할 수 있습니다. 저는 어쨌든 에포크 펜스(epoch fence)를 귀하의 프로세스에 유지할 것이며, 분자(numerator)가 0에서 벗어날 때 게시(publish)를 중단할 것입니다. 저는 여러분에게 공급업체 등급(vendor grade)을 신뢰하라고 요구하는 것이 아니며, 재실행할 수 있는 속성(property)을 신뢰하라고 요구하는 것입니다. 어떤 구체적인 이벤트 순서가 이 단일 에포크 불변성(single-epoch invariant)을 깨뜨리며, 시스템은 거부(reject), 재생(replay), 또는 보상(compensate)해야 할까요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기