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>
142 lines
9.4 KiB
XML
142 lines
9.4 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1228" viewBox="0 0 1200 1228" role="img">
|
|
<title>terminal evidence</title>
|
|
<desc>Terminal-style rendering generated from retained command output. Sensitive-looking values are redacted in the visual asset.</desc>
|
|
<rect x="1" y="1" width="1198" height="1226" rx="14" fill="#0d1117" stroke="#30363d"/>
|
|
<rect x="1" y="1" width="1198" height="44" rx="14" fill="#161b22"/>
|
|
<rect x="1" y="30" width="1198" height="14" fill="#161b22"/>
|
|
<circle cx="24" cy="22" r="6" fill="#ff5f57"/>
|
|
<circle cx="44" cy="22" r="6" fill="#febc2e"/>
|
|
<circle cx="64" cy="22" r="6" fill="#28c840"/>
|
|
<text x="92" y="27" fill="#8b949e" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">terminal evidence</text>
|
|
<text x="24" y="68" fill="#c9d1d9" font-size="15" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">$ set -e
|
|
D=$(mktemp -d)
|
|
trap 'rm -rf "$D"' EXIT
|
|
mkdir -p "$D/src/dev/caskeleton/adapter/outbound/fileserver/platform/loc</tspan><tspan x="24" dy="22">al"
|
|
|
|
cat > "$D/src/dev/caskeleton/adapter/outbound/fileserver/platform/local/BorrowPeakProbe.java" <<'JAVA'
|
|
package dev.</tspan><tspan x="24" dy="22">caskeleton.adapter.outbound.fileserver.platform.local;
|
|
|
|
import dev.caskeleton.application.fileserver.api.StorageNamespac</tspan><tspan x="24" dy="22">e;
|
|
import dev.caskeleton.application.fileserver.api.UploadId;
|
|
import dev.caskeleton.application.fileserver.api.content.A</tspan><tspan x="24" dy="22">ppendResult;
|
|
import dev.caskeleton.application.fileserver.api.content.WriteFence;
|
|
import java.io.IOException;
|
|
import jav</tspan><tspan x="24" dy="22">a.nio.ByteBuffer;
|
|
import java.nio.channels.ReadableByteChannel;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
i</tspan><tspan x="24" dy="22">mport java.util.List;
|
|
import java.util.UUID;
|
|
|
|
/** Runs the same append the bounded-memory regression runs, at several pa</tspan><tspan x="24" dy="22">yload sizes. */
|
|
public final class BorrowPeakProbe {
|
|
|
|
private static final int BUFFER_BYTES = 128 * 1024;
|
|
|
|
public st</tspan><tspan x="24" dy="22">atic void main(String[] args) throws IOException {
|
|
Path root = Files.createTempDirectory("borrow-peak");
|
|
System.</tspan><tspan x="24" dy="22">out.println("BUFFER_BYTES = " + BUFFER_BYTES);
|
|
System.out.println();
|
|
System.out.println("payload maxB</tspan><tspan x="24" dy="22">orrowedBytes BUFFER_BYTES 이하 버퍼 크기의 배수");
|
|
for (long payload : List.of(
|
|
1L * 1024 * 1024, 16L * </tspan><tspan x="24" dy="22">1024 * 1024, 256L * 1024 * 1024)) {
|
|
long peak = peakFor(root, payload);
|
|
System.out.printf(
|
|
"%-18s </tspan><tspan x="24" dy="22">%-18d %-18s %d%n",
|
|
(payload / (1024 * 1024)) + "MiB", peak,
|
|
peak <= BUFFER_BYTES ? "예" : "아니오", </tspan><tspan x="24" dy="22">peak / BUFFER_BYTES);
|
|
}
|
|
System.out.println();
|
|
System.out.println("[대조] 반납 없이 두 번 빌리면 그 단언이 깨</tspan><tspan x="24" dy="22">진다");
|
|
TransferBufferPool leaking = new TransferBufferPool(BUFFER_BYTES, 4);
|
|
leaking.borrow();
|
|
leaking.borr</tspan><tspan x="24" dy="22">ow();
|
|
System.out.println(" maxBorrowedBytes = " + leaking.maxBorrowedBytes()
|
|
+ " BUFFER_BYTES 이하 = " + (</tspan><tspan x="24" dy="22">leaking.maxBorrowedBytes() <= BUFFER_BYTES));
|
|
}
|
|
|
|
private static long peakFor(Path root, long payload) throws IOExcep</tspan><tspan x="24" dy="22">tion {
|
|
TransferBufferPool pool = new TransferBufferPool(BUFFER_BYTES, 4);
|
|
runAppend(root, pool, payload);
|
|
re</tspan><tspan x="24" dy="22">turn pool.maxBorrowedBytes();
|
|
}
|
|
|
|
private static void runAppend(Path root, TransferBufferPool pool, long payload)
|
|
</tspan><tspan x="24" dy="22"> throws IOException {
|
|
SafeFileChannelFactory channels = new SafeFileChannelFactory(true);
|
|
LocalAppendEngine en</tspan><tspan x="24" dy="22">gine = new LocalAppendEngine(channels, pool);
|
|
UploadId uploadId = UploadId.of(UUID.randomUUID());
|
|
Path staging =</tspan><tspan x="24" dy="22"> new DefaultPhysicalPathResolver(root).stagingPath(uploadId);
|
|
Files.createDirectories(staging.getParent());
|
|
File</tspan><tspan x="24" dy="22">s.createFile(staging);
|
|
LocalUploadHandle handle =
|
|
new LocalUploadHandle(uploadId, StorageNamespace.of("tenan</tspan><tspan x="24" dy="22">t-a"), staging, Long.MAX_VALUE);
|
|
AppendResult result =
|
|
engine.append(root, handle, 0, new Generated(payload)</tspan><tspan x="24" dy="22">, payload, WriteFence.unfenced());
|
|
if (result.committedOffset() != payload) {
|
|
throw new IllegalStateException(</tspan><tspan x="24" dy="22">"unexpected committed offset");
|
|
}
|
|
}
|
|
|
|
private static final class Generated implements ReadableByteChannel {
|
|
p</tspan><tspan x="24" dy="22">rivate long remaining;
|
|
private byte next;
|
|
|
|
private Generated(long total) {
|
|
this.remaining = total;
|
|
}
|
|
|
|
</tspan><tspan x="24" dy="22"> @Override
|
|
public int read(ByteBuffer destination) {
|
|
if (remaining <= 0) {
|
|
return -1;
|
|
}
|
|
</tspan><tspan x="24" dy="22"> int count = (int) Math.min(destination.remaining(), remaining);
|
|
for (int index = 0; index < count; index++) {
|
|
</tspan><tspan x="24" dy="22"> destination.put(next++);
|
|
}
|
|
remaining -= count;
|
|
return count;
|
|
}
|
|
|
|
@Override
|
|
public bool</tspan><tspan x="24" dy="22">ean isOpen() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void close() {}
|
|
}
|
|
}
|
|
JAVA
|
|
|
|
CACHE=/root/.gradle/caches</tspan><tspan x="24" dy="22">/modules-2/files-2.1
|
|
LOCK=src/adapter/outbound/fileserver/gradle.lockfile
|
|
CP=src/adapter/outbound/fileserver/build/class</tspan><tspan x="24" dy="22">es/java/main
|
|
CP=$CP:src/application-core/build/classes/java/main:src/domain-core/build/classes/java/main
|
|
while IFS= read</tspan><tspan x="24" dy="22"> -r line; do
|
|
coord=${line%%=*}; confs=${line#*=}
|
|
case ",$confs," in *",testRuntimeClasspath,"*) ;; *) continue ;; es</tspan><tspan x="24" dy="22">ac
|
|
g=${coord%%:*}; r=${coord#*:}; n=${r%%:*}; v=${r##*:}
|
|
jar=$(find "$CACHE/$g/$n/$v" -name '*.jar' ! -name '*source</tspan><tspan x="24" dy="22">s*' ! -name '*javadoc*' 2>/dev/null | head -1)
|
|
[ -n "$jar" ] && CP="$CP:$jar"
|
|
done < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+</tspan><tspan x="24" dy="22">=' "$LOCK")
|
|
|
|
javac -encoding UTF-8 -cp "$CP" -d "$D/out" \
|
|
"$D/src/dev/caskeleton/adapter/outbound/fileserver/platform</tspan><tspan x="24" dy="22">/local/BorrowPeakProbe.java"
|
|
java -Dstdout.encoding=UTF-8 -cp "$D/out:$CP" \
|
|
dev.caskeleton.adapter.outbound.fileserve</tspan><tspan x="24" dy="22">r.platform.local.BorrowPeakProbe</tspan></text>
|
|
<text x="24" y="926" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">cwd: /shared/codebase/clean-architecture-backend-template</tspan></text>
|
|
<text x="24" y="948" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">time: 2026-09-02T10:10:59+00:00 · exit 0</tspan></text>
|
|
<line x1="24" y1="962" x2="1176" y2="962" stroke="#30363d"/>
|
|
<text x="24" y="1002" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">BUFFER_BYTES = 131072</tspan></text>
|
|
<text x="24" y="1024" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1046" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">payload maxBorrowedBytes BUFFER_BYTES 이하 버퍼 크기의 배수</tspan></text>
|
|
<text x="24" y="1068" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">1MiB 131072 예 1</tspan></text>
|
|
<text x="24" y="1090" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">16MiB 131072 예 1</tspan></text>
|
|
<text x="24" y="1112" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">256MiB 131072 예 1</tspan></text>
|
|
<text x="24" y="1134" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1156" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[대조] 반납 없이 두 번 빌리면 그 단언이 깨진다</tspan></text>
|
|
<text x="24" y="1178" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> maxBorrowedBytes = 262144 BUFFER_BYTES 이하 = false</tspan></text>
|
|
</svg>
|