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>
165 lines
5.1 KiB
JSON
165 lines
5.1 KiB
JSON
{
|
|
"version": "1.1",
|
|
"id": "session-sharing-path",
|
|
"title": "세션 공유가 실제로 지나는 경로",
|
|
"question": "두 Keycloak 노드가 같은 세션을 아는 것은 무엇 때문인가",
|
|
"type": "architecture",
|
|
"direction": "LR",
|
|
"audience": [
|
|
"Keycloak 을 다중 노드로 운영하는 백엔드 엔지니어"
|
|
],
|
|
"summary": "클러스터 뷰는 형성되지만 세션 엔트리는 노드 사이를 건너가지 않는다. 두 노드가 같은 답을 내놓는 경로는 PostgreSQL 이다.",
|
|
"alt": "keycloak-0 과 keycloak-1 이 각자 캐시를 갖고 PostgreSQL 을 함께 읽는 구성. 두 캐시 사이에는 세션 복제 경로가 없다.",
|
|
"long_description": "keycloak-0 과 keycloak-1 은 JGROUPS_PING 테이블을 통해 서로를 발견하고 클러스터 뷰를 형성한다. 그러나 sessions 캐시의 엔트리는 노드 사이로 복제되지 않으며, 각 노드는 자기가 처리한 로그인만 캐시한다. 노드 A 로 로그인한 세션을 노드 B 가 아는 이유는 노드 B 가 PostgreSQL 의 OFFLINE_USER_SESSION 을 직접 읽기 때문이고, 이는 반대편 노드가 날린 SQL 을 문장 로깅으로 잡아 확인했다.",
|
|
"source_context": {
|
|
"document": "docs/keycloak-session-store/final/document.md",
|
|
"document_sha256": "609353e10bfd37a9bbb6a79ecf2a32f3d3c02d5d161879a14ad4713e49e7e5e8",
|
|
"anchor": {
|
|
"kind": "heading",
|
|
"value": "그런데 첫 실험에서 전제가 무너졌다",
|
|
"line": 33
|
|
}
|
|
},
|
|
"composition": {
|
|
"profile": "component-flow",
|
|
"diagram_only": true,
|
|
"reference_ids": [
|
|
"payment-event-flow"
|
|
],
|
|
"rationale": "세 구성 요소 사이에서 어느 경로가 실제로 존재하고 어느 경로가 존재하지 않는지가 이 절의 지배적 질문이다. 시간 순서가 아니라 경로의 유무가 핵심이므로 component-flow 를 골랐다."
|
|
},
|
|
"groups": [],
|
|
"nodes": [
|
|
{
|
|
"id": "keycloak-0",
|
|
"label": "keycloak-0",
|
|
"kind": "service",
|
|
"role": "source",
|
|
"emphasis": "primary",
|
|
"description": "로그인을 처리하고 자기 sessions 캐시에만 엔트리를 남긴다.",
|
|
"details": [
|
|
"자기가 처리한 로그인만 캐시"
|
|
],
|
|
"evidence": [
|
|
{
|
|
"start_line": 49,
|
|
"end_line": 51
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "postgres",
|
|
"label": "PostgreSQL",
|
|
"kind": "datastore",
|
|
"role": "store",
|
|
"emphasis": "primary",
|
|
"description": "OFFLINE_USER_SESSION 에 세션 행을 보관한다. 두 노드가 같은 행을 본다.",
|
|
"details": [
|
|
"offline_flag='0' 이 온라인 세션"
|
|
],
|
|
"evidence": [
|
|
{
|
|
"start_line": 48,
|
|
"end_line": 52
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "keycloak-1",
|
|
"label": "keycloak-1",
|
|
"kind": "service",
|
|
"role": "target",
|
|
"emphasis": "primary",
|
|
"description": "다른 노드가 만든 세션을 캐시로 받지 않고 데이터베이스에서 읽는다.",
|
|
"details": [
|
|
"refresh 요청을 받으면 DB 를 조회"
|
|
],
|
|
"evidence": [
|
|
{
|
|
"start_line": 48,
|
|
"end_line": 51
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "jgroups-ping",
|
|
"label": "JGROUPS_PING",
|
|
"kind": "datastore",
|
|
"role": "support",
|
|
"emphasis": "muted",
|
|
"description": "노드가 서로를 발견하는 자리. 여기 등록되어 있다는 것과 세션이 복제된다는 것은 다른 사건이다.",
|
|
"evidence": [
|
|
{
|
|
"start_line": 44,
|
|
"end_line": 46
|
|
}
|
|
],
|
|
"assumption": false
|
|
}
|
|
],
|
|
"edges": [
|
|
{
|
|
"id": "k0-writes",
|
|
"from": "keycloak-0",
|
|
"to": "postgres",
|
|
"label": "세션 INSERT",
|
|
"kind": "write",
|
|
"evidence": [
|
|
{
|
|
"start_line": 49,
|
|
"end_line": 52
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "k1-reads",
|
|
"from": "keycloak-1",
|
|
"to": "postgres",
|
|
"label": "세션 SELECT",
|
|
"kind": "read",
|
|
"evidence": [
|
|
{
|
|
"start_line": 48,
|
|
"end_line": 50
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "k0-discovery",
|
|
"from": "keycloak-0",
|
|
"to": "jgroups-ping",
|
|
"label": "멤버 등록",
|
|
"kind": "write",
|
|
"evidence": [
|
|
{
|
|
"start_line": 44,
|
|
"end_line": 45
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "k1-discovery",
|
|
"from": "keycloak-1",
|
|
"to": "jgroups-ping",
|
|
"label": "멤버 등록",
|
|
"kind": "write",
|
|
"evidence": [
|
|
{
|
|
"start_line": 44,
|
|
"end_line": 45
|
|
}
|
|
],
|
|
"assumption": false
|
|
}
|
|
],
|
|
"legend": [],
|
|
"metadata": {
|
|
"rationale": "클러스터 형성과 세션 복제를 한 그림에서 분리했다. 발견(JGROUPS_PING)과 공유(OFFLINE_USER_SESSION)가 같은 데이터베이스 안의 다른 테이블이라는 점이 이 절의 오해가 생기는 자리다."
|
|
}
|
|
} |