Files
keycloak-pattern/docs/experiment-a7-volatile-comparison.md
DongHyeonkaandClaude Opus 5 8f6d67df35 docs: A-7 — three results invert when persistent sessions are turned off
Disabling persistent-user-sessions moves the session from PostgreSQL into the cluster, and the A-1 and A-8 outcomes flip to 400 Session not active while a new login during database loss starts working. The control group in each case still returns 200, so the injections cut only what they were meant to cut.

This is the pair that makes the A layer legible: the conventional wisdom that sessions ride TCP 7800 is correct for Keycloak 24 and earlier, and the mistake is applying it to 26 without checking the version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 13:33:33 +09:00

9.4 KiB
Raw Permalink Blame History

A-7 — 옛 방식(volatile)이었다면 무엇이 달라지는가

브랜치 feature/keycloak-a7-volatile-comparison · 증거 docs/evidence/a7-volatile-comparison/ · 2026-09-04 13:4514:15 KST

A층의 결론 전체가 "Keycloak 26 기본값"이라는 전제 위에 있다. 전제를 뒤집어 같은 실험을 반복한 것이 이 실험이다.


0. 결론부터 — 비교표

실험 persistent (KC 26 기본) volatile (KC 24 이전 방식)
A-0 세션 저장 위치 DB 에 행이 생긴다 DB 0건. 메모리에만
A-1 7800 차단 후 교차 노드 refresh 200 — 안 깨진다 400 Session not active — 깨진다
A-8 롤링 재시작 후 refresh 200 — 세션 생존 400 Session not active — 전원 로그아웃
A-2 DB 정지 중 새 로그인 500 200 — 된다
A-2 DB 정지 중 refresh 500 500

세 개가 정반대로 뒤집혔다. 예측한 그대로다.

"세션 공유는 7800 을 안 탄다"는 A-1 의 결론은 버전에 달린 사실이다. 인터넷 자료 대부분이 24 이전 기준이므로 거기서는 통념이 맞다. 틀린 것은 자료가 아니라 버전을 확인하지 않고 적용하는 것이다.


1. 전환 방법

# 이 버전에서 끌 수 있는지부터 확인한다
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kc.sh build --help-all \
  | tr ',' '\n' | grep -i persistent
#   persistent-user-sessions[:v1]      ← 목록에 있다
# deploy/lab/k8s/keycloak-cluster.yaml
args: ["start", "--features-disabled=persistent-user-sessions"]
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s

빌드 옵션이므로 기동 시 재빌드가 일어나 평소보다 오래 걸린다.


2. A-0 재실행 — 세션이 DB 에 안 들어간다

=== keycloak-0 에만 로그인 5회 ===
  keycloak-0  sessions 캐시 5.0 건
  keycloak-1  sessions 캐시 0.0 건

=== DB 에는 들어갔는가 (persistent 였을 때는 5건) ===
 offline_flag | count
--------------+-------
(0 rows)                    ← 0 건

=== 교차 노드 세션은 되는가 ===
  keycloak-0 로그인 → keycloak-1 에서 refresh  HTTP 200

DB 는 비어 있는데 교차 노드가 된다. persistent 때와 겉보기 결과가 같지만 경로가 완전히 다르다 — 이제는 DB 가 아니라 클러스터를 타고 있다.

캐시 엔트리 수가 5 / 0 인 것은 persistent 때와 같다. approximate_entries_unique그 노드가 소유한 엔트리만 세므로, 백업본을 들고 있어도 0 으로 보인다. 이 지표만으로는 두 모드를 구분할 수 없다 — 구분하려면 7800 을 끊어봐야 한다. 그게 다음 절이다.


3. A-1 재실행 — 정반대다

A-5 에서 확립한 raw 테이블 양방향 차단을 그대로 썼다.

  [대조군] 차단 전 교차 노드 refresh   HTTP 200

  차단 적용 → 분단 성립
    +25초  cluster_size(k0 k1) = [2.0 2.0]
    +50초  cluster_size(k0 k1) = [1.0 ...]     ← 갈라졌다

=== 분단 상태에서 ===
  keycloak-0 로그인 → keycloak-0 에서 refresh  HTTP 200  ← 대조군
  keycloak-0 로그인 → keycloak-1 에서 refresh  HTTP 400  ← 시험군
  {"error":"invalid_grant","error_description":"Session not active"}

대조군이 200 인 것이 중요하다. 차단이 모든 것을 망가뜨린 게 아니라 교차 노드만 끊었다는 증거다.

   persistent :  세션 ── PostgreSQL ──▶ 양쪽이 본다      7800 무관
   volatile   :  세션 ── 클러스터(7800) ─▶ 상대에게 간다   7800 필수

같은 주입, 같은 관측, 정반대 결과. 이 한 쌍이 A층 전체의 근거다.


4. A-8 재실행 — 배포가 곧 로그아웃

=== 재시작 전 로그인 ===
  sid = aVwYnzKZFFvMqD3bpSeiILuM

=== 롤링 재시작 ===
partitioned roll out complete: 2 new pods have been updated...

=== ★ 재시작 전 토큰이 아직 통하는가 (persistent 였을 때는 200) ===
  keycloak-0 에서 refresh  HTTP 400
  {"error":"invalid_grant","error_description":"Session not active"}

배포할 때마다 전원 로그아웃된다.

persistent volatile
배포 자유롭다 모든 사용자가 다시 로그인
파드 재시작 (OOM, 노드 교체) 무해 그 노드가 처리하던 세션 소멸
무중단 여부 무중단 (A-8) 접속은 되지만 로그인 상태가 사라진다

A-8 에서 "이것이 persistent 를 켜는 진짜 이유"라고 썼는데, 여기서 증명된다. 24 이전 버전을 쓰는 곳에서 "배포하면 로그아웃된다"가 당연하게 여겨졌던 이유다.


5. A-2 재실행 — 예상 못 한 비대칭

  ① 캐시를 가진 노드에서 refresh   HTTP 500
  ② 새 로그인                      HTTP 200      ← persistent 에서는 500 이었다

새 로그인은 되는데 refresh 가 안 된다. 순서가 거꾸로다.

왜 새 로그인이 되는가

   로그인에 필요한 것
     ├─ realm 설정   → Infinispan `realms` 캐시에 있다
     ├─ 사용자 자격  → `users` 캐시에 있다
     └─ 세션 저장    → volatile 이므로 메모리
   → DB 없이 완결된다

A-2 에서 persistent 로 했을 때 로그인이 실패한 이유는 "세션을 DB 에 써야 해서"였다. 그 쓰기가 없어지니 로그인이 통과한다.

refresh 가 500 인 이유 — 가설

측정은 확실하지만 원인은 확정하지 못했다. 유력한 후보는 REVOKED_TOKEN 테이블이다 — refresh token 회전에서 이미 쓴 토큰인지 확인하려면 그 테이블을 봐야 하고, 그 경로는 캐시되지 않는다.

kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c "\dt" | grep revoked
 public | revoked_token | table | keycloak

확정하려면 A-3 에서 쓴 문장 로깅을 켜고 다시 재현해야 한다. 여기서는 가설로 남긴다.

volatile 이 "DB 없이 돌아간다"는 뜻은 아니다. realm·사용자·클라이언트·취소 토큰은 여전히 DB 에 있다. 세션만 메모리로 옮긴 것이다.


6. 원복

# args 를 되돌린다
args: ["start"]
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml
=== [검증] persistent 로 돌아왔는가 ===
["start"]
  DB 온라인 세션: 1 건  (로그인 1회 후 → persistent 복귀 확인)
  외부 진입점 HTTP 200

전환 자체는 설정 한 줄이고 되돌리기도 한 줄이다. 다만 전환 시점에 기존 세션은 전부 사라진다 (저장 위치가 바뀌므로).


7. 개념

두 모드의 데이터 흐름

   persistent (KC 25+, 26 기본)
      로그인 ──▶ PostgreSQL (진실)  +  로컬 캐시 (사본)
      조회   ──▶ 캐시에 없으면 DB
      공유   ──▶ 같은 DB 를 보는 것

   volatile (KC 24 이전)
      로그인 ──▶ Infinispan (진실)
      조회   ──▶ 클러스터에서 찾는다
      공유   ──▶ 7800 을 통한 복제

무엇을 맞바꾸는가

persistent volatile
재시작 내구성 있다 없다
7800 의존 낮다 (무효화만) 높다 (세션 자체)
DB 부하 로그인·refresh 마다 쓰기 세션 관련 없음
노드 확장 DB 가 병목 복제 트래픽이 N² 로 증가
지연 민감도 DB 왕복에 민감 (A-6) 클러스터 왕복에 민감

26 이 기본을 바꾼 이유가 이 표에 있다 — 운영에서 가장 아픈 것이 "배포하면 로그아웃"이었기 때문이다.


8. 재현 절차 (명령어)

# 1. 끌 수 있는지 확인
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kc.sh build --help-all \
  | tr ',' '\n' | grep -i persistent

# 2. 세션을 비우고 전환 (비교 기준을 맞추기 위해)
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
  -c "delete from offline_user_session"
#   args: ["start", "--features-disabled=persistent-user-sessions"]
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s

# 3. volatile 확인 — 로그인 후 DB 가 비어 있어야 한다
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
  -c "select offline_flag, count(*) from offline_user_session group by offline_flag"

# 4. A-1 / A-8 을 그대로 반복한다 (증거 파일 참조)

# 5. 원복
#   args: ["start"]
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml

9. 이 실험이 A층에 남기는 것

A-0~A-6 의 결론은 모두 "26 기본값" 조건부다 버전이 다르면 답이 다르다
A-1 이 통념과 어긋난 이유가 확정됐다 통념은 24 이전에서 맞다
버전 확인이 1순위 kc.sh --version 을 먼저 본다
volatile 이 DB 독립을 뜻하지 않는다 realm·사용자·취소 토큰은 여전히 DB

미해결로 남긴 것

volatile 에서 refresh 만 500 이 되는 이유REVOKED_TOKEN 조회 가설을 세웠지만 확정하지 못했다. A-3 의 문장 로깅 기법으로 재현하면 답이 나온다.