The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
57 lines
3.7 KiB
Markdown
57 lines
3.7 KiB
Markdown
---
|
|
kind: CONCEPT
|
|
slug: adapter-outbound-httpclient-c05
|
|
title: 열린 회로가 토큰과 permit을 쓰기 전에 거절한다
|
|
topic: admission-budget-and-backpressure
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: concept:adapter-outbound-httpclient-c05
|
|
evidenceCapturedOn: 2026-09-01
|
|
assets:
|
|
- key: adapter-outbound-httpclient-c05
|
|
file: ../../../final/evidence/rendered/adapter-outbound-httpclient-c05.svg
|
|
- key: adapter-outbound-httpclient-c05-diagram
|
|
file: ../../../final/assets/diagrams/adapter-outbound-httpclient-c05.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/adapter-outbound-httpclient-c05.txt
|
|
source:
|
|
- 원본 분석 절은 analysis/11-adapter-outbound-httpclient.md#L323 이다.
|
|
module: adapter-outbound-httpclient
|
|
---
|
|
|
|
# 열린 회로가 토큰과 permit을 쓰기 전에 거절한다
|
|
|
|
`AttemptResiliencePipeline`이 물리 시도마다 Circuit Breaker → Rate Limiter → Bulkhead → HTTP 호출을 고정 순서로 적용하고 역순으로 해제한다. 순서는 장식이 아니다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
`AttemptResiliencePipeline`이 물리 시도마다 **Circuit Breaker → Rate Limiter → Bulkhead → HTTP 호출**을 고정 순서로 적용하고 역순으로 해제한다.
|
|
|
|
## 시도마다 지나는 가드 순서
|
|
|
|
:::evidence key="adapter-outbound-httpclient-c05-diagram" alt="회로 차단기와 요금 제한기와 벌크헤드와 HTTP 호출이 왼쪽에서 오른쪽으로 이어지고 화살표에 허가와 토큰과 permit 이 붙은 구조" caption="시도마다 지나는 가드 순서" zoom="false"
|
|
:::
|
|
|
|
> "The order is not cosmetic. An open circuit must reject before a rate token or a bulkhead permit is spent, otherwise **a dead upstream keeps consuming the quota and concurrency that healthy upstreams need.**"
|
|
> "A local rejection (rate limiter or bulkhead) is deliberately *not* recorded as a circuit error: the upstream never saw the request, and **counting our own back-pressure as upstream failure would open the breaker on a healthy dependency.**"
|
|
|
|
## 브레이커가 503을 보지 못하던 이력
|
|
|
|
이전에는 원시 전송만 파이프라인 안에서 돌고 응답→예외 매핑이 밖에서 일어나서 "a 503 completed the call normally, the breaker recorded a success, and **an upstream that answered nothing but 503 never opened its circuit. The thing the breaker is for was the one thing it could not see.**" 지금은 `remoteFailure` 분류기가 반환값을 보고 브레이커에 알린다.
|
|
|
|
## AttemptResiliencePipeline 참조 위치
|
|
|
|
:::evidence key="adapter-outbound-httpclient-c05" alt="코드베이스에서 AttemptResiliencePipeline 를 검색한 출력 16줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="AttemptResiliencePipeline 코드베이스 검색 — 16줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
## 규칙을 덮는 테스트와 프로토콜 증거
|
|
|
|
test 47개가 이 규칙들을 촘촘히 덮는다 — `appliesCircuitThenRateLimiterThenBulkheadPerAttempt`, `openCircuitDoesNotConsumeRateOrBulkheadPermit`, `bulkheadRejectionReleasesTheRateLimiterAndIsNotACircuitError`, `answeredStatusesDoNotRetryANonIdempotentOperation`, `deniesOneShotBodyEvenForPut`, `honorsRetryAfterOnlyInsideDeadline`, `protocolProofOfNonProcessingWinsOverEverything`, `streamAfterGoAwayLastIdIsPeerNotProcessed` 등.
|
|
|
|
`Http2ProtocolEvidence`는 프로토콜 수준 증거를 다룬다 — `REFUSED_STREAM`과 GOAWAY의 last-stream-id보다 큰 스트림 id는 **피어가 처리하지 않았음의 증명**이라 `NOT_SENT`로 승격되고, 그 이하 id의 리셋은 여전히 모호하다(`streamAtOrBelowGoAwayLastIdStaysAmbiguous`, `aBareStreamResetProvesNothing`).
|
|
|
|
<!-- body:end -->
|