{ "version": "1.1", "id": "b5-b6-storage-and-keys", "title": "볼륨 없는 영속화와 replica별 키 회전", "question": "설정을 켰다는 사실만으로 영속화와 일관된 키 회전을 기대할 수 있는가", "type": "architecture", "direction": "LR", "audience": [ "저장소와 키 회전을 운영하는 엔지니어" ], "summary": "Redis AOF 는 볼륨이 없으면 컨테이너와 함께 사라진다. 키 회전 뒤에는 replica별 JWKS cache 상태가 달라 같은 옛 토큰이 401과 200을 번갈아 받았다.", "alt": "운영 변경에서 Redis AOF는 볼륨 없는 데이터 디렉터리로 이어져 소멸하고, realm 키 회전은 replica별 JWKS cache 차이로 401과 200이 갈리는 두 경로.", "long_description": "B-5에서는 appendonly yes 설정이 적용돼도 /data가 컨테이너 파일시스템이라 재시작 때 사라졌다. B-6에서는 옛 키를 지운 뒤 같은 토큰을 반복 요청하자 401과 200이 교대로 나왔다. echo replica 둘의 JWKS cache가 독립적이라 한쪽은 새 목록을 받아 옛 키를 잃었고 다른 쪽은 아직 들고 있었기 때문이다.", "source_context": { "document": "docs/keycloak-session-store/final/document.md", "document_sha256": "28aef96a2bbb94fbb10ade26a71238fee62a5a4d9fa6e7749ae98cfd0a65e560", "anchor": { "kind": "heading", "value": "B-5 · B-6 — 저장소 상실과 키 회전", "line": 859 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "설정이 실제 저장 매체에 닿는가가 지배적 질문이다. 경로의 끝이 어디인가이므로 component-flow 를 골랐다." }, "groups": [], "nodes": [ { "id": "change", "label": "운영 변경", "kind": "process", "role": "source", "emphasis": "normal", "description": "저장 설정과 realm 키 회전을 각각 수행한다.", "details": [ "B-5 · B-6" ], "evidence": [ { "start_line": 861, "end_line": 872 } ], "assumption": false }, { "id": "datadir", "label": "Redis /data", "kind": "datastore", "role": "control", "emphasis": "warning", "description": "AOF가 기록되는 컨테이너 파일시스템이다.", "details": [ "appendonly yes · 볼륨 없음" ], "evidence": [ { "start_line": 861, "end_line": 863 } ], "assumption": false }, { "id": "lost", "label": "Redis 재시작", "kind": "component", "role": "target", "emphasis": "warning", "description": "볼륨이 없어 데이터가 함께 사라진다.", "details": [ "영속화 실패" ], "evidence": [ { "start_line": 861, "end_line": 863 } ], "assumption": false }, { "id": "replicas", "label": "echo replica 2개", "kind": "service", "role": "control", "emphasis": "warning", "description": "JWKS cache를 인스턴스마다 따로 가진다.", "details": [ "replica별 cache" ], "evidence": [ { "start_line": 868, "end_line": 872 } ], "assumption": false }, { "id": "mixed", "label": "교차 응답", "kind": "component", "role": "target", "emphasis": "primary", "description": "같은 옛 토큰의 검증 결과가 replica에 따라 갈린다.", "details": [ "401 · 200 · 반복" ], "evidence": [ { "start_line": 868, "end_line": 877 } ], "assumption": false } ], "edges": [ { "id": "c-d", "from": "change", "to": "datadir", "label": "AOF 설정", "kind": "write", "evidence": [ { "start_line": 861, "end_line": 863 } ], "assumption": false }, { "id": "d-l", "from": "datadir", "to": "lost", "label": "볼륨 없음", "kind": "blocked", "evidence": [ { "start_line": 861, "end_line": 863 } ], "assumption": false }, { "id": "c-r", "from": "change", "to": "replicas", "label": "키 회전", "kind": "request", "evidence": [ { "start_line": 865, "end_line": 872 } ], "assumption": false }, { "id": "r-m", "from": "replicas", "to": "mixed", "label": "cache 상태", "kind": "request", "evidence": [ { "start_line": 868, "end_line": 877 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "B-5와 B-6를 같은 실패로 합치지 않고, 설정이 실제 매체와 인스턴스별 cache에 닿을 때 각각 어떤 결과가 나는지 두 갈래로 연결했다." } }