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>
175 lines
13 KiB
Plaintext
175 lines
13 KiB
Plaintext
# 넘침 분기 전체
|
|
GrpcSerializedStreamWriter.java:70 if (!state.acceptsMessages()) {
|
|
GrpcSerializedStreamWriter.java:71 return GrpcStreamWriteResult.REJECTED_CLOSED;
|
|
GrpcSerializedStreamWriter.java:72 }
|
|
GrpcSerializedStreamWriter.java:73 long nextBytes = payloadSizer.getAsLong();
|
|
GrpcSerializedStreamWriter.java:74 GrpcFlowControlDecision decision =
|
|
GrpcSerializedStreamWriter.java:75 flowControl.decide(queue.size(), queuedBytes, nextBytes, transportReady);
|
|
GrpcSerializedStreamWriter.java:76 return switch (decision.action()) {
|
|
GrpcSerializedStreamWriter.java:77 case TERMINATE -> {
|
|
GrpcSerializedStreamWriter.java:78 state = GrpcStreamWriterState.DRAINING;
|
|
GrpcSerializedStreamWriter.java:79 yield GrpcStreamWriteResult.OVERFLOW_TERMINATING;
|
|
GrpcSerializedStreamWriter.java:80 }
|
|
GrpcSerializedStreamWriter.java:81 case DROP_OLDEST -> {
|
|
GrpcSerializedStreamWriter.java:82 GrpcStreamEnvelope<T> dropped = queue.pollFirst();
|
|
GrpcSerializedStreamWriter.java:83 if (dropped != null) {
|
|
GrpcSerializedStreamWriter.java:84 queuedBytes = Math.max(0L, queuedBytes - nextBytes);
|
|
GrpcSerializedStreamWriter.java:85 droppedMessages++;
|
|
GrpcSerializedStreamWriter.java:86 }
|
|
GrpcSerializedStreamWriter.java:87 enqueue(kind, payload, snapshotVersion, resumeToken, nextBytes);
|
|
GrpcSerializedStreamWriter.java:88 yield GrpcStreamWriteResult.DROPPED;
|
|
GrpcSerializedStreamWriter.java:89 }
|
|
GrpcSerializedStreamWriter.java:90 case PAUSE, PROCEED -> {
|
|
GrpcSerializedStreamWriter.java:91 enqueue(kind, payload, snapshotVersion, resumeToken, nextBytes);
|
|
GrpcSerializedStreamWriter.java:92 yield GrpcStreamWriteResult.ACCEPTED;
|
|
GrpcSerializedStreamWriter.java:93 }
|
|
GrpcSerializedStreamWriter.java:94 };
|
|
GrpcSerializedStreamWriter.java:95 }
|
|
GrpcSerializedStreamWriter.java:96
|
|
GrpcSerializedStreamWriter.java:97 private void enqueue(
|
|
크기를 어디서 얻는가 :
|
|
GrpcSerializedStreamWriter.java:30 private final LongSupplier payloadSizer;
|
|
GrpcSerializedStreamWriter.java:41 * @param payloadSizer the serialized size of the next payload, for the byte bound
|
|
GrpcSerializedStreamWriter.java:48 LongSupplier payloadSizer) {
|
|
GrpcSerializedStreamWriter.java:53 || payloadSizer == null) {
|
|
GrpcSerializedStreamWriter.java:60 this.payloadSizer = payloadSizer;
|
|
GrpcSerializedStreamWriter.java:73 long nextBytes = payloadSizer.getAsLong();
|
|
|
|
# 봉투가 담는 것
|
|
GrpcStreamEnvelope.java:16 public record GrpcStreamEnvelope<T>(
|
|
GrpcStreamEnvelope.java:17 GrpcStreamId streamId,
|
|
GrpcStreamEnvelope.java:18 long sequence,
|
|
GrpcStreamEnvelope.java:19 Kind kind,
|
|
GrpcStreamEnvelope.java:20 Optional<String> snapshotVersion,
|
|
GrpcStreamEnvelope.java:21 Optional<String> resumeToken,
|
|
GrpcStreamEnvelope.java:22 Optional<GrpcStreamTerminationReason> terminationReason,
|
|
GrpcStreamEnvelope.java:23 Optional<T> payload) {
|
|
GrpcStreamEnvelope.java:24
|
|
GrpcStreamEnvelope.java:25 /** What a stream message is. */
|
|
|
|
# 누적 바이트가 무엇에 쓰이는가 — decide 의 분기 전부
|
|
GrpcFlowControlPolicy.java:56 boolean overflowsCount = queuedMessages >= maxQueuedMessages;
|
|
GrpcFlowControlPolicy.java:57 boolean overflowsBytes = queuedBytes + nextMessageBytes > maxQueuedBytes;
|
|
GrpcFlowControlPolicy.java:58 if (overflowsCount || overflowsBytes) {
|
|
GrpcFlowControlPolicy.java:59 return switch (slowConsumerPolicy) {
|
|
GrpcFlowControlPolicy.java:60 case TERMINATE ->
|
|
GrpcFlowControlPolicy.java:61 new GrpcFlowControlDecision(
|
|
GrpcFlowControlPolicy.java:62 GrpcFlowControlDecision.Action.TERMINATE,
|
|
GrpcFlowControlPolicy.java:63 queuedMessages,
|
|
GrpcFlowControlPolicy.java:64 queuedBytes,
|
|
GrpcFlowControlPolicy.java:65 overflowsCount
|
|
GrpcFlowControlPolicy.java:66 ? "queue is at its " + maxQueuedMessages + "-message bound"
|
|
GrpcFlowControlPolicy.java:67 : "queue would exceed its " + maxQueuedBytes + "-byte bound");
|
|
GrpcFlowControlPolicy.java:68 case DROP_OLDEST ->
|
|
GrpcFlowControlPolicy.java:69 new GrpcFlowControlDecision(
|
|
GrpcFlowControlPolicy.java:70 GrpcFlowControlDecision.Action.DROP_OLDEST,
|
|
GrpcFlowControlPolicy.java:71 queuedMessages,
|
|
GrpcFlowControlPolicy.java:72 queuedBytes,
|
|
GrpcFlowControlPolicy.java:73 "the profile permits loss; dropping the oldest queued message");
|
|
GrpcFlowControlPolicy.java:74 };
|
|
GrpcFlowControlPolicy.java:75 }
|
|
GrpcFlowControlPolicy.java:76 if (!transportReady || queuedMessages >= highWaterMarkMessages) {
|
|
GrpcFlowControlPolicy.java:77 return new GrpcFlowControlDecision(
|
|
GrpcFlowControlPolicy.java:78 GrpcFlowControlDecision.Action.PAUSE,
|
|
GrpcFlowControlPolicy.java:79 queuedMessages,
|
|
GrpcFlowControlPolicy.java:80 queuedBytes,
|
|
GrpcFlowControlPolicy.java:81 transportReady
|
|
GrpcFlowControlPolicy.java:82 ? "queue reached its high-water mark of " + highWaterMarkMessages
|
|
GrpcFlowControlPolicy.java:83 : "the transport is not ready for more");
|
|
GrpcFlowControlPolicy.java:84 }
|
|
GrpcFlowControlPolicy.java:85 return new GrpcFlowControlDecision(
|
|
GrpcFlowControlPolicy.java:86 GrpcFlowControlDecision.Action.PROCEED, queuedMessages, queuedBytes, "within bounds");
|
|
GrpcFlowControlPolicy.java:87 }
|
|
바이트 경계를 두는 이유 :
|
|
GrpcFlowControlPolicy.java:6 * <p>Both a message count and a byte count, because either alone is unbounded in the other
|
|
GrpcFlowControlPolicy.java:7 * dimension: a thousand-message bound with no byte bound is a memory limit set by the largest
|
|
GrpcFlowControlPolicy.java:8 * message anyone ever sends, and a byte bound with no count bound is unbounded queue overhead.
|
|
|
|
# 이 타입을 참조하는 자리 — 경로 제한 없이 저장소 전체
|
|
$ git grep -n GrpcSerializedStreamWriter (pathspec 없음)
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3679:- Create: `modules/grpc/grpc-policy/src/main/jav
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3682:- Test: `modules/grpc/grpc-policy/src/test/java/
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3698:class GrpcSerializedStreamWriterTest {
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3701: var writer = GrpcSerializedStreamWriter.
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3716:./gradlew :modules:grpc:grpc-policy:test --tests
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3728:public final class GrpcSerializedStreamWriter<T>
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3731: private GrpcSerializedStreamWriter(int capac
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3736: public static <T> GrpcSerializedStreamWriter
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3738: return new GrpcSerializedStreamWriter<>(
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3763:./gradlew :modules:grpc:grpc-policy:test --tests
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md:3772:git add 'modules/grpc/grpc-policy/src/main/java/
|
|
docs/adr/ADR-GRPC-005-server-streaming-single-writer-and-resume.md:20:**A bounded queue drained by one writer.** `GrpcSe
|
|
main · GrpcSerializedStreamWriter.java:24 public final class GrpcSerializedStreamWriter<T> {
|
|
main · GrpcSerializedStreamWriter.java:43 public GrpcSerializedStreamWriter(
|
|
test · GrpcSerializedStreamWriterTest.java:15 class GrpcSerializedStreamWriterTest {
|
|
test · GrpcSerializedStreamWriterTest.java:22 private GrpcSerializedStreamWriter<String> writer(
|
|
test · GrpcSerializedStreamWriterTest.java:24 return new GrpcSerializedStreamWriter<>(
|
|
test · GrpcSerializedStreamWriterTest.java:31 GrpcSerializedStreamWriter<String> writer = writer(GrpcFlowControlP
|
|
test · GrpcSerializedStreamWriterTest.java:47 GrpcSerializedStreamWriter<String> writer =
|
|
test · GrpcSerializedStreamWriterTest.java:87 GrpcSerializedStreamWriter<String> writer =
|
|
test · GrpcSerializedStreamWriterTest.java:103 GrpcSerializedStreamWriter<String> writer =
|
|
test · GrpcSerializedStreamWriterTest.java:116 GrpcSerializedStreamWriter<String> writer = writer(GrpcFlowControl
|
|
test · GrpcSerializedStreamWriterTest.java:129 GrpcSerializedStreamWriter<String> writer = writer(GrpcFlowControl
|
|
자기 파일 밖 main 참조 : 0 개 파일
|
|
|
|
# 대응 시험이 크기 공급자에 주는 값
|
|
GrpcSerializedStreamWriterTest.java:22 private GrpcSerializedStreamWriter<String> writer(
|
|
GrpcSerializedStreamWriterTest.java:23 GrpcFlowControlPolicy policy, long messageSize) {
|
|
GrpcSerializedStreamWriterTest.java:24 return new GrpcSerializedStreamWriter<>(
|
|
GrpcSerializedStreamWriterTest.java:25 STREAM, GrpcStreamSequence.forNewStream(STREAM), policy, sent::add, () -> messageSize);
|
|
GrpcSerializedStreamWriterTest.java:26 }
|
|
GrpcSerializedStreamWriterTest.java:31 GrpcSerializedStreamWriter<String> writer = writer(GrpcFlowControlPolicy.stable(), 16L);
|
|
GrpcSerializedStreamWriterTest.java:49 new GrpcFlowControlPolicy(1000, 1_000_000L, 999, GrpcSlowConsumerPolicy.TERMINATE), 1L);
|
|
GrpcSerializedStreamWriterTest.java:88 writer(new GrpcFlowControlPolicy(1, 1024L, 1, GrpcSlowConsumerPolicy.TERMINATE), 8L);
|
|
GrpcSerializedStreamWriterTest.java:104 writer(new GrpcFlowControlPolicy(1, 1024L, 1, GrpcSlowConsumerPolicy.DROP_OLDEST), 8L);
|
|
GrpcSerializedStreamWriterTest.java:116 GrpcSerializedStreamWriter<String> writer = writer(GrpcFlowControlPolicy.stable(), 8L);
|
|
GrpcSerializedStreamWriterTest.java:129 GrpcSerializedStreamWriter<String> writer = writer(GrpcFlowControlPolicy.stable(), 8L);
|
|
DROP_OLDEST 를 쓰는 시험이 단언하는 것 :
|
|
GrpcSerializedStreamWriterTest.java:101 @DisplayName("a lossy profile drops the oldest and reports it rather than hiding it")
|
|
GrpcSerializedStreamWriterTest.java:102 void aLossyProfileReportsWhatItDropped() {
|
|
GrpcSerializedStreamWriterTest.java:103 GrpcSerializedStreamWriter<String> writer =
|
|
GrpcSerializedStreamWriterTest.java:104 writer(new GrpcFlowControlPolicy(1, 1024L, 1, GrpcSlowConsumerPolicy.DROP_OLDEST), 8L);
|
|
GrpcSerializedStreamWriterTest.java:105 writer.write(GrpcStreamEnvelope.Kind.LIVE, "first", null, null, true);
|
|
GrpcSerializedStreamWriterTest.java:106
|
|
GrpcSerializedStreamWriterTest.java:107 assertThat(writer.write(GrpcStreamEnvelope.Kind.LIVE, "second", null, null, true))
|
|
GrpcSerializedStreamWriterTest.java:108 .isEqualTo(GrpcStreamWriteResult.DROPPED);
|
|
GrpcSerializedStreamWriterTest.java:109 assertThat(writer.droppedMessages()).isEqualTo(1);
|
|
GrpcSerializedStreamWriterTest.java:110 assertThat(writer.queuedMessages()).isEqualTo(1);
|
|
GrpcSerializedStreamWriterTest.java:111 }
|
|
GrpcSerializedStreamWriterTest.java:112
|
|
|
|
# 프로브 — 두 방향과 TERMINATE 가능성
|
|
# 실제 큐는 writer 의 queue 필드를 읽어 봉투마다 원래 크기를 되찾아 합산한다
|
|
|
|
가. 작은 것을 버리며 큰 것을 넣는다 — 과소 계상
|
|
정책 : 메시지 상한 3 · 바이트 상한 1000000 · DROP_OLDEST
|
|
n insize result writerBytes realQueue err
|
|
1 100 ACCEPTED 100 100 0
|
|
2 100 ACCEPTED 200 200 0
|
|
3 100 ACCEPTED 300 300 0
|
|
4 1000 DROPPED 1000 1200 -200
|
|
5 1000 DROPPED 1000 2100 -1100
|
|
6 1000 DROPPED 1000 3000 -2000
|
|
7 1000 DROPPED 1000 3000 -2000
|
|
8 1000 DROPPED 1000 3000 -2000
|
|
9 1000 DROPPED 1000 3000 -2000
|
|
남은 메시지 3 · 버린 수 6 · 상태 OPEN
|
|
|
|
나. 큰 것을 버리며 작은 것을 넣는다 — 과대 계상
|
|
정책 : 메시지 상한 3 · 바이트 상한 25000 · DROP_OLDEST
|
|
n insize result writerBytes realQueue err
|
|
1 10000 ACCEPTED 10000 10000 0
|
|
2 10000 ACCEPTED 20000 20000 0
|
|
3 10 ACCEPTED 20010 20010 0
|
|
4 10 DROPPED 20010 10020 9990
|
|
5 10 DROPPED 20010 30 19980
|
|
6 10 DROPPED 20010 30 19980
|
|
7 5000 DROPPED 20010 5020 14990
|
|
8 10 DROPPED 20010 5020 14990
|
|
9 10 DROPPED 20010 5020 14990
|
|
남은 메시지 3 · 버린 수 6 · 상태 OPEN
|
|
|
|
# DROP_OLDEST 를 쓰는 writer 가 TERMINATE 결정을 받을 수 있는가
|
|
decide 를 156282 조합으로 불렀을 때 TERMINATE 가 나온 횟수 : 0
|
|
그 정책 값으로 낼 수 있는 결정 : [DROP_OLDEST, PAUSE, PROCEED]
|