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>
152 lines
10 KiB
Plaintext
152 lines
10 KiB
Plaintext
# 승인 메서드 셋의 본문
|
|
GrpcAdmissionController.java:50 /** Admits a call, queues it, or refuses it. */
|
|
GrpcAdmissionController.java:51 public Decision tryAdmit() {
|
|
GrpcAdmissionController.java:52 int running = inFlight.get();
|
|
GrpcAdmissionController.java:53 if (running < maxConcurrentCalls) {
|
|
GrpcAdmissionController.java:54 inFlight.incrementAndGet();
|
|
GrpcAdmissionController.java:55 return new Decision(true, inFlight.get(), queued.get(), "within the concurrency bound");
|
|
GrpcAdmissionController.java:56 }
|
|
GrpcAdmissionController.java:57 int waiting = queued.get();
|
|
GrpcAdmissionController.java:58 if (waiting < maxQueuedCalls) {
|
|
GrpcAdmissionController.java:59 queued.incrementAndGet();
|
|
GrpcAdmissionController.java:60 return new Decision(true, running, queued.get(), "queued within the admission bound");
|
|
GrpcAdmissionController.java:61 }
|
|
GrpcAdmissionController.java:62 rejected.incrementAndGet();
|
|
GrpcAdmissionController.java:63 return new Decision(
|
|
GrpcAdmissionController.java:64 false,
|
|
GrpcAdmissionController.java:65 running,
|
|
GrpcAdmissionController.java:66 waiting,
|
|
GrpcAdmissionController.java:67 "at capacity: "
|
|
GrpcAdmissionController.java:68 + running
|
|
GrpcAdmissionController.java:69 + " in flight and "
|
|
GrpcAdmissionController.java:70 + waiting
|
|
GrpcAdmissionController.java:71 + " queued. Refusing is better than accepting work whose caller will have given up.");
|
|
GrpcAdmissionController.java:72 }
|
|
GrpcAdmissionController.java:73
|
|
GrpcAdmissionController.java:74 /** Moves a queued call into execution. */
|
|
GrpcAdmissionController.java:75 public void promoteFromQueue() {
|
|
GrpcAdmissionController.java:76 if (queued.get() > 0) {
|
|
GrpcAdmissionController.java:77 queued.decrementAndGet();
|
|
GrpcAdmissionController.java:78 inFlight.incrementAndGet();
|
|
GrpcAdmissionController.java:79 }
|
|
GrpcAdmissionController.java:80 }
|
|
GrpcAdmissionController.java:81
|
|
GrpcAdmissionController.java:82 /** Releases a finished call. */
|
|
GrpcAdmissionController.java:83 public void release() {
|
|
GrpcAdmissionController.java:84 if (inFlight.get() > 0) {
|
|
GrpcAdmissionController.java:85 inFlight.decrementAndGet();
|
|
GrpcAdmissionController.java:86 }
|
|
GrpcAdmissionController.java:87 }
|
|
계수기 필드 :
|
|
GrpcAdmissionController.java:17 private final AtomicInteger inFlight = new AtomicInteger();
|
|
GrpcAdmissionController.java:18 private final AtomicInteger queued = new AtomicInteger();
|
|
GrpcAdmissionController.java:19 private final AtomicInteger rejected = new AtomicInteger();
|
|
|
|
# 클래스가 자기 존재 이유를 무엇이라고 적는가
|
|
GrpcAdmissionController.java:5 /**
|
|
GrpcAdmissionController.java:6 * Refuses work the server cannot do, before it spends anything on it.
|
|
GrpcAdmissionController.java:7 *
|
|
GrpcAdmissionController.java:8 * <p>Rejecting with {@code RESOURCE_EXHAUSTED} is a better outcome than queueing for two reasons
|
|
GrpcAdmissionController.java:9 * that both matter under load: the client learns immediately and can shed or retry elsewhere, and
|
|
GrpcAdmissionController.java:10 * the server stops accumulating work whose callers have already given up. A server that queues
|
|
GrpcAdmissionController.java:11 * instead spends its capacity finishing requests nobody is reading.
|
|
GrpcAdmissionController.java:12 */
|
|
GrpcAdmissionController.java:13 public final class GrpcAdmissionController {
|
|
|
|
# 이 타입이 조립되는가, 그리고 승인 API 를 부르는 자리는 어디인가
|
|
main · GrpcDrainCoordinator.java:3 import dev.caskeleton.grpc.server.GrpcAdmissionController;
|
|
main · GrpcDrainCoordinator.java:25 private final GrpcAdmissionController admission;
|
|
main · GrpcDrainCoordinator.java:42 GrpcAdmissionController admission,
|
|
test · GrpcDrainCoordinatorTest.java:6 import dev.caskeleton.grpc.server.GrpcAdmissionController;
|
|
test · GrpcDrainCoordinatorTest.java:27 health, new GrpcAdmissionController(8, 8), policy, unaryCalls::get, streams::get);
|
|
main · GrpcAdmissionController.java:13 public final class GrpcAdmissionController {
|
|
main · GrpcAdmissionController.java:22 public GrpcAdmissionController(int maxConcurrentCalls, int maxQueuedCalls) {
|
|
main · GrpcAdmissionController.java:31 public static GrpcAdmissionController forExecutor(GrpcExecutorProfile profile) {
|
|
main · GrpcAdmissionController.java:35 return new GrpcAdmissionController(
|
|
test · GrpcServerProfileTest.java:105 GrpcAdmissionController controller = new GrpcAdmissionController(2, 1);
|
|
test · GrpcServerProfileTest.java:110 GrpcAdmissionController.Decision refused = controller.tryAdmit();
|
|
test · GrpcServerProfileTest.java:122 GrpcAdmissionController controller = new GrpcAdmissionController(1, 1);
|
|
test · GrpcServerProfileTest.java:136 GrpcAdmissionController controller = GrpcAdmissionController.forExecutor(EXECUTOR);
|
|
main · GrpcPlatformAutoConfiguration.java:11 import dev.caskeleton.grpc.server.GrpcAdmissionController;
|
|
main · GrpcPlatformAutoConfiguration.java:56 public GrpcAdmissionController grpcAdmissionController(GrpcExecutorProfile executorProfile) {
|
|
main · GrpcPlatformAutoConfiguration.java:57 return GrpcAdmissionController.forExecutor(executorProfile);
|
|
tryAdmit · promoteFromQueue · release 를 부르는 자리 :
|
|
test · GrpcServerProfileTest.java:107 assertThat(controller.tryAdmit().admitted()).isTrue();
|
|
test · GrpcServerProfileTest.java:108 assertThat(controller.tryAdmit().admitted()).isTrue();
|
|
test · GrpcServerProfileTest.java:109 assertThat(controller.tryAdmit().admitted()).isTrue();
|
|
test · GrpcServerProfileTest.java:110 GrpcAdmissionController.Decision refused = controller.tryAdmit();
|
|
test · GrpcServerProfileTest.java:123 controller.tryAdmit();
|
|
test · GrpcServerProfileTest.java:124 controller.tryAdmit();
|
|
test · GrpcServerProfileTest.java:126 controller.release();
|
|
test · GrpcServerProfileTest.java:127 controller.promoteFromQueue();
|
|
test · GrpcServerProfileTest.java:139 assertThat(controller.tryAdmit().admitted()).isTrue();
|
|
그중 main 소스 세트 : 0 개 파일
|
|
main 에서 이 타입을 들고 있는 GrpcDrainCoordinator 가 하는 일 :
|
|
GrpcDrainCoordinator.java:148 return admission.inFlight();
|
|
그 빈을 만드는 자동설정이 언제 켜지는가 :
|
|
GrpcPlatformAutoConfiguration.java:29 @Configuration(proxyBeanMethods = false)
|
|
GrpcPlatformAutoConfiguration.java:30 @ConditionalOnProperty(
|
|
GrpcPlatformAutoConfiguration.java:31 prefix = "ca-skeleton.grpc.platform",
|
|
GrpcPlatformAutoConfiguration.java:32 name = "enabled",
|
|
GrpcPlatformAutoConfiguration.java:33 havingValue = "true",
|
|
GrpcPlatformAutoConfiguration.java:34 matchIfMissing = false)
|
|
GrpcPlatformAutoConfiguration.java:35 @EnableConfigurationProperties(GrpcPlatformProperties.class)
|
|
GrpcPlatformAutoConfiguration.java:36 public class GrpcPlatformAutoConfiguration {
|
|
그 프로퍼티를 켜는 설정 파일 : 0 개
|
|
src/grpc 밖에서 이 starter 에 의존하는 모듈 : 0 개
|
|
main 소비자 GrpcDrainCoordinator 가 생성되는 자리 :
|
|
test · GrpcDrainCoordinatorTest.java:26 return new GrpcDrainCoordinator(
|
|
src/grpc 안의 ApplicationContextRunner : 0 개 파일
|
|
|
|
# 저장소가 쓰는 승격 순서
|
|
GrpcServerProfileTest.java:119 @Test
|
|
GrpcServerProfileTest.java:120 @DisplayName("a released call makes room, and a queued call is promoted into it")
|
|
GrpcServerProfileTest.java:121 void releaseAndPromotionTrackCapacity() {
|
|
GrpcServerProfileTest.java:122 GrpcAdmissionController controller = new GrpcAdmissionController(1, 1);
|
|
GrpcServerProfileTest.java:123 controller.tryAdmit();
|
|
GrpcServerProfileTest.java:124 controller.tryAdmit();
|
|
GrpcServerProfileTest.java:125
|
|
GrpcServerProfileTest.java:126 controller.release();
|
|
GrpcServerProfileTest.java:127 controller.promoteFromQueue();
|
|
GrpcServerProfileTest.java:128
|
|
GrpcServerProfileTest.java:129 assertThat(controller.inFlight()).isEqualTo(1);
|
|
GrpcServerProfileTest.java:130 assertThat(controller.queued()).isZero();
|
|
GrpcServerProfileTest.java:131 }
|
|
|
|
# 같은 가족의 올바른 형태 — 재시도 예산
|
|
src/grpc/grpc-policy/src/main/java/dev/caskeleton/grpc/resilience/GrpcRetryBudget.java
|
|
GrpcRetryBudget.java:50 public boolean tryConsume() {
|
|
GrpcRetryBudget.java:51 while (true) {
|
|
GrpcRetryBudget.java:52 long observed = tokens.get();
|
|
GrpcRetryBudget.java:53 if (observed < tokensPerRetry) {
|
|
GrpcRetryBudget.java:54 return false;
|
|
GrpcRetryBudget.java:55 }
|
|
GrpcRetryBudget.java:56 if (tokens.compareAndSet(observed, observed - tokensPerRetry)) {
|
|
GrpcRetryBudget.java:57 return true;
|
|
GrpcRetryBudget.java:58 }
|
|
GrpcRetryBudget.java:59 }
|
|
GrpcRetryBudget.java:60 }
|
|
GrpcRetryBudget.java:61
|
|
compareAndSet 을 쓰는 grpc main 파일 :
|
|
GrpcChannelRuntimeRegistry.java
|
|
GrpcCancellationToken.java
|
|
GrpcRetryBudget.java
|
|
GrpcAdmissionController.java compareAndSet 0 건
|
|
GrpcStreamAdmission.java compareAndSet 0 건
|
|
|
|
# 프로브 — 아래 소스를 그 자리에서 컴파일해 돌린다
|
|
GrpcAdmissionController paired = new GrpcAdmissionController(1, 8);
|
|
for (int i = 0; i < 6; i++) paired.tryAdmit();
|
|
for (int i = 0; i < 5; i++) { paired.release(); paired.promoteFromQueue(); }
|
|
GrpcAdmissionController solo = new GrpcAdmissionController(1, 8);
|
|
for (int i = 0; i < 6; i++) solo.tryAdmit();
|
|
for (int i = 0; i < 5; i++) solo.promoteFromQueue();
|
|
가. 저장소 시험과 같은 순서 — 해제 하나에 승격 하나
|
|
여섯 번 승인 후 : 진행 중 1 · 대기 5
|
|
해제·승격을 다섯 쌍 후 : 진행 중 1 · 대기 0
|
|
|
|
나. 해제 없이 승격만 — 승격이 해제를 앞지른 경우
|
|
여섯 번 승인 후 : 진행 중 1 · 대기 5
|
|
승격만 다섯 번 후 : 진행 중 6 · 대기 0
|
|
동시성 상한 : 1
|