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>
15 lines
4.9 KiB
JSON
15 lines
4.9 KiB
JSON
{
|
|
"assetKey": "a10-f005-hyperloglog-merge-budget",
|
|
"kind": "terminal",
|
|
"command": "set -e\nD=$(mktemp -d); trap 'rm -rf \"$D\"' EXIT\nJAR=/shared/codebase/clean-architecture-backend-template/src/adapter/outbound/cache-redis/build/libs/cache-redis-0.0.1+21234e38cdb9.jar\ncat > \"$D/BudgetFillProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.RedisDeploymentMode;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.AdvancedOperationPermit;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.MultiKeyPermit;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.OperationBudget;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.PersistentKeyPermit;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.RedisPermitVerifier;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.RedisPolicyAuthority;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisKeyRenderer;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisNamespace;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.operations.RedisOperationContext;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.operations.RedisOperationLimits;\n\npublic final class BudgetFillProbe {\n public static void main(String[] args) {\n RedisOperationLimits limits = RedisOperationLimits.defaults();\n RedisOperationContext ctx =\n new RedisOperationContext(\n new RedisNamespace(\"prod\", \"orders\", \"cache\"),\n new RedisKeyRenderer(512),\n new RedisPermitVerifier() {\n public void verify(AdvancedOperationPermit p, String s) {}\n public void verify(MultiKeyPermit p, String s) {}\n public void verify(PersistentKeyPermit p, String s) {}\n },\n new RedisPolicyAuthority() {\n public AdvancedOperationPermit issueAdvanced(String s) { return null; }\n public MultiKeyPermit issueMultiKey(String s) { return null; }\n public PersistentKeyPermit issuePersistentKey(String s) { return null; }\n },\n limits,\n RedisDeploymentMode.STANDALONE);\n\n long keyCeiling = dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key\n .RedisKeyRules.MAX_KEY_BYTES;\n long pathMax = keyCeiling * limits.maxCollectionElements();\n\n System.out.println(\"[SDK가 채워 넣는 budget] collectionBudget(키 수, 요청 바이트)\");\n for (long bytes : new long[] {512L, 4_096L, pathMax}) {\n OperationBudget b = ctx.collectionBudget(8, bytes);\n System.out.printf(\n \" 요청 %-9d -> maxRequestBytes %-9d allowsRequestBytes(요청) %s%n\",\n bytes, b.maxRequestBytes(), b.allowsRequestBytes(bytes));\n }\n System.out.printf(\n \" 이 경로의 요청 최대 : 렌더된 키 %d 바이트 x 요소 천장 %d = %d 바이트%n\",\n keyCeiling, limits.maxCollectionElements(), pathMax);\n\n System.out.println();\n System.out.println(\"[호출자가 건네는 budget] 같은 요청을 거부할 수 있다\");\n OperationBudget declared = new OperationBudget(8, 4_096L, 65_536L, java.time.Duration.ofSeconds(2));\n System.out.printf(\n \" maxRequestBytes %d, 요청 8192 -> allowsRequestBytes %s%n\",\n declared.maxRequestBytes(), declared.allowsRequestBytes(8_192L));\n\n System.out.println();\n System.out.println(\"[채워 넣은 budget이 실제로 거부하는 것]\");\n OperationBudget b = ctx.collectionBudget(limits.maxCollectionElements(), 1_024L);\n System.out.printf(\" 요소 %d개 -> 통과, maxReplyBytes %d, timeout %s%n\",\n limits.maxCollectionElements(), b.maxReplyBytes(), b.timeout());\n try {\n ctx.collectionBudget(limits.maxCollectionElements() + 1, 1_024L);\n System.out.println(\" 요소 1001개 -> 통과\");\n } catch (RuntimeException e) {\n System.out.println(\" 요소 1001개 -> \" + e.getClass().getSimpleName() + \": \" + e.getMessage());\n }\n\n System.out.println();\n System.out.println(\"[PFCOUNT/PFMERGE가 선언하는 expectedReplyBytes] 0\");\n System.out.println(\" allowsReplyBytes(0) = \" + b.allowsReplyBytes(0L));\n }\n}\nJAVA\njava -version 2>&1 | head -1\njavac -encoding UTF-8 -cp \"$JAR\" -d \"$D\" \"$D/BudgetFillProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$JAR:$D\" BudgetFillProbe\n",
|
|
"cwd": "/shared/codebase/clean-architecture-backend-template/src/adapter/outbound/cache-redis",
|
|
"exitCode": 0,
|
|
"executedAt": "2026-09-02T12:53:03+00:00",
|
|
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
|
|
"raw": "evidence/raw/a10-f005-hyperloglog-merge-budget.txt",
|
|
"svg": "evidence/rendered/a10-f005-hyperloglog-merge-budget.svg",
|
|
"rawSha256": "81684a8a2afc5b3e2d02421c8cdaf46c1e345ba8160c0beb82a942f25446dc19",
|
|
"lines": 16,
|
|
"redaction": "none — 밀폐 탐침 실행 출력, 자격증명 없음"
|
|
}
|