{ "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, "details": [] } ], "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)가 같은 데이터베이스 안의 다른 테이블이라는 점이 이 절의 오해가 생기는 자리다." } }