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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+170
@@ -0,0 +1,170 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1954" viewBox="0 0 1200 1954" 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="1952" 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 'docker rm -f mg-rs >/dev/null 2>&1; rm -rf "$D"' EXIT
|
||||
|
||||
docker rm -f mg-rs >/dev/null 2>&1 </tspan><tspan x="24" dy="22">|| true
|
||||
docker run -d --name mg-rs --network container:workmachine \
|
||||
mongo:8.0.16 --port 57020 --bind_ip 127.0.0.1 --r</tspan><tspan x="24" dy="22">eplSet rs0 >/dev/null
|
||||
for i in $(seq 1 60); do
|
||||
docker exec mg-rs mongosh --port 57020 --quiet --eval 'db.runCommand({p</tspan><tspan x="24" dy="22">ing:1}).ok' 2>/dev/null \
|
||||
| grep -q 1 && break
|
||||
sleep 1
|
||||
done
|
||||
docker exec mg-rs mongosh --port 57020 --quiet \
|
||||
--e</tspan><tspan x="24" dy="22">val 'rs.initiate({_id:"rs0",members:[{_id:0,host:"127.0.0.1:57020"}]})' >/dev/null 2>&1
|
||||
for i in $(seq 1 60); do
|
||||
docke</tspan><tspan x="24" dy="22">r exec mg-rs mongosh --port 57020 --quiet --eval 'db.hello().isWritablePrimary' 2>/dev/null \
|
||||
| grep -q true && brea</tspan><tspan x="24" dy="22">k
|
||||
sleep 1
|
||||
done
|
||||
|
||||
cat > "$D/LedgerFenceProbe.java" <<'JAVA'
|
||||
import com.mongodb.MongoWriteException;
|
||||
import com.mongodb.c</tspan><tspan x="24" dy="22">lient.MongoClients;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import dev.caskeleton.adapter.outbound.mongo.migration.Mong</tspan><tspan x="24" dy="22">oCollectionMigrationLedger;
|
||||
import dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationCheckpoint;
|
||||
import dev.</tspan><tspan x="24" dy="22">caskeleton.adapter.outbound.mongo.migration.MongoMigrationChecksum;
|
||||
import dev.caskeleton.adapter.outbound.mongo.migrati</tspan><tspan x="24" dy="22">on.MongoMigrationId;
|
||||
import java.time.Instant;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import or</tspan><tspan x="24" dy="22">g.bson.Document;
|
||||
|
||||
public final class LedgerFenceProbe {
|
||||
|
||||
private static final MongoMigrationId ID = new MongoMigration</tspan><tspan x="24" dy="22">Id("20260829-001");
|
||||
private static final Instant NOW = Instant.parse("2026-09-02T00:00:00Z");
|
||||
private static final l</tspan><tspan x="24" dy="22">ong LIVE = 5L;
|
||||
private static final long STALE = 1L;
|
||||
|
||||
public static void main(String[] args) {
|
||||
MongoDatabase ind</tspan><tspan x="24" dy="22">exed = MongoClients.create(args[0]).getDatabase("indexed");
|
||||
indexed.drop();
|
||||
MongoCollectionMigrationLedger ledge</tspan><tspan x="24" dy="22">r = new MongoCollectionMigrationLedger(indexed);
|
||||
ledger.ensureIndexes();
|
||||
|
||||
System.out.println("[준비] 살아 있는 </tspan><tspan x="24" dy="22">실행자가 펜스 " + LIVE + " 로 체크포인트를 쓴다");
|
||||
ledger.saveCheckpoint(new MongoMigrationCheckpoint(ID, "o-900", 9</tspan><tspan x="24" dy="22">00L, NOW), LIVE);
|
||||
System.out.println(" 체크포인트 : " + one(indexed, "mongoMigrationCheckpoint"));
|
||||
System.out.p</tspan><tspan x="24" dy="22">rintln();
|
||||
|
||||
System.out.println("[밀려난 실행자, 펜스 " + STALE + "] 같은 계약을 두 메서드에 건다");
|
||||
attempt("save</tspan><tspan x="24" dy="22">Checkpoint", () -> ledger.saveCheckpoint(
|
||||
new MongoMigrationCheckpoint(ID, "o-1", 1L, NOW), STALE));
|
||||
attempt</tspan><tspan x="24" dy="22">("recordApplied ", () -> ledger.recordApplied(
|
||||
ID, new MongoMigrationChecksum("superseded"), "stale-runner", NOW</tspan><tspan x="24" dy="22">, STALE));
|
||||
System.out.println(" 원장 항목 : " + one(indexed, "mongoMigrationLedger"));
|
||||
System.out.println();
|
||||
</tspan><tspan x="24" dy="22">
|
||||
System.out.println("[살아 있는 실행자, 펜스 " + LIVE + "] 밀려난 쪽이 쓴 뒤에 기록한다");
|
||||
attempt("recordApplie</tspan><tspan x="24" dy="22">d ", () -> ledger.recordApplied(
|
||||
ID, new MongoMigrationChecksum("live"), "live-runner", NOW, LIVE));
|
||||
System.</tspan><tspan x="24" dy="22">out.println(" 원장 항목 : " + one(indexed, "mongoMigrationLedger"));
|
||||
System.out.println();
|
||||
|
||||
System.out.printl</tspan><tspan x="24" dy="22">n("[대조 1] 펜스가 미지정 값이면 두 메서드 다 거절한다");
|
||||
attempt("saveCheckpoint", () -> ledger.saveCheckpoint(
|
||||
</tspan><tspan x="24" dy="22"> new MongoMigrationCheckpoint(ID, "o-0", 0L, NOW), -1L));
|
||||
attempt("recordApplied ", () -> ledger.recordApplied(
|
||||
</tspan><tspan x="24" dy="22"> ID, new MongoMigrationChecksum("unfenced"), "runner", NOW, -1L));
|
||||
System.out.println();
|
||||
|
||||
MongoDatabase b</tspan><tspan x="24" dy="22">are = MongoClients.create(args[0]).getDatabase("bare");
|
||||
bare.drop();
|
||||
MongoCollectionMigrationLedger unindexed = </tspan><tspan x="24" dy="22">new MongoCollectionMigrationLedger(bare);
|
||||
System.out.println("[대조 2] ensureIndexes 를 부르지 않은 원장에서 같은 경</tspan><tspan x="24" dy="22">합");
|
||||
attempt("recordApplied ", () -> unindexed.recordApplied(
|
||||
ID, new MongoMigrationChecksum("superseded"),</tspan><tspan x="24" dy="22"> "stale-runner", NOW, STALE));
|
||||
attempt("recordApplied ", () -> unindexed.recordApplied(
|
||||
ID, new MongoMigrati</tspan><tspan x="24" dy="22">onChecksum("live"), "live-runner", NOW, LIVE));
|
||||
System.out.println(" 원장 항목 수 : " + bare.getCollection("mongoMi</tspan><tspan x="24" dy="22">grationLedger").countDocuments());
|
||||
for (Document row : bare.getCollection("mongoMigrationLedger").find()) {
|
||||
ro</tspan><tspan x="24" dy="22">w.remove("_id");
|
||||
System.out.println(" " + row.toJson());
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static String</tspan><tspan x="24" dy="22"> one(MongoDatabase database, String collection) {
|
||||
Document found = database.getCollection(collection).find().first()</tspan><tspan x="24" dy="22">;
|
||||
if (found == null) {
|
||||
return "(없음)";
|
||||
}
|
||||
found.remove("_id");
|
||||
return found.toJson();
|
||||
}
|
||||
|
||||
priva</tspan><tspan x="24" dy="22">te static void attempt(String what, Runnable call) {
|
||||
try {
|
||||
call.run();
|
||||
System.out.println(" " + what + </tspan><tspan x="24" dy="22">" -> 수용");
|
||||
} catch (RuntimeException rejected) {
|
||||
System.out.println(" " + what + " -> 거절, " + describe(re</tspan><tspan x="24" dy="22">jected));
|
||||
}
|
||||
}
|
||||
|
||||
private static String describe(RuntimeException failure) {
|
||||
String name = failure.getClass().g</tspan><tspan x="24" dy="22">etSimpleName();
|
||||
if (failure instanceof MongoWriteException write) {
|
||||
Matcher index = Pattern.compile("index: (\</tspan><tspan x="24" dy="22">\S+)").matcher(write.getError().getMessage());
|
||||
return name + " code=" + write.getError().getCode()
|
||||
+ (in</tspan><tspan x="24" dy="22">dex.find() ? " index=" + index.group(1) : "");
|
||||
}
|
||||
String line = failure.getMessage() == null ? "" : failure.getMe</tspan><tspan x="24" dy="22">ssage().split("\n")[0].split(" \\[")[0];
|
||||
return name + ": " + (line.length() > 110 ? line.substring(0, 110) + " …" </tspan><tspan x="24" dy="22">: line);
|
||||
}
|
||||
}
|
||||
JAVA
|
||||
|
||||
CACHE=/root/.gradle/caches/modules-2/files-2.1
|
||||
LOCK=src/adapter/outbound/persistence-mongo/gradle.l</tspan><tspan x="24" dy="22">ockfile
|
||||
CP=src/adapter/outbound/persistence-mongo/build/classes/java/main
|
||||
while IFS= read -r line; do
|
||||
coord=${line%%=*</tspan><tspan x="24" dy="22">}; confs=${line#*=}
|
||||
case ",$confs," in *",testRuntimeClasspath,"*) ;; *) continue ;; esac
|
||||
g=${coord%%:*}; r=${coord#</tspan><tspan x="24" dy="22">*:}; n=${r%%:*}; v=${r##*:}
|
||||
jar=$(find "$CACHE/$g/$n/$v" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/</tspan><tspan x="24" dy="22">null | head -1)
|
||||
[ -n "$jar" ] && CP="$CP:$jar"
|
||||
done < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' "$LOCK")
|
||||
|
||||
javac -encoding UT</tspan><tspan x="24" dy="22">F-8 -cp "$CP" -d "$D" "$D/LedgerFenceProbe.java"
|
||||
java -Dstdout.encoding=UTF-8 -cp "$D:$CP" LedgerFenceProbe \
|
||||
'mongodb</tspan><tspan x="24" dy="22">://127.0.0.1:57020/probe?replicaSet=rs0' 2>&1 \
|
||||
| grep -vE '^[0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} ' </tspan></text>
|
||||
<text x="24" y="1190" 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="1212" 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-02T07:39:34+00:00 · exit 0</tspan></text>
|
||||
<line x1="24" y1="1226" x2="1176" y2="1226" stroke="#30363d"/>
|
||||
<text x="24" y="1266" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[준비] 살아 있는 실행자가 펜스 5 로 체크포인트를 쓴다</tspan></text>
|
||||
<text x="24" y="1288" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 체크포인트 : {"migrationId": "20260829-001", "resumePosition": "o-900", "processedCount": 900, "updatedAt": {"$date": "2026-09</tspan><tspan x="24" dy="22">-02T00:00:00Z"}, "fence": 5}</tspan></text>
|
||||
<text x="24" y="1332" 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="1354" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[밀려난 실행자, 펜스 1] 같은 계약을 두 메서드에 건다</tspan></text>
|
||||
<text x="24" y="1376" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> saveCheckpoint -> 거절, MongoOperationRejectedException: a newer migration runner owns the lease; this runner's checkpoint wri</tspan><tspan x="24" dy="22">te was refused</tspan></text>
|
||||
<text x="24" y="1420" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> recordApplied -> 수용</tspan></text>
|
||||
<text x="24" y="1442" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 원장 항목 : {"migrationId": "20260829-001", "checksum": "superseded", "operator": "stale-runner", "appliedAt": {"$date": "20</tspan><tspan x="24" dy="22">26-09-02T00:00:00Z"}, "fence": 1}</tspan></text>
|
||||
<text x="24" y="1486" 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="1508" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[살아 있는 실행자, 펜스 5] 밀려난 쪽이 쓴 뒤에 기록한다</tspan></text>
|
||||
<text x="24" y="1530" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> recordApplied -> 거절, MongoWriteException code=11000 index=migrationId_1</tspan></text>
|
||||
<text x="24" y="1552" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 원장 항목 : {"migrationId": "20260829-001", "checksum": "superseded", "operator": "stale-runner", "appliedAt": {"$date": "20</tspan><tspan x="24" dy="22">26-09-02T00:00:00Z"}, "fence": 1}</tspan></text>
|
||||
<text x="24" y="1596" 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="1618" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[대조 1] 펜스가 미지정 값이면 두 메서드 다 거절한다</tspan></text>
|
||||
<text x="24" y="1640" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> saveCheckpoint -> 거절, MongoOperationRejectedException: refusing to write the checkpoint for 20260829-001 under an unfenced l</tspan><tspan x="24" dy="22">ease; a lease with no acquisition token c …</tspan></text>
|
||||
<text x="24" y="1684" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> recordApplied -> 거절, MongoOperationRejectedException: refusing to write the ledger entry for 20260829-001 under an unfenced</tspan><tspan x="24" dy="22"> lease; a lease with no acquisition token …</tspan></text>
|
||||
<text x="24" y="1728" 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="1750" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[대조 2] ensureIndexes 를 부르지 않은 원장에서 같은 경합</tspan></text>
|
||||
<text x="24" y="1772" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> recordApplied -> 수용</tspan></text>
|
||||
<text x="24" y="1794" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> recordApplied -> 수용</tspan></text>
|
||||
<text x="24" y="1816" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 원장 항목 수 : 2</tspan></text>
|
||||
<text x="24" y="1838" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> {"migrationId": "20260829-001", "checksum": "superseded", "operator": "stale-runner", "appliedAt": {"$date": "2026-09-02T00:</tspan><tspan x="24" dy="22">00:00Z"}, "fence": 1}</tspan></text>
|
||||
<text x="24" y="1882" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> {"migrationId": "20260829-001", "checksum": "live", "operator": "live-runner", "appliedAt": {"$date": "2026-09-02T00:00:00Z"</tspan><tspan x="24" dy="22">}, "fence": 5}</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
Reference in New Issue
Block a user