# 실험 색인 — 23개 전부 각 실험은 **자기 브랜치**에 있고, 해설 문서와 증거 폴더가 한 벌이다. | # | 실험 | 브랜치 | 한 줄 결과 | |---|---|---|---| | **A-0** | 세션 복제 확인 | `...multinode-cluster-jdbc-ping` | 세션 공유는 Infinispan 이 아니라 **PostgreSQL** 이 한다 | | **A-1** | TCP 7800 차단 | `...a1-jgroups-transport-block` | 세션은 견디지만 **로그아웃 무효화가 7800 을 탄다** | | **A-2** | DB 정상 정지 | `...a2-database-loss` | 전면 장애. **그런데 `up` 은 1이었다** | | **A-3** | DB 강제 종료 | `...a3-database-crash` | **153건 중 4건의 로그인이 사라졌다** | | **A-4** | 노드 전원 차단 | `...a4-node-loss` | **죽은 파드가 산 파드보다 건강해 보인다** | | **A-5** | 비대칭 파티션 | `...a5-asymmetric-partition` | 단방향은 자가 치유. 완전 분단도 **한쪽은 산다** | | **A-6** | 지연 주입 | `...a6-latency-injection` | **200ms → 22초** (왕복 × 풀 큐잉) | | **A-7** | volatile 비교 | `...a7-volatile-comparison` | **세 결과가 정반대로 뒤집힌다** | | **A-8** | 롤링 재시작 | `...a8-rolling-restart` | 무중단 + 세션 생존 | | **B-0** | 자동구성 확인 | `...b0-bff-redis-deploy` | 조회 키에 **session id 가 없다** | | **B-1** | Redis 세션 | `...b1-redis-session-store` | 세션만 옮겨지고 **토큰은 남는다** | | **B-2** | 다중 인스턴스 | `...b2-multi-instance-session` | 공유는 되지만 **평문·덮어쓰기·로그아웃 미정리** | | **B-3** | refresh 경쟁 | `...b3-refresh-token-contention` | 경쟁이 아니라 **세션이 파괴된다** | | **B-4** | Edge 인가 | `...b4-edge-authorization-scope` | nginx 는 **설정하지 않은 헤더를 덮어쓰지 않는다** | | **B-5** | Redis 상실 | `...b5-redis-loss-persistence` | **파드가 Ready 인 채로 계속 실패한다** | | **B-6** | key 회전 | `...b6-key-rotation` | 회전은 안전, **옛 키를 버리는 순간이 위험** | | **B-7** | cookie secret | `...b7-cookie-secret-rotation` | **겹침 구간이 없고 세션이 고아로 남는다** | | **C-1** | 다중 앱 SSO | `...c1-multi-app-sso` | **IdP 세션을 죽여도 아무도 로그아웃되지 않는다** | | **C-2** | 백채널 로그아웃 | `...c2-backchannel-logout` | **받는 쪽을 아무도 구현하지 않았다** | | **D-1** | 백업·복구 | `...d1-backup-restore` | **빈 데이터베이스가 `200` 을 냈다** | | **D-2** | 버전 업그레이드 | `...d2-version-upgrade` | **이미지를 되돌려도 스키마는 안 돌아온다** | | **D-3** | 비밀 관리 | `...d3-secret-management` | **RBAC 만 실제로 감춘다** | | **D-4** | 인증서 갱신 | `...d4-certificate-renewal` | **갱신은 됐는데 38분 25초 반영 안 됨** (훅 3경로 전부 비었음). reload 자체는 **무중단**(8856건 0실패) | | **A-7a** | volatile refresh 500 원인 확정 | `...a7a-volatile-cause` | **가설(`REVOKED_TOKEN`)은 틀렸다** — `CLIENT_SCOPE_CLIENT` 조회다. **A-7 의 표는 캐시 온도에 따라 400/500/200** | | **B-7a** | 고아 세션 정리 | `...b7a-orphan-session` | **지울 수 있다** — 프록시가 못 할 뿐. TTL 역산으로 고아만 **1초 오차**로 골라낸다 | | **D-4a** | deploy 훅 검증 | `...d4a-deploy-hook` | **처방이 듣는다** — 발급→서빙 **1~2초**(D-4 는 38분 25초). test-server 시계가 **106초 빨라** D-4 수치를 정정 | | **후속** | 미측정 3항목 채우기 | `...followup-untested-items` | **셋 다 완료.** 정방향 업그레이드 무중단 · **롤백 불가는 조건부였다** · role 변경은 요청으로 반영 안 됨 · **D-4 갱신 38분 25초 미반영** | ## 시각 자료 | | | |---|---| | [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg) | 실험대 전체 구조 | | [`diagrams/_style.md`](diagrams/_style.md) | 다이어그램 규약 (붉은 점선 = 죽인 것) | | `diagrams/*.svg` | 실험별 구조도 12개 | | `evidence/*/*.png` | Grafana · 브라우저 스크린샷 | **스크린샷이 없는 실험은 14개다** — `A-3 A-7 A-7a B-3 B-4 B-5 B-6 C-2 D-1 D-3 D-4 D-4a 후속 멀티노드`. 없는 이유는 **관측 대상에 그 지표가 없기 때문**이지 안 찍어서가 아니다 — Prometheus 는 keycloak·kubelet·node-exporter·prometheus 만 긁는다 ([`followup/04-observability-gap.txt`](evidence/followup/04-observability-gap.txt)). 터미널·DB·로그로 측정한 실험은 브라우저에 띄울 화면 자체가 없다. **구조도(SVG)는 28개이고, 실험 문서 29개 중 28개가 하나 이상을 싣는다.** 싣지 않는 하나는 후속 요약 문서이며, 세 실험의 구조도를 표로 참조한다. 그중 B층은 **Prometheus 가 Redis·BFF·PostgreSQL 을 긁지 않아** 만들 수가 없다 — [`followup/04-observability-gap.txt`](evidence/followup/04-observability-gap.txt) 에 측정해 두었다. ## 문서 지도 | 문서 | 용도 | |---|---| | [`session-lab-prerequisites.md`](session-lab-prerequisites.md) | **먼저 읽을 것** — 왜 이런 걸 재는지 | | [`experiment-plan.md`](experiment-plan.md) | 23개의 구조도·주입 방법·예측 | | [`open-questions-coverage.md`](open-questions-coverage.md) | 공개 열린 질문 4개 대조 | | [`session-lab-concepts.md`](session-lab-concepts.md) | 등장 개념 전체 (13층) | ## 문서가 자기 증거와 어긋났던 곳 **서브에이전트 감사(2026-09-04)에서 찾아 정정한 것들이다.** | 문서 | 무엇이 틀렸나 | |---|---| | C-1 | 세션 수를 `0` 으로 인쇄. 증거는 `4` | | C-2 | `exit code 1` 로 실패한 명령의 성공 출력을 인쇄 | | A-1 | conntrack 삭제에 분단을 귀속. 실제로는 **파드 재시작 4초 뒤** | | A-2 | ④ 의 첫 측정이 오염됐는데 정제된 값만 인쇄 | | A-3 | `wal_writer_delay` 를 재지 않고 "기본값과 맞는다" | | A-6 | 대조군이 −41% 변했는데 "영향 없음" | | A-8 | 표본 9개로 "무중단" | | B-2 | "두 브라우저" — 실제로는 세션만 지웠다. `Liquibase` 오귀속 | | D-1 | RTO 30초 — 실제 41초 | | D-2 | "롤백 불가" 단정 — 조건부였다 | **png 6장이 실은 3장**(동일 내용이라 바이트가 같다)이고, `a5/02-injection-verify.txt` 는 0바이트였다. 각 증거 README 에 명시했다. ## 반복해서 배운 것 — 주입이 안 걸린 사례 **아홉 번 있었다. 전부 "아무 일도 없었다" 로 보였다.** | 실험 | 안 걸린 주입 | 원인 | |---|---|---| | A-1 | NetworkPolicy 로 7800 차단 | conntrack `ESTABLISHED` 가 먼저 통과시킨다 | | A-3 | `delete --grace-period=0 --force` | 컨테이너 런타임이 SIGTERM → 정상 종료 | | A-3 | `kill -9 1` | 컨테이너 안에서 **PID 1 은 SIGKILL 을 무시**한다 | | A-5 | `iptables -I FORWARD 1` | kube-router 가 자기 체인을 위로 재삽입 | | A-5 | raw 테이블, 반대 노드 | **연결 방향이 뒤집혀 있었다** | | A-6 | `tc ... dev eth0` | 인터페이스가 `enp1s0` 이다 | | A-6 | `enp1s0` 에 파드 IP 필터 | VXLAN 캡슐화로 **안 보인다** | | B-2 | `spring.sql.init` 스키마 | PostgreSQL 에 없는 `blob` 타입 + `continue-on-error` | | B-7·D-4 | `sudo nginx -T` 등 | **호스트 sudo 가 비밀번호를 요구**한다. D-4 는 사용자가 직접 실행해 해소했다 | **그래서 실험마다 "주입 성공 신호" 를 먼저 정하게 됐다** — `cluster_size` 하락, `not properly shut down` 로그, iptables 패킷 카운터, `tc -s qdisc` 의 `Sent`. ## 예측이 빗나간 곳 | 실험 | 예측 | 실제 | |---|---|---| | A-1 | 로그아웃 전파는 안 깨진다 | **깨졌다** — 무효화는 7800 을 탄다 | | A-2 | `up` 이 잡아줄 것 | **1로 평평했다** | | A-6 | 낙관적 락 충돌이 는다 | **0건** — 로그인은 새 행을 만든다 | | B-4 | nginx 가 동명 헤더를 덮어쓴다 | **둘 다 도착했다** | | B-6 | JWKS 캐시가 유예를 준다 | **즉시 401** | ## 말할 수 있는 것과 없는 것 **말할 수 있다** — Keycloak 멀티노드에서 세션과 토큰이 어디에 저장되고 각 저장소가 죽으면 무엇이 어떻게 실패하는지 **재현하고 복구했다.** 버전에 따라 결론이 뒤집힌다는 것을 **같은 주입으로 양쪽 다 측정했다.** **말하면 안 된다** — "운영해봤다", "대규모 트래픽을 다뤄봤다". 규모·시간·다른 사람·실제 사용자·비용은 이 실험대에 없다.