- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4.4 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | assets | evidence | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | draining-began-and-a-service-came-back-serving | 배수를 시작한 뒤에도 한 서비스가 다시 SERVING 이 될 수 있다 | non-atomic-check-then-act | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:draining-began-and-a-service-came-back-serving | 2026-09-01 |
|
|
|
배수를 시작한 뒤에도 한 서비스가 다시 SERVING 이 될 수 있다
배수 시작이 플래그를 쓰고 상태 맵을 바꾸는 두 단계다. 그 사이에 끼어든 상태 갱신이 한 서비스를 서빙으로 남기고, 전역 상태 재계산이 그것을 따라 전체를 서빙으로 되돌린다.
관계
- 원자 타입 위의 검사 후 실행과 비교 후 교체 루프 이 사례가 속한 구조다.
- 전이를 시작하는 쓰기와 그 전이가 덮는 집합은 한 연산이어야 한다 이 사례가 만든 규칙이다.
- 새 승인을 거절한다는 메서드가 단계만 기록하고 아무것도 거절하지 않는다 같은 리프에서 배수를 무력하게 만드는 다른 절반이다.
문제
헬스 레지스트리는 서비스별 상태와 전역 상태를 갖는다. 로드밸런서가 읽는 것은 전역 상태다.
배수를 시작하면 모든 서비스가 배수 중으로 바뀌고 전역 상태도 그렇게 된다. 그래야 로드밸런서가 새 연결을 보내지 않는다.
결론
배수 시작이 두 단계다.
먼저 배수 플래그를 참으로 쓴다. 그다음 상태 맵의 모든 값을 배수 중으로 바꾼다.
서비스 상태를 서빙으로 바꾸는 메서드는 첫 줄에서 그 플래그를 확인하고 참이면 즉시 돌아간다. 배수 중에는 서빙으로 되돌릴 수 없게 하려는 가드다.
그 가드를 통과한 스레드가 두 단계 사이에 쓰면 결과가 뒤집힌다. 그 서비스만 서빙으로 남고, 전역 상태 재계산이 서빙인 서비스가 하나라도 있으면 전역을 서빙으로 만든다.
배수 중인 인스턴스가 로드밸런서에 준비됐다고 답하는 상태이고, 배수의 목적이 정확히 그것을 막는 것이다.
같은 리프의 새 승인 거절이 단계만 기록하고 아무것도 거절하지 않는 것과 겹치면, 배수라는 절차 전체가 상태 기록으로만 존재하고 트래픽에 대해서는 효과가 없다.
검증 환경
OpenJDK : 21.0.12 Gradle : 9.0.0 확인 방식 : 배수 시작 메서드의 두 단계 순서와 상태 갱신 메서드의 가드 위치 대조 소스 수정 : x
재현 조건
- 배수 시작 메서드에서 플래그 쓰기와 상태 맵 갱신의 순서를 확인한다.
- 서빙 상태로 바꾸는 메서드의 첫 줄 가드를 확인한다.
- 전역 상태 재계산이 서비스 상태를 어떻게 집계하는지 확인한다.
본문
beginDraining() 이 두 단계다 — 먼저 draining = true 를 쓰고, 그다음 states.replaceAll(...) 로 모든 서비스를 DRAINING 으로 바꾼다. markServing 은 첫 줄에서 if (draining) return; 으로 자기를 막는다.
beginDraining() 의 두 단계
:::evidence key="draining-began-and-a-service-came-back-serving" alt="분석 문서 final/document.md#a20-grpc-admin 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md#a20-grpc-admin 발췌 — 18줄" zoom="true" :::
두 단계 사이가 열려 있다
그 가드를 통과한 스레드가 두 단계 사이에 쓰면, 그 서비스만 SERVING 으로 남고 recomputeGlobal() 이 전체 상태를 SERVING 으로 되돌린다. 배수 중인 인스턴스가 로드밸런서에 준비됐다고 답하는 상태이고, 배수의 목적이 정확히 그것을 막는 것이다.
같은 리프의 짝
rejectNewAdmission() 이 단계만 기록하고 아무것도 거절하지 않는다.
확인하지 못한 것
경합을 실행으로 재현하지 않았다.