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
6.0 KiB
JSON
15 lines
6.0 KiB
JSON
{
|
|
"assetKey": "a10-f004-pub-sub-bound",
|
|
"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/PubSubBoundProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.codec.RedisCodec;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.QualifiedRedisKey;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisKeyName;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisKeyRenderer;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisKeyRules;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisNamespace;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisSlotTag;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.operations.PubSubChannel;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.operations.PubSubPattern;\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.operations.ShardedPubSubChannel;\nimport java.nio.charset.StandardCharsets;\n\npublic final class PubSubBoundProbe {\n\n private static String token(int n) {\n return \"a\".repeat(n - 1) + \"z\";\n }\n\n private static int bytes(String s) {\n return s.getBytes(StandardCharsets.UTF_8).length;\n }\n\n private static final RedisCodec<String> CODEC =\n new RedisCodec<>() {\n public String id() { return \"probe-utf8\"; }\n public byte[] encode(String v) { return v.getBytes(StandardCharsets.UTF_8); }\n public String decode(byte[] b) { return new String(b, StandardCharsets.UTF_8); }\n };\n\n public static void main(String[] args) {\n RedisNamespace ns = new RedisNamespace(token(64), token(64), token(64));\n RedisKeyName name = new RedisKeyName(token(64), token(128));\n\n System.out.println(\"MAX_KEY_BYTES = \" + RedisKeyRules.MAX_KEY_BYTES);\n System.out.println();\n System.out.println(\"[구성 요소의 상한] 토큰 64, 식별자 128, 슬롯 태그 128\");\n System.out.println(\" namespace.prefix() 길이 : \" + bytes(ns.prefix()));\n System.out.println();\n\n System.out.println(\"[채널] 구성 요소를 최대로 채운 렌더\");\n String ch = new PubSubChannel<>(ns, name, CODEC).render();\n String sh = new ShardedPubSubChannel<>(ns, name, CODEC).render();\n System.out.printf(\" PubSubChannel 렌더 %d 바이트 여유 %d%n\",\n bytes(ch), RedisKeyRules.MAX_KEY_BYTES - bytes(ch));\n System.out.printf(\" ShardedPubSubChannel 렌더 %d 바이트 여유 %d%n\",\n bytes(sh), RedisKeyRules.MAX_KEY_BYTES - bytes(sh));\n System.out.println();\n\n System.out.println(\"[키] 같은 규칙을 받은 조각들, 슬롯 태그 하나가 더 붙는다\");\n RedisKeyRenderer renderer = new RedisKeyRenderer(RedisKeyRules.MAX_KEY_BYTES);\n System.out.println(\" 태그 없음 -> 렌더 \" + bytes(renderer.render(QualifiedRedisKey.of(ns, name))) + \" 바이트\");\n QualifiedRedisKey tagged =\n QualifiedRedisKey.tagged(ns, name, new RedisSlotTag(token(128)));\n try {\n System.out.println(\" 태그 있음 -> 렌더 \" + bytes(renderer.render(tagged)) + \" 바이트\");\n } catch (IllegalArgumentException e) {\n System.out.println(\" 태그 있음 -> \" + e.getMessage());\n }\n System.out.println();\n\n System.out.println(\"[기본 이름공간] 설정 기본값 local:sample-service:shared\");\n RedisNamespace def = new RedisNamespace(\"local\", \"sample-service\", \"shared\");\n System.out.println(\" prefix 길이 : \" + bytes(def.prefix()));\n System.out.println(\" 채널 렌더 : \" + bytes(new PubSubChannel<>(def, name, CODEC).render()) + \" 바이트\");\n QualifiedRedisKey defTagged =\n QualifiedRedisKey.tagged(def, name, new RedisSlotTag(token(128)));\n try {\n System.out.println(\" 태그 붙인 키 : \" + bytes(renderer.render(defTagged)) + \" 바이트\");\n } catch (IllegalArgumentException e) {\n System.out.println(\" 태그 붙인 키 -> \" + e.getMessage());\n }\n System.out.println();\n\n System.out.println(\"[패턴] 접미가 토큰 규칙을 받지 않는다 (이름공간은 위와 같은 최대치)\");\n for (int n : new int[] {64, 317, 318}) {\n try {\n PubSubPattern<String> p = new PubSubPattern<>(ns, token(n), CODEC);\n System.out.printf(\" suffix %-4d -> 생성됨, 렌더 %d 바이트%n\", n, bytes(p.render()));\n } catch (IllegalArgumentException e) {\n System.out.printf(\" suffix %-4d -> %s%n\", n, e.getMessage());\n }\n }\n System.out.println();\n\n System.out.println(\"[상한을 어디서 가져오는가]\");\n System.out.println(\" RedisKeyRenderer : 생성자 인자 (1..512)\");\n System.out.println(\" PubSubPattern : RedisKeyRules.MAX_KEY_BYTES 상수\");\n RedisKeyRenderer tight = new RedisKeyRenderer(256);\n try {\n tight.render(QualifiedRedisKey.of(ns, name));\n System.out.println(\" 상한 256 렌더러에 키 388 바이트 -> 통과\");\n } catch (IllegalArgumentException e) {\n System.out.println(\" 상한 256 렌더러에 키 -> \" + e.getMessage());\n }\n System.out.println(\" 같은 배포의 채널 \" + bytes(ch) + \" 바이트 -> 검사 없음\");\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$JAR\" -d \"$D\" \"$D/PubSubBoundProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$JAR:$D\" PubSubBoundProbe\n",
|
|
"cwd": "/shared/codebase/clean-architecture-backend-template/src/adapter/outbound/cache-redis",
|
|
"exitCode": 0,
|
|
"executedAt": "2026-09-02T11:21:34+00:00",
|
|
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
|
|
"raw": "evidence/raw/a10-f004-pub-sub-bound.txt",
|
|
"svg": "evidence/rendered/a10-f004-pub-sub-bound.svg",
|
|
"rawSha256": "f9ca0446a9dbdba46c9eef12f89a1d325b8876df7f04fff16e7be9193849dee1",
|
|
"lines": 28,
|
|
"redaction": "none — 밀폐 탐침 실행 출력, 자격증명 없음"
|
|
}
|