빌린 평가 평면이 재시도 예산을 가로채기 전에 진입 깊이를 불변값으로 만들기
요약
본 글은 시스템 아키텍처에서 '빌린 평가 평면'과 같은 독립적인 프로세스가 주 경로의 자원(재시도 예산, 진입 깊이)을 부적절하게 소모하는 문제를 지적합니다. 특히, 빌린 리소스가 주 사가에 피해를 줄 수 없도록 엄격한 불변값(invariant)을 강제해야 함을 강조합니다.
핵심 포인트
- 빌린 평면은 주 경로의 재시도 예산을 가로채지 않아야 합니다.
- 진입 깊이와 재시도 지출은 생성된 실패 영역 내에 머물러야 합니다.
- 읽기 전용으로 제한하고, 유한한 재시도 카운터로 취급해야 합니다.
- 단일 프로세스 시뮬레이터를 통해 아키텍처의 불변성을 검증할 수 있습니다.
지난 목요일, 저는 플래너가 빌린 평가 평면(borrowed eval plane)이 이미 침수된 상태에서 도구 의도(tool intent)를 수락하는 것을 지켜보았습니다. 무료 서버는 다운되지 않았지만, 그 큐는 제가 주 경로(primary path)에서 허용할 깊이를 넘어 있었습니다. 그 느린 평면으로부터의 재시도는 돌아와서 공유되는 재시도 예산(shared retry budget)을 가로채고, 관련 없는 의도의 보상(compensation)을 지연시켰습니다. 이것을 여유 용량이라고 부르겠습니까, 아니면 솔직히 말해서 유출된 실패 영역(leaked failure domain)이라고 부르겠습니까?
일반적인 구현은 빌린 평가 서버를 주 사가(primary saga)에 피해를 줄 수 없는 무한한 여분의 처리량으로 취급합니다. 이 조용한 가정이 두 평면이 하나의 재시도 카운터와 하나의 진입 게이트를 공유하는 순간 실패합니다. 저는 여기에서 지루한 불변값(boring invariant)을 원하며, 이것은 규모가 버그를 지연 시간처럼 보이게 만들기 전에 강제되어야 합니다. 한 평면은 점수 계산을 위해 모델 접근 권한을 빌릴 수 있지만, 다른 평면의 재시도 예산을 소모해서는 안 됩니다.
상자(box)를 그리기 전에 위반하는 순서
누군가 무료 레인이 단지 추가 작업자일 뿐이라고 말할 때 제가 계속 재생하는 순서입니다. 주 경로가 의도 I1을 수락한 다음, 빌린 평면에 고정된 리비전(fixture revision) R3의 점수 계산을 요청합니다. 빌린 건강 샘플은 이미 제 최대 나이보다 오래되었지만, 게이트는 침묵을 용량으로 취급합니다. 빌린 재시도가 주 예산을 감소시키므로, 의도 I2가 자체 작업자 시간 초과(time out) 시 보상할 수 없습니다.
그 큐 깊이는 빌린 쪽의 것인가요, 아니면 제가 그 지연을 주 사가로 세탁한 것일까요? 저는 이 검토에서 쓰기 키 격리(write-key isolation)나 아웃박스 중복 제거(outbox dedupe)를 다시 열지는 않을 것입니다. 왜냐하면 그것들은 다른 불변값들이기 때문입니다. 이 검토는 진입 깊이와 재시도 지출이 생성된 실패 영역 내에 머무르는지 여부를 묻습니다. 만약 답이 '아니오'라면, 저는 그 시도를 다른 평면에서 다시 재생하는 것보다 점수 계산을 포기하는 편이 나을 것입니다.
제가 실제로 방어할 가정들
저는 intent id당 하나의 입학 결정이 있고, health는 나이를 가진 풀링된 샘플이라고 가정합니다. 저는 빌린 레인이 주 쓰기 경로에 대해 읽기 전용이며, 따라서 점수(score)가 도구 측 효과를 커밋할 수 없다고 가정합니다. 저는 재시도 예산(retry budget)이 어떤 풀이 제때 자동 확장될 것이라는 약속이 아니라 유한 카운터라고 가정합니다. 또한 무료 모델 접근(free model access)은 제한되거나 사라질 수 있으며, 부재는 통과처럼 보이기보다는 폐기되어야 한다고 가정합니다.
이러한 가정들은 의도적으로 좁게 설정되었으며, 더 많은 장치가 없다면 다중 리전 결제 사가(multi-region payment saga)를 견디지 못할 것입니다. 만약 시스템이 크로스-리전 합의(cross-region consensus)가 필요하다면 여기서 멈추고 이 구조가 해당 프로토콜인 것처럼 가장하지 마십시오. 단일 프로세스 시뮬레이터가 실제 파티션은 시뮬레이션 할 수 없더라도 불변성(invariant)을 가르칠 수 있을까요? 저는 그렇다고 생각합니다. 클라우드를 측정했다고 가정하는 대신 나이와 깊이를 주입한다면요.
제가 빌릴 의향이 있는 레인
저는 이 빌린 용량 설계(borrowed-capacity design)를 제품 투어나 벤치마크 주장으로 보는 것이 아니라 아키텍처 문제로 검토하고 있습니다. 공개합니다: 이 글은 MonkeyCode의 제품 홍보의 일환으로 작성되었습니다. 운영자는 저에게 무료 모델 접근과 무료 서버 옵션을 그림자 점수 레인(shadow scoring lane)에 사용할 수 있는 것으로 간주하도록 요청했습니다. 저는 모델 이름, 할당량, 하드웨어, 기간 또는 영속성을 주장하지 않습니다. 왜냐하면 그러한 사실들은 검증된 측정치로 제공되지 않았기 때문입니다.
만약 무료 서버가 구조(fixture)를 점수화할 수 있더라도, 그것이 귀하의 주 재시도 폭풍을 흡수할 수 있다는 것을 증명하지는 못합니다. 저는 그림자 점수화만을 거기에 배치하고, 보상(compensation), Idempotency 키, 그리고 사용자에게 보이는 쓰기 작업은 주 경로에 유지할 것입니다. 무료 옵션이 주 사가(primary saga)의 내구성 의존성으로 조용히 변하는 것 없이 여기서 유용하게 남아 있을 수 있을까요? 네, 하지만 입학 펜스(admission fence)가 누락된 용량을 조용한 통과가 아니라 폐기로 처리할 때만 가능합니다.
두 실패 영역을 가로지르는 데이터 흐름
sequenceDiagram
participant P as Primary saga
participant A as Admission fence
...
주요 도메인(primary domain)은 사가(saga), 보상 경로(compensation path), 그리고 사용자가 관찰할 수 있는 모든 쓰기 작업을 소유합니다. 빌린 도메인(borrowed domain)은 섀도우 점수(shadow scores)를 소유하며, 자체적인 영속적인 아웃박스(durable outbox)가 없다면 사라질 수도 있습니다. 어드미션 컨트롤러(admission controller)는 위험한 중간 지점인데, 공유 카운터 하나가 두 도메인을 하나의 폭발 반경(blast radius)으로 만듭니다. 프롬프트를 조정하거나 빌린 평면에 워커를 추가하기 전에, 저는 이 중간 지점을 먼저 바꿀 것입니다.
규모 확장 전에 구현할 단계들
1. 신선한 어드미션 리스(admission lease) 고정
저는 각 평면에 health_age를 기록하고, 그 나이가 health_max_age를 초과하면 진입을 거부합니다. 오래된 건강 상태 값은 여분의 용량 증거가 아닙니다. 마지막 샘플이 큐가 비어있다고 말했더라도 마찬가지입니다. 제어 평면(control plane)이 언제 가져갔는지 명시할 수 없는 샘플을 제가 어떻게 신뢰하겠습니까? 저는 볼 수 없는 용량을 추측하는 것보다, 빌린 것을 거부하고 로컬에서 점수를 매기거나 섀도우를 건너뛰는 편이 나을 것입니다.
2. 평면별 깊이 및 재시도 예산 설정
각 평면은 자체적인 depth_limit과 자체적인 retry_budget을 가지며, 빌린 평면에서 발생하는 어떤 작업(shed)도 주요 카운터를 감소시켜서는 안 됩니다. 만약 빌린 깊이가 이미 한계에 도달했다면, 저는 그것이 재시도가 되기 전에 점수 요청을 중단합니다. 만약 빌린 예산이 이미 0이라면, 해당 평면 내부에서 보상하거나 작업을 버리고, 주요 사가는 건드리지 않습니다. 공유 정수(shared integer)가 첫 번째 관련 없는 보상 작업으로 인해 주요 경로에서 멈추기 전까지는 더 간단하게 느껴지나요?
3. 점수를 관찰한 리비전에만 바인딩
빌린 워커가 느려서 장치가 이동하는 동안, 리비전 R3에 대한 통과는 리비전 R4에 연결되어서는 안 됩니다. 저는 이 바인드 확인(bind check)을 방화벽 옆에 유지합니다. 왜냐하면 늦은 점수는 깊은 큐와는 다른 종류의 실패이기 때문입니다. 시스템이 그 늦은 점수를 거부해야 할까요, 재실행해야 할까요, 아니면 다른 평면에서 시도를 보상해야 할까요? 바인드를 거부하고, 주요 예산에 다시 실행하지 않으며, 빌린 평면이 이미 해당 시도를 수락한 경우에만 보상해야 합니다.
4. 다이어그램을 신뢰하기 전에 실패 주입하기
빌린 건강(stale borrowed health), 한계에서의 빌린 깊이(borrowed depth at the limit), 그리고 주된 포화도(primary saturation)를 차용자가 유휴 상태인 동안 주입합니다. 또한, 울타리가 이미 빌림을 떨쳐낸 후에 도착하는 후기 점수(late score)도 주입합니다. 수락 규칙은 자동화하기에 충분히 작으며, 명확하게 실패하기 위해 호스팅 풀이 필요하지 않습니다. 이들 주입된 사례 전반에 걸쳐, 빌림이 떨쳐질 때마다 주된 재시도 예산(primary retry budget)은 변하지 않고 유지되어야 합니다.
로컬에서 실행할 수 있는 테스트 코드 (A fixture you can run locally)
이것은 제가 검토를 위해 작성한 제안용 테스트 코드(proposal fixture)이며, 어떤 호스팅 풀에 대한 벤치마크가 아닙니다. 이것을 admission_fence.py로 저장하고 최신 Python 3가 설치된 모든 기기에서 python3 admission_fence.py를 실행하세요. assert가 발동되면, 실패하는 사례 이름이 발생한 이벤트 순서에서 불변성(invariant)이 깨졌다는 의미입니다. 저는 이 파일을 미실행 제안용 테스트 코드(unexecuted proposal fixture)로 표시하고 있으므로, 나중에 녹색으로 실행된다고 해서 용량 주장(capacity claim)이 되는 것은 아닙니다.
#!/usr/bin/env python3
# 제안용 테스트 코드: 빌린 평면이 주된 재시도를 소모하지 못하도록 울타리 진입 관리 (fence admission)
...
제가 수용할 트레이드오프 (Tradeoffs I would accept)
| 선택지 | 지연 시간 영향 (Latency effect) | 정확성 (Correctness) | 비용 형태 (Cost shape) | 허용 가능한 실패 (Failure I accept) |
|---|---|---|---|---|
| 공유 재시도 예산 (Shared retry budget) | 폭풍 전까지 빠름 (Fast until the storm) | 불변성 파괴 (Breaks the invariant) | 빌린 비용 숨김 (Hides borrowed cost) | 관련 없는 보상 지연 (Unrelated compensation stalls) |
| ... |
저는 주된 예산이 여전히 보상할 수 있는 상태를 유지하는 대신, 낮은 그림자 커버리지(lower shadow coverage)를 받아들이겠습니다. 코드가 공유 정수(shared integer) 하나만 유지하는 동안 두 개의 평면을 보여주는 다이어그램은 받아들이지 않겠습니다. 나중에 비교되는 모든 것의 분모는 이 테스트 코드에서 주입된 100개의 의도당 떨쳐진 결정 수여야 합니다. 호스팅 토큰-초(hosted tokens-per-second) 수치를 인용하는 수락 규칙은 허구이므로, 저는 그것을 작성하지 않겠습니다.
다음에 변경할 것 (What I would change next)
저는 health_age를 제어 평면(control plane)이 알려진 간격으로 새로 고치는 부호화된 임대 만료일(signed lease expiry)로 대체할 것입니다. 재시도 카운터는 프로세스 메모리에 남겨두기보다, 의도 기록(intent record) 옆에 영구적으로 저장할 것입니다. 점수(score)가 R3에서 R4로 넘어갈 때 새로운 승인(admission) 없이 발생하지 못하도록 명시적인 개정 워터마크(revision watermark)를 추가할 것입니다. 또한, 섀도우 레인(shadow lane)을 자체 큐 이름으로 분리하여 대시보드가 빌린 포화 상태(borrowed saturation) 때문에 주(primary) 시스템에 페이지를 보내는 것을 막을 것입니다.
이것이 주말 프로토타입이 첫 검토까지 감당하기에는 너무 많은 구성 요소가 될까요? 네, 하지만 저는 추가 큐나 다른 점수 계산기(scorer)를 추가하기 전에 울타리(fence)를 먼저 설치할 것입니다. 예산(budget)을 공유하는 프로토타입은 더 작은 설계가 아닙니다. 왜냐하면 누락된 울타리는 숨겨진 결합(hidden coupling)이기 때문입니다. 저는 나중에 설명되는 중단된 보상(stalled compensation)보다는 검토에서 헛간(shed)을 설명하는 편이 낫습니다.
이것을 복사해서는 안 되는 경우
이 구조를 지급 사가(payment saga), 영역 간 합의 프로토콜(cross-region consensus protocol), 또는 무료 서버가 내구성 저장소라는 증거로 사용하지 마십시오. 점수 계산기가 쓰기 작업을 수행하는 도구(tools that write)를 호출할 수 있는 경우에 이 구조를 사용해서는 안 됩니다. 왜냐하면 이 검토는 의도적으로 읽기 전용 섀도우(read-only shadow)를 가정했기 때문입니다. admission fence properties held라는 인쇄된 줄을 운영 인증서(production certification)나 어떤 공급업체의 측정된 용량으로 취급하지 마십시오. 중복 전달(duplicate delivery) 하에서 정확히 한 번의 부작용(exactly-once side effects)이 필요한 경우에도, 이 파일은 구현하지 않는 아웃박스(outbox)와 보상 프로토콜(compensation protocol)이 여전히 필요합니다.
유지하고 싶은 반례(counterexample)
어떤 이벤트 순서가 불변성(invariant)을 깨뜨리며, 시스템은 빌린 시도(borrowed attempt)를 거부해야 할까요, 재실행해야 할까요, 아니면 보상해야 할까요? 주 시스템이 이미 다른 의도를 승인한 후에 빌린 평면(borrowed plane)이 깊이 제한에 도달하는 경우를 가정해 봅시다. 저는 새로운 차용을 거부하고 그 점수를 주 시스템의 재시도 예산에 재실행하는 것을 거부할 것입니다. 저는 헛간 이전에 이미 수락했던 시도에 대해서만 보상할 것입니다.
만약 게이트가 다른 작업을 수행한다면, 그 무료 레인은 주(primary)를 위한 여유 용량이 아니게 됩니다. 그림자 점수(shadow scoring) 레인을 주차할 장소를 원한다면, 현재의 무료 서버 약관을 직접 읽어보십시오. 두 비행기 모두 재시도 예산이나 입장 카운터를 공유하도록 허용되기 전에 이 경기를 다시 재생하십시오. 자체 이벤트 순서를 가져와서, 그 순서가 거부(reject)해야 하는지, 다시 재생(replay)해야 하는지, 아니면 대신 보상(compensate)해야 하는지 물어보십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기