From cdac9b8178391311d8eca1ebc6cac15bb62d79af Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Fri, 4 Sep 2026 21:44:36 +0900 Subject: [PATCH] docs: give the twelve experiments that had no architecture diagram one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An audit against the standard the series set — concepts, procedure, commands, architecture diagram, evidence table, terminal output — found the three new experiments met it while twelve of the original ones had no diagram at all: A-0, A-1, A-3, A-4, A-5, A-6, A-8, B-0, B-2, B-7, C-2, D-2. Each now has one drawn from what that experiment actually found, not filler: A-0 shows sharing going through PostgreSQL rather than between the caches; A-3 the gap between the 200 and the WAL flush, with both failed injections; A-5 the three silent injection failures; A-6 the two places latency is multiplied; B-0 the repository keyed by principal with no session id; B-2 the primary key that causes the overwrite; D-2 why the rolling update stopped the accident halfway. Also corrected the index's stale claim of 11 experiments without a screenshot — it is 14, and the reason is recorded: those experiments were measured from terminals, the database and logs, and the observability stack does not scrape Redis, the BFF or PostgreSQL, so there is no console to photograph. Co-Authored-By: Claude Opus 5 --- docs/diagrams/a0-session-replication.svg | 48 ++++++++++++++++ docs/diagrams/a1-transport-block.svg | 47 ++++++++++++++++ docs/diagrams/a3-database-crash.svg | 55 +++++++++++++++++++ docs/diagrams/a4-node-loss.svg | 52 ++++++++++++++++++ docs/diagrams/a5-asymmetric-partition.svg | 49 +++++++++++++++++ docs/diagrams/a6-latency-injection.svg | 51 +++++++++++++++++ docs/diagrams/a8-rolling-restart.svg | 47 ++++++++++++++++ docs/diagrams/b0-autoconfiguration.svg | 45 +++++++++++++++ docs/diagrams/b2-store-split-consequences.svg | 55 +++++++++++++++++++ docs/diagrams/b7-cookie-secret.svg | 52 ++++++++++++++++++ docs/diagrams/c2-backchannel-logout.svg | 44 +++++++++++++++ docs/diagrams/d2-version-upgrade.svg | 48 ++++++++++++++++ docs/experiment-00-session-replication.md | 9 +++ docs/experiment-a1-jgroups-transport-block.md | 9 +++ docs/experiment-a3-database-crash.md | 9 +++ docs/experiment-a4-node-loss.md | 9 +++ docs/experiment-a5-asymmetric-partition.md | 9 +++ docs/experiment-a6-latency-injection.md | 9 +++ docs/experiment-a8-rolling-restart.md | 9 +++ docs/experiment-b0-bff-redis-deploy.md | 9 +++ docs/experiment-b2-multi-instance-session.md | 9 +++ docs/experiment-b7-cookie-secret-rotation.md | 9 +++ docs/experiment-c2-backchannel-logout.md | 9 +++ docs/experiment-d2-version-upgrade.md | 9 +++ docs/experiment-followup-untested-items.md | 12 ++++ docs/experiment-index.md | 9 ++- 26 files changed, 721 insertions(+), 1 deletion(-) create mode 100644 docs/diagrams/a0-session-replication.svg create mode 100644 docs/diagrams/a1-transport-block.svg create mode 100644 docs/diagrams/a3-database-crash.svg create mode 100644 docs/diagrams/a4-node-loss.svg create mode 100644 docs/diagrams/a5-asymmetric-partition.svg create mode 100644 docs/diagrams/a6-latency-injection.svg create mode 100644 docs/diagrams/a8-rolling-restart.svg create mode 100644 docs/diagrams/b0-autoconfiguration.svg create mode 100644 docs/diagrams/b2-store-split-consequences.svg create mode 100644 docs/diagrams/b7-cookie-secret.svg create mode 100644 docs/diagrams/c2-backchannel-logout.svg create mode 100644 docs/diagrams/d2-version-upgrade.svg diff --git a/docs/diagrams/a0-session-replication.svg b/docs/diagrams/a0-session-replication.svg new file mode 100644 index 0000000..d23d854 --- /dev/null +++ b/docs/diagrams/a0-session-replication.svg @@ -0,0 +1,48 @@ + + + + + + + +A-0 — 세션을 공유하는 것은 Infinispan 이 아니라 PostgreSQL 이다 +클러스터가 형성됐다는 것과 세션이 복제된다는 것은 다른 얘기였다 + +keycloak-0 (kc-lab-2) + +Infinispan sessions 캐시 +자기가 처리한 로그인만 들어 있다 +entries_unique 는 노드마다 다르다 + +keycloak-1 (kc-lab-1) + +Infinispan sessions 캐시 +여기에도 자기 것만 있다 +상대 세션은 들어오지 않는다 + + +복제 없음 +세션 엔트리는 노드 사이를 건너가지 않는다 + +PostgreSQL +OFFLINE_USER_SESSION (offline_flag='0') +두 노드가 같은 행을 본다 + + +SELECT / INSERT +SELECT / INSERT + +근거 — 노드 A 로 로그인하고 노드 B 로 refresh 했을 때, 반대편 노드가 날린 SQL 을 문장 로깅으로 직접 잡았다 + diff --git a/docs/diagrams/a1-transport-block.svg b/docs/diagrams/a1-transport-block.svg new file mode 100644 index 0000000..1fafc3a --- /dev/null +++ b/docs/diagrams/a1-transport-block.svg @@ -0,0 +1,47 @@ + + + + + + + +A-1 — 7800 을 막아도 세션 공유는 안 깨진다. 깨지는 것은 로그아웃 전파다 +예측 하나가 빗나갔고, 예상 못 한 것이 둘 나왔다 + +keycloak-0 +로그인 처리 · 세션을 DB 에 쓴다 + +keycloak-1 +DB 를 읽어 같은 세션을 안다 + +TCP 7800 차단 +NetworkPolicy 는 허용목록이다 +8080·9000 만 열고 7800 은 누락시킨다 + +PostgreSQL +세션은 여기 있다 → 교차 노드 refresh 200 + + + +깨진 것 — 로그아웃 전파 +무효화 통지가 7800 을 탄다 +400 이어야 할 refresh 가 200 을 반환했다 + +★ 주입이 먹지 않았다 — conntrack +ESTABLISHED 연결은 규칙 평가를 건너뛴다 +cluster_size 가 25분간 2 로 남았다 → 삭제 후에야 갈렸다 +그리고 예상 못 한 둘째 — 분단된 노드가 readiness 실패로 스스로 로드밸런서에서 빠진다 +덕분에 외부에서는 장애가 보이지 않는다. 관측 지점을 밖에만 두면 이 실험은 「아무 일도 없음」으로 보인다 + diff --git a/docs/diagrams/a3-database-crash.svg b/docs/diagrams/a3-database-crash.svg new file mode 100644 index 0000000..02a4a16 --- /dev/null +++ b/docs/diagrams/a3-database-crash.svg @@ -0,0 +1,55 @@ + + + + + + + +A-3 — 200 을 받은 로그인 153건 중 4건이 DB 에 없다 +버그가 아니라 synchronous_commit OFF 의 대가를 실측한 것이다 + +클라이언트 +로그인 요청 +200 + 토큰 수신 + +Keycloak +트랜잭션마다 +SET LOCAL synchronous_commit OFF + +PostgreSQL +COMMIT 을 즉시 반환 +WAL 은 아직 메모리에 + +디스크 +wal_writer_delay +200ms 뒤 기록 + + + +이 구간이 비어 있다 + +✂ 여기서 강제 종료 + +결과 — RPO 는 0 이 아니다 +클라이언트가 200 받은 로그인 153 건 +그중 DB 에 실제로 존재 149 건 +★ 유실 4 건 + +주입도 두 번 실패했다 +kubectl delete --force 는 크래시가 아니다 — 런타임이 SIGTERM 을 보내 +PostgreSQL 이 정상 플러시했고 유실이 0 이었다 +kill -9 1 도 무시된다 — PID 1 은 자기 네임스페이스의 SIGKILL 을 받지 않는다 +백엔드 프로세스를 죽여 postmaster 가 reinitialize 하게 만들자 비로소 "not properly shut down / redo starts" 가 찍혔다 + diff --git a/docs/diagrams/a4-node-loss.svg b/docs/diagrams/a4-node-loss.svg new file mode 100644 index 0000000..2ac5824 --- /dev/null +++ b/docs/diagrams/a4-node-loss.svg @@ -0,0 +1,52 @@ + + + + + + + +A-4 — 둘 다 전면 장애지만 이유가 다르다 +그리고 죽은 파드가 산 파드보다 건강해 보인다 + +4a — 워커(kc-lab-2) 상실 + +keycloak-0 +PostgreSQL 도 여기 있었다 + +keycloak-1 살아있음 +그러나 DB 가 없다 +외부 응답 503 · kubectl 정상 +DB 가 같이 죽어서 장애다. 남은 파드는 돌지만 아무것도 못 한다 +PVC 가 local-path 라 다른 노드로 재배치되지 않는다 + +4b — 컨트롤 플레인(kc-lab-1) 상실 + +keycloak-0 +★ 계속 돌고 있다 + +API 서버 · traefik +들어갈 길이 없다 +외부 응답 000 · kubectl 불통 +워크로드는 멀쩡한데 도달할 수 없어 장애다 +진입점이 단일 노드에 있으면 워크로드 이중화는 의미가 없다 + +★ 예상하지 못한 것 셋 +1. 죽은 파드가 산 파드보다 건강해 보인다 — kubelet 이 사라져 상태가 갱신되지 않으니 Running 으로 남는다 +2. StatefulSet 은 Terminating 파드의 대체를 만들지 않는다 — 이름이 같아야 하므로 지워지기를 기다린다 +3. node-monitor-grace-period 40초 + tolerationSeconds 300초 = 축출까지 5분 40초 + +복구는 둘 다 virsh start 이후 60초 +장애 시간의 대부분은 복구가 아니라 "누가 죽은 것을 알아채는 데" 걸린 시간이다 + diff --git a/docs/diagrams/a5-asymmetric-partition.svg b/docs/diagrams/a5-asymmetric-partition.svg new file mode 100644 index 0000000..1cf0ab9 --- /dev/null +++ b/docs/diagrams/a5-asymmetric-partition.svg @@ -0,0 +1,49 @@ + + + + + + + +A-5 — 비대칭 차단은 클러스터를 가르지 못한다. 그리고 갈라도 서비스는 계속된다 +주입을 세 번 실패했고, 세 번 모두 「아무 일도 없었다」로 보였다 + +① 한 방향만 차단 + +keycloak-0 + +keycloak-1 + + +열린 방향으로 재연결한다 → cluster_size 2 유지. 가르지 못한다 + +② 양방향 완전 차단 + +keycloak-0 + +keycloak-1 + + +양쪽 모두 멤버 1개. 그런데 한쪽만 DOWN 이 된다 — 코디네이터 쪽이 살아남는다 + +그래서 전면 장애 경로가 없다 — 외부 응답 200 유지 +분단된 쪽이 스스로 readiness 를 떨어뜨려 로드밸런서에서 빠지고, 남은 쪽이 계속 응답한다 + +★ 세 번의 주입 실패 — 전부 「조용히」 실패했다 +1. iptables -I FORWARD 1 이 무시됐다 — kube-router 가 자기 체인을 FORWARD 맨 위에 다시 끼워 넣는다 (패킷 0) +2. 방향이 뒤집혀 있었다 — JGroups 의 client/server 역할은 재시작마다 바뀐다. raw 규칙이 엉뚱한 노드에 걸렸다 +3. dev eth0 이 없다 — Debian 은 enp1s0 이고, 게다가 flannel VXLAN 이 이미 캡슐화해 파드 IP 가 안 보인다 +해결: raw 테이블 PREROUTING(conntrack 보다 먼저) + flannel.1 인터페이스에서 필터 + diff --git a/docs/diagrams/a6-latency-injection.svg b/docs/diagrams/a6-latency-injection.svg new file mode 100644 index 0000000..7ab072f --- /dev/null +++ b/docs/diagrams/a6-latency-injection.svg @@ -0,0 +1,51 @@ + + + + + + + +A-6 — 200밀리초가 22초가 된다 +지연은 왕복 횟수만큼 곱해지고, 커넥션 풀에서 한 번 더 곱해진다 + +대조군 keycloak-0 +DB 와 같은 노드 · 66ms + +시험군 keycloak-1 +tc netem delay 200ms + + +단일 요청 +1,872 ms (28배) + +동시 20건 +22.2 초 + + +왜 200ms 가 1,872ms 가 되는가 — 왕복 횟수 + +로그인 한 번은 DB 왕복을 여러 번 한다: 클라이언트 조회 · 사용자 조회 · 세션 INSERT · 커밋 … +왕복마다 200ms 가 더해진다. 애플리케이션은 아무것도 잘못하지 않았다 +왜 1,872ms 가 22.2초가 되는가 — 커넥션 풀 + +요청이 커넥션을 오래 붙들고 있으면 +뒤의 요청은 풀에서 대기한다 +agroal 커넥션 획득 대기 최대 20,000 ms + +그리고 파드가 죽는다 +readiness 프로브가 타임아웃으로 실패 → +느린 노드가 로드밸런서에서 빠진다. 느림이 장애로 승격된다 +측정 장치 주의 — 동시 20건을 kubectl run --rm -i 로 돌리면 출력 스트림이 유실된다. 상주 탐침에 파일로 모아야 한다 + diff --git a/docs/diagrams/a8-rolling-restart.svg b/docs/diagrams/a8-rolling-restart.svg new file mode 100644 index 0000000..c464454 --- /dev/null +++ b/docs/diagrams/a8-rolling-restart.svg @@ -0,0 +1,47 @@ + + + + + + + +A-8 — 세션은 살아남고 캐시만 사라진다 +이것이 persistent-user-sessions 를 켜는 진짜 이유다 + +kubectl rollout restart statefulset/keycloak — 한 번에 한 파드씩 + +사라지는 것 — 메모리 +Infinispan sessions 캐시 +entries_unique → 0 +파드와 함께 없어진다 + +남는 것 — PostgreSQL +OFFLINE_USER_SESSION +151 → 151 그대로 +재시작과 무관하다 + +그래서 사용자는 +재시작 전 발급한 refresh token +여전히 200 +전 구간 중단 없음 + + + +클러스터는 자동 재형성된다 +JGROUPS_PING 테이블에 다시 등록되고 cluster_size 가 2 로 돌아온다 — 사람이 할 일이 없다 + +★ A-7 과 대조 — volatile 이면 같은 재시작에서 refresh 가 400 Session not active 가 된다. +세션이 메모리에만 있었으므로 캐시가 사라지는 순간 세션도 사라진다. 이 실험의 결론은 버전 조건부다 + diff --git a/docs/diagrams/b0-autoconfiguration.svg b/docs/diagrams/b0-autoconfiguration.svg new file mode 100644 index 0000000..88e2363 --- /dev/null +++ b/docs/diagrams/b0-autoconfiguration.svg @@ -0,0 +1,45 @@ + + + + + + + +B-0 — 아무것도 설정하지 않으면 Spring 이 무엇을 고르는가 +추측이 맞았지만, 추측으로 두면 안 되는 이유가 둘째 줄에 있다 + +/actuator/beans 가 실제로 답한 것 +authorizedClientService → InMemoryOAuth2AuthorizedClientService +authorizedClientRepository → AuthenticatedPrincipalOAuth2AuthorizedClientRepository +SessionRepository → 없음 (서블릿 컨테이너 in-memory) +Redis / Spring Session → 없음 + +★ 이름이 곧 설명이다 +AuthenticatedPrincipal…Repository 는 +principal 이름으로 찾는다 + +조회 키에 session id 가 없다 +그래서 세션 저장소를 Redis 로 옮겨도 +토큰은 따라오지 않는다 — B-1 이 겪는 문제의 뿌리 + +두 개가 서로 다른 것을 저장한다 + +Application Session +누가 로그인했는지 · 세션 id 로 찾는다 + +OAuth2AuthorizedClient +access / refresh token · principal 이름으로 찾는다 +이 둘을 하나로 생각하면 B-1·B-2 의 결과를 해석할 수 없다 + diff --git a/docs/diagrams/b2-store-split-consequences.svg b/docs/diagrams/b2-store-split-consequences.svg new file mode 100644 index 0000000..9cff794 --- /dev/null +++ b/docs/diagrams/b2-store-split-consequences.svg @@ -0,0 +1,55 @@ + + + + + + + +B-2 — 저장소를 나눠 문제를 풀자 다른 두 문제가 남았다 +Q1 의 네 항목 중 둘이 통과, 둘이 실패 + +bff 인스턴스 2개 +어느 쪽으로 요청이 가도 된다 +enableServiceLinks: false +(REDIS_PORT 충돌 회피) + +Redis +Application Session +세션 id 로 조회 +로그아웃 시 정리됨 + +PostgreSQL +OAuth2AuthorizedClient +principal 이름으로 조회 +로그아웃해도 남는다 + + + +통과 +① 다른 인스턴스로 요청해도 된다 +② 재시작 후에도 로그인 유지 +저장소를 밖으로 뺐으니 당연한 결과다 + +실패 +③ 같은 사용자의 다른 브라우저가 덮어쓴다 +④ 로그아웃해도 한쪽만 정리된다 +둘 다 저장소 선택의 문제가 아니라 스키마의 문제다 + +★ 뿌리는 DDL 한 줄이다 +PRIMARY KEY (client_registration_id, principal_name) +세션 id 가 키에 없다 → 같은 사용자의 두 세션이 같은 행을 쓴다 → 나중 로그인이 앞의 토큰을 덮어쓴다 + +로그아웃 후: Redis 세션 0 키 (정리됨) · PostgreSQL 토큰 1 행 — 평문 refresh token 이 그대로 남는다 + diff --git a/docs/diagrams/b7-cookie-secret.svg b/docs/diagrams/b7-cookie-secret.svg new file mode 100644 index 0000000..0f6f6c5 --- /dev/null +++ b/docs/diagrams/b7-cookie-secret.svg @@ -0,0 +1,52 @@ + + + + + + + +B-7 — BFF 와 정반대의 성질: 공유할 상태가 없고, 대신 겹침 구간도 없다 +oauth2-proxy 는 세션을 쿠키에 담는다 + +replica 는 무엇을 공유하나 + +oauth2-proxy A + +oauth2-proxy B + +같은 k8s Secret 을 읽는다 + + + + +그래서 얻는 것 +공유할 서버 상태가 없다 → 콜백이 다른 replica 로 가도 된다 +replica 를 늘려도 세션 저장소 고민이 없다 +BFF 가 Redis·PostgreSQL 로 풀던 문제가 아예 생기지 않는다 + +★ 대신 겹침 구간을 만들 수 없다 +--cookie-secret 은 단수다 +「옛 secret 도 당분간 받아준다」가 불가능하다 +교체하는 순간 모든 쿠키가 한꺼번에 무효가 된다 + +교체하면 벌어지는 일 +옛 쿠키 → session ticket cookie failed validation +Keycloak SSO 가 살아 있으면 로그인 화면 없이 조용히 재인증 +서버 쪽 세션은 고아로 남는다 — 티켓을 못 풀어 지우지도 못한다 + +→ B-7a 가 이어받았다 +「지울 수 없다」는 oauth2-proxy 의 한계일 뿐이었다. 운영자는 지울 수 있고, TTL 역산으로 고아만 골라낼 수 있다 +그리고 고아는 생성 후 정확히 1시간에 사라진다 — TTL 이 요청으로 갱신되지 않기 때문이다 + diff --git a/docs/diagrams/c2-backchannel-logout.svg b/docs/diagrams/c2-backchannel-logout.svg new file mode 100644 index 0000000..7c012d0 --- /dev/null +++ b/docs/diagrams/c2-backchannel-logout.svg @@ -0,0 +1,44 @@ + + + + + + + +C-2 — 로그아웃이 안 퍼지는 이유는 단순했다. 아무도 구현하지 않았다 +C-1 이 관측한 현상의 원인 +백채널 로그아웃이 동작하려면 양쪽이 다 있어야 한다 + +① IdP 쪽 — Keycloak 클라이언트 설정 +backchannelLogoutUrl +두 클라이언트 모두 비어 있다 +Keycloak 은 부를 주소를 모른다 + +② 앱 쪽 — 수신 엔드포인트 +/logout/connect/back-channel/{registrationId} +BFF 소스에 oidcLogout 설정이 없다 +주소를 알려줘도 받을 곳이 없다 + +★ 그래서 IdP 쪽만 설정하면 되는 줄 알면 틀린다 +backchannelLogoutUrl 을 넣어 봤지만 앱 세션은 그대로 남았다. 받는 쪽이 없기 때문이다 + +네트워크 문제가 아니라는 확인 +Keycloak 파드에서 앱 URL 로 요청 → HTTP 200 +닿기는 한다. 닿아도 처리할 코드가 없을 뿐이다 + +남기는 것 +「설정이 빠졌다」와 「기능이 없다」는 다르게 고쳐야 한다 +여기는 둘 다였고, 확인 순서를 바꿨다면 한쪽만 고치고 끝냈을 것이다 + diff --git a/docs/diagrams/d2-version-upgrade.svg b/docs/diagrams/d2-version-upgrade.svg new file mode 100644 index 0000000..aedec0c --- /dev/null +++ b/docs/diagrams/d2-version-upgrade.svg @@ -0,0 +1,48 @@ + + + + + + + +D-2 — 롤백은 안 된다. 다만 롤링 업데이트가 사고를 절반에서 멈춰줬다 +그리고 이 결론은 조건부였다 — 스키마가 안 바뀌면 롤백된다(후속에서 정밀화) + +앞으로 (26.7.0 → 26.7.3) +된다. 무중단 +87회 요청 전부 200 +databasechangelog 210 → 210 (스키마 변경 없음) +세션 유지 · Infinispan 16.0.12 → 16.0.14 + +뒤로 (스키마가 이미 움직였을 때) +안 된다 +liquibase ValidationFailedException: +1 changesets check sum +새 버전이 남긴 체크섬을 옛 버전이 거부한다 + +★ 그런데 서비스는 살아 있었다 — StatefulSet 롤링 업데이트 덕분이다 +한 번에 한 파드씩 바꾼다. 첫 파드가 기동에 실패하면 거기서 멈추고 나머지는 건드리지 않는다 +그래서 남은 파드가 외부 200 을 계속 냈다. 「롤백 계획」이 없어도 사고가 전면화되지 않았다 + +그러면 무엇으로 판단하나 +select count(*) from databasechangelog +업그레이드 전후 이 수가 같으면 롤백 가능 +늘었으면 스키마가 움직였다 = 롤백 불가 + +전제 — 백업이 먼저다 (D-1) +롤백이 막히는 상황에서 되돌릴 방법은 덤프 복원뿐이다 +D-1 이 그 절차를 재고, D-2 가 그 절차가 필요한 이유를 만든다 +순서를 바꾸면 되돌릴 수 없는 상태에서 백업을 배우게 된다 + diff --git a/docs/experiment-00-session-replication.md b/docs/experiment-00-session-replication.md index 1435976..5a0717a 100644 --- a/docs/experiment-00-session-replication.md +++ b/docs/experiment-00-session-replication.md @@ -16,6 +16,15 @@ --- +## 구조 + +![A-0 — 세션 공유는 PostgreSQL 을 거친다](diagrams/a0-session-replication.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 물음 | 답 | diff --git a/docs/experiment-a1-jgroups-transport-block.md b/docs/experiment-a1-jgroups-transport-block.md index 837193d..7a218c6 100644 --- a/docs/experiment-a1-jgroups-transport-block.md +++ b/docs/experiment-a1-jgroups-transport-block.md @@ -9,6 +9,15 @@ --- +## 구조 + +![A-1 — 7800 차단과 그때 깨지는 것](diagrams/a1-transport-block.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 예측 | 결과 | diff --git a/docs/experiment-a3-database-crash.md b/docs/experiment-a3-database-crash.md index f6a6ca4..5b2687f 100644 --- a/docs/experiment-a3-database-crash.md +++ b/docs/experiment-a3-database-crash.md @@ -9,6 +9,15 @@ --- +## 구조 + +![A-3 — 200 과 디스크 사이의 빈 구간](diagrams/a3-database-crash.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 ``` diff --git a/docs/experiment-a4-node-loss.md b/docs/experiment-a4-node-loss.md index b1b5b1c..fd132d6 100644 --- a/docs/experiment-a4-node-loss.md +++ b/docs/experiment-a4-node-loss.md @@ -17,6 +17,15 @@ --- +## 구조 + +![A-4 — 두 가지 노드 상실과 서로 다른 이유](diagrams/a4-node-loss.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | | 4a 워커 상실 | 4b 컨트롤 플레인 상실 | diff --git a/docs/experiment-a5-asymmetric-partition.md b/docs/experiment-a5-asymmetric-partition.md index 826ff17..335eb83 100644 --- a/docs/experiment-a5-asymmetric-partition.md +++ b/docs/experiment-a5-asymmetric-partition.md @@ -12,6 +12,15 @@ --- +## 구조 + +![A-5 — 비대칭·양방향 차단과 세 번의 주입 실패](diagrams/a5-asymmetric-partition.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 물음 | 답 | diff --git a/docs/experiment-a6-latency-injection.md b/docs/experiment-a6-latency-injection.md index 2b3aa43..6d00339 100644 --- a/docs/experiment-a6-latency-injection.md +++ b/docs/experiment-a6-latency-injection.md @@ -9,6 +9,15 @@ --- +## 구조 + +![A-6 — 지연이 곱해지는 두 단계](diagrams/a6-latency-injection.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 측정 | 값 | diff --git a/docs/experiment-a8-rolling-restart.md b/docs/experiment-a8-rolling-restart.md index 0904799..1d30d58 100644 --- a/docs/experiment-a8-rolling-restart.md +++ b/docs/experiment-a8-rolling-restart.md @@ -9,6 +9,15 @@ --- +## 구조 + +![A-8 — 사라지는 캐시와 남는 세션](diagrams/a8-rolling-restart.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 확인 | 결과 | diff --git a/docs/experiment-b0-bff-redis-deploy.md b/docs/experiment-b0-bff-redis-deploy.md index 1dc5550..7488d5f 100644 --- a/docs/experiment-b0-bff-redis-deploy.md +++ b/docs/experiment-b0-bff-redis-deploy.md @@ -11,6 +11,15 @@ --- +## 구조 + +![B-0 — 자동구성이 고른 것과 그 조회 키](diagrams/b0-autoconfiguration.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 ``` diff --git a/docs/experiment-b2-multi-instance-session.md b/docs/experiment-b2-multi-instance-session.md index a6f61df..ab10136 100644 --- a/docs/experiment-b2-multi-instance-session.md +++ b/docs/experiment-b2-multi-instance-session.md @@ -10,6 +10,15 @@ --- +## 구조 + +![B-2 — 저장소 분리 후 남은 두 문제](diagrams/b2-store-split-consequences.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 B-1 이 남긴 문제(세션만 공유되고 토큰은 안 됨)를 **JDBC 로 옮겨 해결했다.** diff --git a/docs/experiment-b7-cookie-secret-rotation.md b/docs/experiment-b7-cookie-secret-rotation.md index fe0ef71..266d5e3 100644 --- a/docs/experiment-b7-cookie-secret-rotation.md +++ b/docs/experiment-b7-cookie-secret-rotation.md @@ -12,6 +12,15 @@ --- +## 구조 + +![B-7 — 쿠키 세션의 성질과 회전의 대가](diagrams/b7-cookie-secret.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 물음 | 답 | diff --git a/docs/experiment-c2-backchannel-logout.md b/docs/experiment-c2-backchannel-logout.md index 067b27c..6985b2d 100644 --- a/docs/experiment-c2-backchannel-logout.md +++ b/docs/experiment-c2-backchannel-logout.md @@ -8,6 +8,15 @@ --- +## 구조 + +![C-2 — 백채널 로그아웃에 필요한 양쪽](diagrams/c2-backchannel-logout.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 확인 | 결과 | diff --git a/docs/experiment-d2-version-upgrade.md b/docs/experiment-d2-version-upgrade.md index 25f8782..caf89f5 100644 --- a/docs/experiment-d2-version-upgrade.md +++ b/docs/experiment-d2-version-upgrade.md @@ -23,6 +23,15 @@ --- +## 구조 + +![D-2 — 방향에 따라 갈리는 결과](diagrams/d2-version-upgrade.svg) + +> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md). +> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg). + +--- + ## 0. 결론부터 | 확인 | 결과 | diff --git a/docs/experiment-followup-untested-items.md b/docs/experiment-followup-untested-items.md index bdc5555..1c348c1 100644 --- a/docs/experiment-followup-untested-items.md +++ b/docs/experiment-followup-untested-items.md @@ -10,6 +10,18 @@ --- +## 구조 + +이 문서는 세 실험의 후속이므로 각자의 구조도를 참조한다. + +| 항목 | 구조도 | +|---|---| +| D-2 정방향 업그레이드 | [`d2-version-upgrade.svg`](diagrams/d2-version-upgrade.svg) | +| B-4 ③ role 반영 | [`b4-header-forgery.svg`](diagrams/b4-header-forgery.svg) | +| D-4 강제 갱신 | [`d4-renewal-gap.svg`](diagrams/d4-renewal-gap.svg) · [`d4a-hook-timeline.svg`](diagrams/d4a-hook-timeline.svg) | + +--- + ## 0. 결론부터 | 항목 | 결과 | diff --git a/docs/experiment-index.md b/docs/experiment-index.md index bfd38a7..0295a95 100644 --- a/docs/experiment-index.md +++ b/docs/experiment-index.md @@ -41,7 +41,14 @@ | `diagrams/*.svg` | 실험별 구조도 12개 | | `evidence/*/*.png` | Grafana · 브라우저 스크린샷 | -**스크린샷이 없는 실험은 11개다** — `A-3 A-7 B-3 B-4 B-5 B-6 C-2 D-1 D-3 D-4 후속`. +**스크린샷이 없는 실험은 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) 에 측정해 두었다.