Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/a20-f008-grpcserializedstreamwriter-drop-oldest.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
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>
2026-09-04 22:51:59 +09:00

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]