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>
163 lines
17 KiB
Plaintext
163 lines
17 KiB
Plaintext
Sub-scope 06 (cache-redis) programmability + extensions + raw + cluster + admin — manifest and probes
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-30T04:50:38+00:00
|
|
|
|
=== OWNED FILES ===
|
|
a96d272530e00a0e0dd347e8609fb93d394c5d02 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/admin/ClientSummary.java 26
|
|
eef5784a152033db9aaafab8a8552c9c48e8d6d9 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/admin/LettuceRedisAdminOperations.java 337
|
|
c71ea8639d9b6c841dbd7fc395d493142b037d0e main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/admin/RedisAdminOperations.java 103
|
|
11f7f121af974e920697a2fef0a9e89a091b8388 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/admin/SlowLogEntry.java 28
|
|
892ad2b70c9491618f2a969508a36b2454d7bb33 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/ClusterRedirect.java 11
|
|
5f88a8fe5ba0f65b6fbc061749e658dfc0026818 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/ClusterScanCursor.java 115
|
|
8b2da0f91befbff1e3844d1ebb346949e645cf9e main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/ClusterTopologyObserver.java 99
|
|
3c5ad12b5807b3c8665627173ffa22ceaf2d28bb main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/RedisSlotCalculator.java 76
|
|
be06a40a03780b990088658ecc384a99998e9e3d main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/SameSlotValidator.java 62
|
|
4f5aaf19e0274f67c0e07d9bbeb5993513b47078 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/ExtensionCommandRunner.java 145
|
|
c3b363a59dde0e34917d2077a7210c1550a3569f main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/json/JsonPath.java 33
|
|
a69c3d7456c3c1e89e661a333c278917ff217781 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/json/LettuceRedisJsonOperations.java 170
|
|
c873224e54cb0cff685f206cd418a450d2fb3fe1 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/json/RedisJsonOperations.java 89
|
|
b6453d9d1cb02c1fa13bd01e300b35211e3257b7 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/probabilistic/LettuceRedisProbabilisticOperations.java 220
|
|
7630e6e8491cd8112f33ca38af770ca1e882f452 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/probabilistic/RedisProbabilisticOperations.java 141
|
|
664f6beb33d58a2eb538c4182e8f6f558e909047 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/search/LettuceRedisSearchOperations.java 187
|
|
00389d7f9a3a914a3aa7a1db9b6c6d349c74e7c1 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/search/RedisSearchOperations.java 40
|
|
fd08c5e1e4b773d9298579f64b917ab77b240e9a main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/search/SearchField.java 29
|
|
44396587a7dbaa32de06435a0284b6edca85ff97 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/search/SearchFieldType.java 15
|
|
58dfc9818157a595f2200e1d034476b54fd87bd3 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/search/SearchHit.java 20
|
|
f572923191d18561173da8f82288bcafe24aea56 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/search/SearchIndex.java 28
|
|
e29dcb123fac6f8f79ab950af0b08e949f3549d6 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/search/SearchQuery.java 37
|
|
90d7ce07c18807bb3897788b3ef3ec12663f7093 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/timeseries/LettuceRedisTimeSeriesOperations.java 230
|
|
4554c9f148bce069e0826ca701122483f91c6130 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/timeseries/RedisTimeSeriesOperations.java 87
|
|
a6a318b4c81bc05da437c9cb96a482394c33d360 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/timeseries/TimeSeriesAggregation.java 19
|
|
57ffae54d087fb8d2b7626409c260186abb563e3 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/timeseries/TimeSeriesSample.java 18
|
|
ffe5ff5f3f6159a75605eb2d19e796ff8ad9feeb main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisFunctionOperations.java 123
|
|
a30567a4ad332251994395fa81001c2f86ac7ac5 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisScriptOperations.java 140
|
|
7052138c85751ab47538875a5c1d89d27e93e49f main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisTransactionOperations.java 361
|
|
b8c11bc0db6db153febdf14c1d5b9ef37ee81b64 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/QueuedReply.java 62
|
|
fee4b2d4bcbee26da20f9ec54494ce234bb07cb6 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisArgument.java 79
|
|
b317370ce321d30553ae82ad4122d7326f579785 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisFunctionOperations.java 27
|
|
52c9cf84d5ac0b9996a49528a49217d844f02805 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisResultDecoder.java 23
|
|
f18e215a8f4a8721fd3f11588f8e55c0bbd2b39c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisScriptOperations.java 25
|
|
7b4283f19f9ab6dd8a39b1ea14bfe56b4a7df822 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisScriptRegistry.java 128
|
|
160fd61368810d658885c947a3ff665a106efebc main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisTransactionCallback.java 24
|
|
bcae2201a01ac32aaae6f3cbb78bb8751a55403e main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisTransactionOperations.java 44
|
|
3dd8791b015a4eac07bfda5770237e57c27d2761 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisTransactionQueue.java 107
|
|
398da58e738e26315e72f6b15b778ddeb1312e12 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisTransactionRunner.java 139
|
|
da029d8a64886770ebac908f523c85d90ccad872 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RegisteredRedisFunction.java 71
|
|
38aa79072a79349524a265f456c36199462753b2 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RegisteredRedisScript.java 55
|
|
b45f96424cb3bd1d7dc10c7345e8114fd2a3ab8a main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/TransactionOptions.java 38
|
|
bd824883d2f8f1b5fd41abf33c377cc58a3d6b15 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/TransactionResult.java 58
|
|
b0669f65ddc9307de863f894133c0f4d99dd9bdd main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/ApprovedRawCommand.java 52
|
|
cd7de98ad510b43409eed3b4c7abca59ec16ec79 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/LettuceRedisRawGateway.java 160
|
|
6a8b3fb96cfcc3f57a51d9dca4a13830e96e3d7c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/RawCommandApprovals.java 135
|
|
476623571362638e61c82cc0e495bd6a51c3e35c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/RawCommandPolicyToken.java 19
|
|
bc090db339e51f4d87284548b77ba8afd4ce4554 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/RawMovableKeys.java 126
|
|
f636007dde006a4c7b5d512a5c804d1cfa26b479 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/RawReplyDecoder.java 26
|
|
f8d7510bc4d99c9d51b15c82e8b97e965462224d main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/RedisRawGateway.java 28
|
|
463ef6e04c67696db1e4e3fa08ffc3d6459b9926 test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/ClusterObservationTest.java 86
|
|
06787545122a68ed7f1fdf9b14ad49308bc15ad5 test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/RedisSlotCalculatorTest.java 83
|
|
2ee571239fc65cd1e1d434e062ccb9d53a4e38b1 test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LiveRedisClusterTransactionTest.java 242
|
|
57b89ef07fad298cd6c97a2043e93cd979cdd084 test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/RawMovableKeysTest.java 104
|
|
|
|
main: 50 test: 4
|
|
=== 8.1 reachability: 이 sub-scope의 진입 타입들 ===
|
|
LettuceRedisRawGateway: main 참조 파일 0
|
|
RawCommandApprovals: main 참조 파일 2
|
|
RedisTransactionRunner: main 참조 파일 0
|
|
RedisScriptRegistry: main 참조 파일 1
|
|
LettuceRedisAdminOperations: main 참조 파일 0
|
|
ExtensionCommandRunner: main 참조 파일 4
|
|
ClusterTopologyObserver: main 참조 파일 0
|
|
ClusterScanCursor: main 참조 파일 0
|
|
RedisSlotCalculator: main 참조 파일 3
|
|
SameSlotValidator: main 참조 파일 2
|
|
=== 8.2 README가 주장하는 EVALSHA -> NOSCRIPT -> SCRIPT LOAD -> digest verify -> EVALSHA ===
|
|
$ git grep -n 'forget(' -- 'adapter/outbound/cache-redis/src/main'
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisScriptOperations.java:100: registry.forget(script.id());
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisScriptRegistry.java:23: * both invalidate it server side; {@link #forget(String)} is how the one caller that can detect
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisScriptRegistry.java:97: public void forget(String id) {
|
|
exit=0
|
|
|
|
$ git grep -n 'NOSCRIPT\|NoScript' -- 'adapter/outbound/cache-redis/src/main' | head -20
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/idempotency/IdempotencyScripts.java:263: return message != null && message.strip().toUpperCase(Locale.ROOT).startsWith("NOSCRIPT");
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/lease/LeaseScripts.java:161: return message != null && message.strip().toUpperCase(Locale.ROOT).startsWith("NOSCRIPT");
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RateLimitScripts.java:30: * <p>Loaded once and called by digest. A {@code NOSCRIPT} means the server rejected the call before
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RateLimitScripts.java:241: return message != null && message.strip().toUpperCase(Locale.ROOT).startsWith("NOSCRIPT");
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisNoScriptException.java:4:public class RedisNoScriptException extends RedisOperationException {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisNoScriptException.java:14: public RedisNoScriptException(String reason, RedisFailureMetadata metadata) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisNoScriptException.java:25: public RedisNoScriptException(String reason, RedisFailureMetadata metadata, Throwable cause) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/LettuceExceptionTranslator.java:92: if (cause instanceof io.lettuce.core.RedisNoScriptException) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/LettuceExceptionTranslator.java:93: return new dev.caskeleton.adapter.outbound.cache.redis.sdk.api.error.RedisNoScriptException(
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/LettuceExceptionTranslator.java:190: if ("NOSCRIPT".equals(code)) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/LettuceExceptionTranslator.java:191: return new dev.caskeleton.adapter.outbound.cache.redis.sdk.api.error.RedisNoScriptException(
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/AtomicCounterScripts.java:31: * <p>The digest is resolved once and cached. A {@code NOSCRIPT} reply means the server rejected the
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/AtomicCounterScripts.java:190: if (cause instanceof io.lettuce.core.RedisNoScriptException) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/AtomicCounterScripts.java:195: dev.caskeleton.adapter.outbound.cache.redis.sdk.api.error.RedisNoScriptException) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/AtomicCounterScripts.java:199: return message != null && message.strip().toUpperCase(Locale.ROOT).startsWith("NOSCRIPT");
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisScriptOperations.java:5:import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.error.RedisNoScriptException;
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisScriptOperations.java:26: * <p>{@code NOSCRIPT} is the one failure retried automatically. The server rejects the call before
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisScriptOperations.java:129: if (cause instanceof RedisNoScriptException) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisScriptOperations.java:133: if (message != null && message.strip().toUpperCase(Locale.ROOT).startsWith("NOSCRIPT")) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisScriptRegistry.java:24: * that — a {@code NOSCRIPT} reply — drops the stale entry so the next call reloads.
|
|
exit=0
|
|
|
|
=== 8.3 슬롯 검사가 두 곳에 있는가 ===
|
|
$ git grep -n 'class SameSlotValidator' -A 20 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster/SameSlotValidator.java | head -30
|
|
fatal: option '-A' must come before non-option arguments
|
|
exit=0
|
|
|
|
$ git grep -n 'SameSlotValidator' -- 'adapter/outbound/cache-redis/src/main' | grep -v 'cluster/SameSlotValidator.java'
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisTransactionOperations.java:15:import dev.caskeleton.adapter.outbound.cache.redis.sdk.cluster.SameSlotValidator;
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisTransactionOperations.java:59: private final SameSlotValidator sameSlot;
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisTransactionOperations.java:75: SameSlotValidator sameSlot) {
|
|
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/RedisTransactionRunner.java:33: * <p>Cross-slot is still refused before anything is sent, by the same {@code SameSlotValidator} the
|
|
exit=0
|
|
|
|
$ grep -n 'requireSameSlot' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java
|
|
97: OptionalInt slot = requireSameSlot(policy, request);
|
|
188: private OptionalInt requireSameSlot(RedisCommandPolicy policy, CommandRequest<?> request) {
|
|
exit=0
|
|
|
|
=== 8.4 test 밀도 ===
|
|
$ git ls-files 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability' 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw' 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster' | xargs -n1 basename
|
|
ClusterObservationTest.java
|
|
RedisSlotCalculatorTest.java
|
|
LiveRedisClusterTransactionTest.java
|
|
RawMovableKeysTest.java
|
|
exit=0
|
|
|
|
$ git grep -h ' void [a-z]' -- 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability' 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw' 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/cluster' | sed 's/^ *//'
|
|
void reshardingIsDistinguishedFromStaleTopology() {
|
|
void slotsAreBounded() {
|
|
void topologyRefreshRecordsPrimaries() {
|
|
void sweepNeedsEveryPrimary() {
|
|
void sweepRefusesUnknownPrimaries() {
|
|
void matchesPublishedSlots() {
|
|
void bracesControlSlotCalculation() {
|
|
void emptyBracesHashTheWholeKey() {
|
|
void slotsStayInRange() {
|
|
void sameSlotRequiresAHashTag() {
|
|
void openRuntime() {
|
|
void closeRuntime() {
|
|
void aSameSlotTransactionExecutes() {
|
|
void aCrossSlotTransactionIsRefusedBeforeSend() {
|
|
void anUnroutableTransactionIsRefused() {
|
|
void anExplicitSlotTagRoutesTheWindow() {
|
|
void aPlainSortHasOneKey() {
|
|
void storeAddsTheDestination() {
|
|
void readOnlySortCannotStore() {
|
|
void patternsAreRefused() {
|
|
void anUnknownOptionIsRefused() {
|
|
void aMalformedLimitIsRefused() {
|
|
void anUnparsableCommandIsRefused() {
|
|
void anEmptySortIsRefused() {
|
|
exit=0
|
|
|
|
--- extensions / admin 전용 test 존재 여부 ---
|
|
$ git ls-files 'adapter/outbound/cache-redis/src/test' | grep -i 'extension\|admin\|json\|search\|timeseries\|probabilistic'
|
|
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/codec/VersionedJsonCodecTest.java
|
|
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisAdminPlaneContractTest.java
|
|
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisExtensionModulesContractTest.java
|
|
adapter/outbound/cache-redis/src/test/resources/redis-sdk/golden/order-summary-v1.json
|
|
exit=0
|
|
|