From e0d27d47ce955653bafc6ee2680697ddb6de358b Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Fri, 4 Sep 2026 16:35:49 +0900 Subject: [PATCH] docs: correct the places where documents contradicted their own evidence An independent audit found ten documents printing values their evidence files do not contain. C-1 printed a session count of 0 where the evidence says 4, C-2 printed a success readback for a command that exited 1, and A-1 credited the conntrack flush with a split that the timestamps attribute to a pod restart four seconds earlier. Also measured wal_writer_delay, which A-3 had asserted as matching without ever querying it, relabelled the A-6 control that moved 41 percent, noted A-8's nine-sample resolution, corrected D-1's RTO to the 41 seconds its own timeline shows, and added a correction banner to D-2. Every experiment document now links its evidence files with their real collection times, and the duplicate screenshots are documented as duplicates. Co-Authored-By: Claude Opus 5 --- .../console-2026-09-04T07-16-34-745Z.log | 85 +++++++++++++++++++ .../a3-database-crash/08-wal-settings.txt | 9 ++ .../02-injection-verify.txt | 19 +++++ .../evidence/b1-redis-session-store/README.md | 7 ++ .../b2-multi-instance-session/README.md | 11 +++ docs/evidence/c1-multi-app-sso/README.md | 8 ++ docs/evidence/d2-version-upgrade/README.md | 3 +- docs/experiment-00-session-replication.md | 17 ++++ docs/experiment-a1-jgroups-transport-block.md | 49 ++++++++++- docs/experiment-a2-database-loss.md | 26 +++++- docs/experiment-a3-database-crash.md | 38 ++++++++- docs/experiment-a4-node-loss.md | 31 ++++++- docs/experiment-a5-asymmetric-partition.md | 30 +++++++ docs/experiment-a6-latency-injection.md | 27 +++++- docs/experiment-a7-volatile-comparison.md | 17 ++++ docs/experiment-a8-rolling-restart.md | 23 ++++- docs/experiment-b0-bff-redis-deploy.md | 16 ++++ docs/experiment-b1-redis-session-store.md | 16 ++++ docs/experiment-b2-multi-instance-session.md | 33 ++++++- .../experiment-b3-refresh-token-contention.md | 15 ++++ .../experiment-b4-edge-authorization-scope.md | 12 +++ docs/experiment-b5-redis-loss-persistence.md | 15 ++++ docs/experiment-b6-key-rotation.md | 14 +++ docs/experiment-b7-cookie-secret-rotation.md | 15 ++++ docs/experiment-c1-multi-app-sso.md | 24 +++++- docs/experiment-c2-backchannel-logout.md | 23 +++++ docs/experiment-d1-backup-restore.md | 18 +++- docs/experiment-d2-version-upgrade.md | 29 +++++++ docs/experiment-d3-secret-management.md | 13 +++ docs/experiment-d4-certificate-renewal.md | 12 +++ docs/experiment-followup-untested-items.md | 15 ++++ docs/experiment-index.md | 24 +++++- 32 files changed, 678 insertions(+), 16 deletions(-) create mode 100644 docs/evidence/a3-database-crash/08-wal-settings.txt diff --git a/.playwright-mcp/console-2026-09-04T07-16-34-745Z.log b/.playwright-mcp/console-2026-09-04T07-16-34-745Z.log index f03486a..a339bf9 100644 --- a/.playwright-mcp/console-2026-09-04T07-16-34-745Z.log +++ b/.playwright-mcp/console-2026-09-04T07-16-34-745Z.log @@ -19,3 +19,88 @@ [ 179100ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 [ 187598ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 [ 207362ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 213255ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 230544ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 248833ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 257549ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 262324ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 274443ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 294124ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 310689ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 312049ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 327025ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 339933ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 342391ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 362305ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 370693ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 378685ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 397930ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 414007ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 419130ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 420355ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 428557ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 442088ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 445041ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 446677ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 460087ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 462505ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 463673ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 482762ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 495123ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 513442ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 516414ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 536581ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 543441ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 563624ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 580510ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 588408ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 594209ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 604166ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 605295ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 621987ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 639705ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 653320ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 660911ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 678105ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 693662ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 703089ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 704712ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 719727ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 725514ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 730429ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 735135ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 736677ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 751626ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 767197ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 782052ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 797775ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 803343ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 822168ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 828683ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 845938ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 865903ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 883116ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 894375ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 898471ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 914886ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 933184ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 953353ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 963902ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 982846ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 998975ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1000721ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1003229ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1009879ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1016334ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1023805ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1043684ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1049467ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1059107ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1075856ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1076956ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1094159ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1102825ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1116077ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1134809ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1136553ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1141191ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 diff --git a/docs/evidence/a3-database-crash/08-wal-settings.txt b/docs/evidence/a3-database-crash/08-wal-settings.txt new file mode 100644 index 0000000..177ee9d --- /dev/null +++ b/docs/evidence/a3-database-crash/08-wal-settings.txt @@ -0,0 +1,9 @@ +=== A-3 이 가정만 하고 재지 않은 값 === + name | setting | unit | source +------------------------+---------+------+--------- + commit_delay | 0 | | default + synchronous_commit | on | | default + wal_writer_delay | 200 | ms | default + wal_writer_flush_after | 128 | 8kB | default +(4 rows) + diff --git a/docs/evidence/a5-asymmetric-partition/02-injection-verify.txt b/docs/evidence/a5-asymmetric-partition/02-injection-verify.txt index e69de29..2aec49e 100644 --- a/docs/evidence/a5-asymmetric-partition/02-injection-verify.txt +++ b/docs/evidence/a5-asymmetric-partition/02-injection-verify.txt @@ -0,0 +1,19 @@ +# 주의 — 이 파일은 원 실험 시점에 0바이트로 저장됐다. +# 리다이렉션이 stdout 만 받았는데 출력이 stderr 로 갔거나 tee 앞 파이프가 +# 비어 있었던 것으로 보인다. README 는 그 사이 파일 내용을 서술하고 있었는데, +# 그것은 화면에서 본 것을 적은 것이지 이 파일에서 온 것이 아니었다. +# +# 아래는 사후에 다시 수집한 것이며, 원 시점의 DROP 규칙(0 패킷)은 이미 +# 제거되어 재현되지 않는다. 구조적 사실(kube-router 가 자기 체인을 FORWARD +# 최상단에 유지한다)만 확인할 수 있다. +# 원 실험의 결정적 증거는 04-correct-direction.txt 의 패킷 카운터 19/21 이다. + +=== A-5 재수집 — filter 테이블 규칙이 CNI 체인에 밀리는 것 === +Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) +num pkts bytes target prot opt in out source destination +1 1690 3386K KUBE-ROUTER-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* kube-router netpol - TEMCG2JMHZYE7H7T */ +2 7 612 KUBE-PROXY-FIREWALL 0 -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes load balancer firewall */ +3 40 13196 KUBE-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ + + (원 실험 시점의 규칙은 이미 제거됐다. 아래는 kube-router 가 자기 체인을 + FORWARD 최상단에 유지한다는 구조적 사실만 보여준다 — 그것이 실패 원인이었다.) diff --git a/docs/evidence/b1-redis-session-store/README.md b/docs/evidence/b1-redis-session-store/README.md index 902109e..a06ac01 100644 --- a/docs/evidence/b1-redis-session-store/README.md +++ b/docs/evidence/b1-redis-session-store/README.md @@ -16,3 +16,10 @@ 1. **세션은 옮겨졌고 토큰은 안 옮겨졌다.** 빈 81개가 늘었는데 authorized client 관련은 하나도 안 바뀌었다. 2. **refresh token 은 Redis 에 평문으로 있는 게 아니라 아예 없다.** 암호화를 고민하기 전에 이걸 알아야 한다. 3. **"로그인은 되어 있는데 아무것도 못 하는" 상태가 만들어진다** — 완전 로그아웃보다 나쁘다. + +## 스크린샷 주의 + +`b1-login-works-two-replicas.png` 과 `b1-token-boundary-after-redis.png` 는 +**동일 파일**이며 `b2-before-relogin.png` 와도 같다 (md5 `6de826a7…`). +세 시점 모두 `accessTokenStoredOnServer: false` 인 같은 화면이었다. +**시점 구별은 터미널 출력과 Redis/DB 조회가 한다.** diff --git a/docs/evidence/b2-multi-instance-session/README.md b/docs/evidence/b2-multi-instance-session/README.md index 647baf7..b2a09ca 100644 --- a/docs/evidence/b2-multi-instance-session/README.md +++ b/docs/evidence/b2-multi-instance-session/README.md @@ -19,3 +19,14 @@ 2. **refresh token 은 평문이다.** DB 읽기 권한이면 작동하는 토큰을 얻는다. 3. **같은 사용자의 두 번째 로그인이 첫 번째를 덮어쓴다.** 기본키에 session id 가 없어 구조적으로 그렇다. 4. **로그아웃은 셋 중 하나만 지운다.** 평문 토큰과 Keycloak SSO 세션이 남는다. + +## 스크린샷 주의 + +`b2-tokens-shared-across-instances.png` 는 **B-0 의 +`b0-bff-token-boundary.png` 와 동일 파일**이다 (md5 `9ed00537…`). +두 시점 모두 `accessTokenStoredOnServer: true` 인 같은 화면이라 바이트가 같다. + +**그래서 이 png 는 "JDBC 전환으로 토큰이 공유된다" 를 단독으로 증명하지 +못한다.** 그 증명은 `01-jdbc-store-deploy.txt`(테이블 생성)과 +`03-plaintext-tokens.txt`(행에 토큰이 들어 있음)가 한다. +`b2-before-relogin.png` 는 B-1 의 캡처와 동일 파일이다. diff --git a/docs/evidence/c1-multi-app-sso/README.md b/docs/evidence/c1-multi-app-sso/README.md index cdf44cb..b42f480 100644 --- a/docs/evidence/c1-multi-app-sso/README.md +++ b/docs/evidence/c1-multi-app-sso/README.md @@ -17,3 +17,11 @@ 1. **SSO 는 user session 1개에 client session N개** 구조다 — A-3(전체 소실)과 B-3(client 만 제거)의 차이가 여기서 의미를 갖는다. 2. **IdP 세션을 죽여도 두 앱은 계속 동작한다.** 세 층(IdP·앱·토큰)의 수명이 각자이기 때문이다. 3. **IdP 는 "로그인 경로"의 단일 장애점이지 "이미 로그인한 사용자"의 단일 장애점이 아니다.** 장애는 앱 세션 수명만큼 지연되어 몰려온다. + +## 스크린샷 주의 + +`c1-sso-app2-no-login-screen.png` 과 `c1-apps-alive-after-idp-logout.png` 는 +**바이트 단위로 동일한 파일**이다 (md5 `2c703176…`). 두 시점의 화면이 실제로 +같은 내용이었기 때문이며, 조작이 아니다. **다만 그래서 두 시점을 구별하는 +증거가 되지 못한다** — 구별은 `03-` 과 `04-` 의 터미널 출력(client_sessions +1→2, 그리고 IdP 세션 삭제 후 Redis 키 잔존)이 한다. diff --git a/docs/evidence/d2-version-upgrade/README.md b/docs/evidence/d2-version-upgrade/README.md index bd752af..7a81368 100644 --- a/docs/evidence/d2-version-upgrade/README.md +++ b/docs/evidence/d2-version-upgrade/README.md @@ -7,10 +7,11 @@ |---|---| | `01-pre-upgrade.txt` | 백업 396KB · 이미지 26.7.0 · **마이그레이션 210건** · 세션 4 | | `02-rollback-attempt.txt` | 26.0 으로 내리자 `Running(0/1) → Error → CrashLoopBackOff`. **`liquibase.exception.ValidationFailedException`** | +| `d2-upgrade-window.png` | Grafana — 26.7.3 업그레이드 구간의 `cluster_size` 2→1→2 두 번과 파드별 `up` 시계열 교체 (후속 작업에서 촬영) | | `03-roll-forward.txt` | **서비스는 `HTTP 200` 유지**(ready 주소 1개) · 오류 원인 `1 changesets check sum` · 26.7.0 복귀 후 마이그레이션 210·세션 4 그대로 | ## 핵심 세 줄 -1. **롤백은 안 된다.** 체크섬이 안 맞아 Liquibase 가 기동 자체를 거부한다 — "모르는 변경"이 아니라 "아는 변경인데 정의가 다르다". +1. **스키마가 바뀌었으면 롤백은 안 된다.** (26.7.0↔26.7.3 처럼 안 바뀌면 된다 — [`followup`](../followup/) 참조.) 체크섬이 안 맞아 Liquibase 가 기동 자체를 거부한다 — "모르는 변경"이 아니라 "아는 변경인데 정의가 다르다". 2. **StatefulSet 이 사고를 절반에서 멈춰줬다.** 한 파드가 남아 외부 200 을 유지했다. replica 1 이었다면 전면 장애다. 3. **실패한 기동은 스키마를 안 건드렸다.** 그래서 이미지만 되돌려도 복구됐다 — 이미 적용된 뒤였다면 DB 복구(D-1)가 필요하다. diff --git a/docs/experiment-00-session-replication.md b/docs/experiment-00-session-replication.md index b091cd5..b8b8536 100644 --- a/docs/experiment-00-session-replication.md +++ b/docs/experiment-00-session-replication.md @@ -597,6 +597,23 @@ for n in ('BEFORE_K0','BEFORE_K1','AFTER_K0','AFTER_K1'): --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 09:52 – 10:12 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-cross-node-session.txt`](evidence/session-replication/01-cross-node-session.txt) | 터미널 원문 | +| [`02-cache-delta.txt`](evidence/session-replication/02-cache-delta.txt) | 터미널 원문 | +| [`03-cache-ownership.txt`](evidence/session-replication/03-cache-ownership.txt) | 터미널 원문 | +| [`04-read-path-sql.txt`](evidence/session-replication/04-read-path-sql.txt) | 터미널 원문 | +| [`keycloak-admin-sessions.png`](evidence/session-replication/keycloak-admin-sessions.png) | 스크린샷 | +| [`session-cache-entries-per-pod.png`](evidence/session-replication/session-cache-entries-per-pod.png) | 스크린샷 | + +파일별 상세는 [`evidence/session-replication/README.md`](evidence/session-replication/README.md). + ## 11. 재현 ```bash diff --git a/docs/experiment-a1-jgroups-transport-block.md b/docs/experiment-a1-jgroups-transport-block.md index 1ccc33a..80d0297 100644 --- a/docs/experiment-a1-jgroups-transport-block.md +++ b/docs/experiment-a1-jgroups-transport-block.md @@ -177,15 +177,34 @@ sudo conntrack -D -p tcp -s 10.42.1.43 -d 10.42.0.35 --sport 7800 --dport 40023 **양쪽 노드에서, 양쪽 방향으로** 지워야 한다. 서버 쪽 노드에는 튜플이 뒤집혀 기록되어 있다. -그리고 **즉시 끊기지 않는다.** +### ★ 정정 — conntrack 삭제가 분단을 만들었다고 볼 근거가 없다 + +이 문서는 처음에 이렇게 썼다. ``` 11:41 conntrack 삭제 11:44 cluster_size 2 → 1 ← 약 3분 뒤 ``` -TCP 는 상대가 사라졌음을 **재전송 타임아웃**으로 알아낸다. 소켓은 한동안 -`ESTABLISHED` 로 남아 있다. +**증거를 다시 보면 그 인과가 성립하지 않는다.** + +| 시각 | 증거 | +|---|---| +| 11:41 | conntrack 삭제. 직후 `07-cluster-size.txt` 는 **11:45 까지 전부 `2`** | +| **11:44:23** | **`keycloak-0` 파드의 `startTime`** — 스스로 재시작했다 | +| 11:44:27 | `cluster_size` 2 → 1 | +| 11:46:07 | 내가 `delete pod` 를 실행 (이미 떨어진 뒤) | + +**하락은 conntrack 삭제 3분 뒤가 아니라 파드 재시작 4초 뒤에 일어났다.** +같은 문서 6절이 "정책이 걸린 채 재시작되자" 라고 쓴 것이 맞고, +**4절의 "conntrack 삭제 → 3분 뒤 분단" 은 시각이 겹친 것을 인과로 읽은 것이다.** + +conntrack 삭제 자체가 무의미했다는 뜻은 아니다 — 다만 **이 실험은 +그것만으로 분단이 되는지 판정하지 못했다.** 판정한 것은 A-5 이고, +거기서 `raw` 테이블이 필요하다는 것이 드러났다. + +> TCP 가 재전송 타임아웃으로 상대를 알아채는 것은 사실이지만, +> **이 실험에서 그 경로가 발동했다는 증거는 없다.** --- @@ -462,6 +481,30 @@ vendor_jgroups_merge3_get_num_merge_events --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 11:34 – 11:50 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-baseline-cluster.txt`](evidence/a1-jgroups-transport-block/01-baseline-cluster.txt) | 터미널 원문 | +| [`02-control-before-block.txt`](evidence/a1-jgroups-transport-block/02-control-before-block.txt) | 터미널 원문 | +| [`03-block-applied.txt`](evidence/a1-jgroups-transport-block/03-block-applied.txt) | 터미널 원문 | +| [`04-after-block-state.txt`](evidence/a1-jgroups-transport-block/04-after-block-state.txt) | 터미널 원문 | +| [`05-conntrack-problem.txt`](evidence/a1-jgroups-transport-block/05-conntrack-problem.txt) | 터미널 원문 | +| [`06-partition-observed.txt`](evidence/a1-jgroups-transport-block/06-partition-observed.txt) | 터미널 원문 | +| [`07-cluster-size.txt`](evidence/a1-jgroups-transport-block/07-cluster-size.txt) | 터미널 원문 | +| [`08-restart-forced-partition.txt`](evidence/a1-jgroups-transport-block/08-restart-forced-partition.txt) | 터미널 원문 | +| [`09-cross-node-under-partition.txt`](evidence/a1-jgroups-transport-block/09-cross-node-under-partition.txt) | 터미널 원문 | +| [`10-logout-not-propagated.txt`](evidence/a1-jgroups-transport-block/10-logout-not-propagated.txt) | 터미널 원문 | +| [`11-service-impact.txt`](evidence/a1-jgroups-transport-block/11-service-impact.txt) | 터미널 원문 | +| [`12-recovery.txt`](evidence/a1-jgroups-transport-block/12-recovery.txt) | 터미널 원문 | +| [`a1-cluster-size-partition-recovery.png`](evidence/a1-jgroups-transport-block/a1-cluster-size-partition-recovery.png) | 스크린샷 | + +파일별 상세는 [`evidence/a1-jgroups-transport-block/README.md`](evidence/a1-jgroups-transport-block/README.md). + ## 11. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-a2-database-loss.md b/docs/experiment-a2-database-loss.md index eac488b..bf06ced 100644 --- a/docs/experiment-a2-database-loss.md +++ b/docs/experiment-a2-database-loss.md @@ -103,12 +103,19 @@ kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s ① 캐시를 가진 노드(keycloak-0)에서 refresh HTTP 500 ② 캐시가 없는 노드(keycloak-1)에서 refresh HTTP 500 ③ 새 로그인 HTTP 500 - ④ 관리 API (세션 조회 필요) HTTP 500 + ④ 관리 API (세션 조회 필요) HTTP 500 ← 5절의 재측정값 --- 오류 본문 --- {"error":"unknown_error","error_description":"For more on this error consult the server log."} ``` +> **④ 의 첫 측정은 오염됐다** — +> [`03-four-paths.txt`](evidence/a2-database-loss/03-four-paths.txt) 에는 +> `HTTP 000000{"error":"HTTP 401 Unauthorized"}401` 이 남아 있다. +> `curl -w %{http_code}` 출력에 본문이 섞인 것이고, 재시도가 `000` 을 세 번 +> 찍은 뒤 `401` 이 왔다. **위 표의 `500` 은 5절에서 다시 잰 값**이며, +> 첫 측정을 그대로 쓰지 않았다. + ### ① 이 500 인 것이 중요하다 **캐시에 세션을 들고 있어도 refresh 는 실패한다.** @@ -272,6 +279,23 @@ DB 가 돌아와도 CrashLoopBackOff 의 백오프 때문에 회복이 늦어진 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 11:53 – 11:56 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-baseline.txt`](evidence/a2-database-loss/01-baseline.txt) | 터미널 원문 | +| [`02-setup-sessions.txt`](evidence/a2-database-loss/02-setup-sessions.txt) | 터미널 원문 | +| [`03-four-paths.txt`](evidence/a2-database-loss/03-four-paths.txt) | 터미널 원문 | +| [`04-health-and-service.txt`](evidence/a2-database-loss/04-health-and-service.txt) | 터미널 원문 | +| [`05-recovery.txt`](evidence/a2-database-loss/05-recovery.txt) | 터미널 원문 | +| [`a2-up-stayed-1-during-outage.png`](evidence/a2-database-loss/a2-up-stayed-1-during-outage.png) | 스크린샷 | + +파일별 상세는 [`evidence/a2-database-loss/README.md`](evidence/a2-database-loss/README.md). + ## 9. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-a3-database-crash.md b/docs/experiment-a3-database-crash.md index 0db939b..1d8220e 100644 --- a/docs/experiment-a3-database-crash.md +++ b/docs/experiment-a3-database-crash.md @@ -181,8 +181,23 @@ database system is ready to accept connections ★ p5XybeQIYmAs818gO4Vl_5ea ``` -**약 2.6% 유실.** 초당 19건 정도 로그인하던 중이었으므로 -**대략 마지막 0.2초 분량**이다 — `wal_writer_delay` 기본값(200ms)과 맞는다. +**약 2.6% 유실.** + +처음 이 문서는 *"초당 19건 … `wal_writer_delay` 기본값(200ms)과 맞는다"* 고 +썼는데, **그 시점에 `wal_writer_delay` 를 조회한 적이 없었다.** 나중에 쟀다. + +``` + name | setting | unit | source +------------------------+---------+------+--------- + wal_writer_delay | 200 | ms | default + wal_writer_flush_after | 128 | 8kB | default + synchronous_commit | on | | default +``` +[`08-wal-settings.txt`](evidence/a3-database-crash/08-wal-settings.txt) + +**값은 맞았지만 그때는 추정이었다.** 그리고 로그인 속도도 정확히는 +증거의 `8초에 112건` ≈ **초당 14건**이며 19건이 아니다. 4건은 그 속도에서 +**약 0.29초 분량**이고, 200ms 창과 같은 자릿수이되 정확히 일치하지는 않는다. ### 사용자에게 어떻게 보이는가 @@ -284,6 +299,25 @@ ALTER DATABASE keycloak SET synchronous_commit = on; -- SET LOCAL 이 이깁 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 11:58 – 16:32 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-crash-injection.txt`](evidence/a3-database-crash/01-crash-injection.txt) | 터미널 원문 | +| [`02-design-check.txt`](evidence/a3-database-crash/02-design-check.txt) | 터미널 원문 | +| [`03-loss-measurement.txt`](evidence/a3-database-crash/03-loss-measurement.txt) | 터미널 원문 | +| [`04-comparison.txt`](evidence/a3-database-crash/04-comparison.txt) | 터미널 원문 | +| [`05-true-crash.txt`](evidence/a3-database-crash/05-true-crash.txt) | 터미널 원문 | +| [`06-backend-kill-crash.txt`](evidence/a3-database-crash/06-backend-kill-crash.txt) | 터미널 원문 | +| [`07-loss-result.txt`](evidence/a3-database-crash/07-loss-result.txt) | 터미널 원문 | +| [`08-wal-settings.txt`](evidence/a3-database-crash/08-wal-settings.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/a3-database-crash/README.md`](evidence/a3-database-crash/README.md). + ## 8. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-a4-node-loss.md b/docs/experiment-a4-node-loss.md index c619478..ee99d37 100644 --- a/docs/experiment-a4-node-loss.md +++ b/docs/experiment-a4-node-loss.md @@ -24,7 +24,7 @@ | 외부 응답 | **503** | **000** (연결 자체가 안 됨) | | `kubectl` | 정상 | **불통** | | 살아 있는 워크로드 | keycloak-1 (하지만 DB 없음) | **keycloak-0 은 계속 돌고 있다** | -| 복구 시간 | **60초** | **60초** | +| **`virsh start` 이후** 복구 | **60초** | **60초** | **둘 다 전면 장애**지만 이유가 다르다. 4a 는 **DB 가 같이 죽어서**, 4b 는 **들어갈 길이 없어서**다. @@ -204,6 +204,15 @@ virsh start kc-lab-2 **60초.** 사람 개입 없이 전부 제자리로 돌아왔다. +> **이 60초는 MTTR 이 아니다.** `virsh start` 를 친 뒤의 시간이며, +> 실제 장애 구간은 **12:07:43(차단) → 12:17:31(서비스 복귀) ≈ 10분**이다. +> 그 대부분은 내가 관찰하며 보낸 시간이고, **사람이 알아채고 결정하는 시간이 +> 복구 시간의 대부분**이라는 점이 오히려 현실적이다. +> +> 그리고 본문의 `40초`(node-monitor-grace-period)와 `5분`(tolerationSeconds)은 +> **쿠버네티스 기본값을 인용한 것**이며, 관측된 전이 시점(+45초, +270초)이 +> 그 값과 모순되지 않는다는 것까지가 이 실험이 말할 수 있는 범위다. + --- ## 4b. 컨트롤 플레인 노드 상실 (`kc-lab-1`) @@ -358,6 +367,26 @@ virsh start kc-lab-1 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 12:05 – 12:23 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-baseline.txt`](evidence/a4-node-loss/01-baseline.txt) | 터미널 원문 | +| [`02-worker-node-killed.txt`](evidence/a4-node-loss/02-worker-node-killed.txt) | 터미널 원문 | +| [`03-state-during-loss.txt`](evidence/a4-node-loss/03-state-during-loss.txt) | 터미널 원문 | +| [`04-eviction-timing.txt`](evidence/a4-node-loss/04-eviction-timing.txt) | 터미널 원문 | +| [`05-recovery.txt`](evidence/a4-node-loss/05-recovery.txt) | 터미널 원문 | +| [`06-control-plane-inventory.txt`](evidence/a4-node-loss/06-control-plane-inventory.txt) | 터미널 원문 | +| [`07-control-plane-loss.txt`](evidence/a4-node-loss/07-control-plane-loss.txt) | 터미널 원문 | +| [`08-control-plane-recovery.txt`](evidence/a4-node-loss/08-control-plane-recovery.txt) | 터미널 원문 | +| [`a4-up-dropped-per-node.png`](evidence/a4-node-loss/a4-up-dropped-per-node.png) | 스크린샷 | + +파일별 상세는 [`evidence/a4-node-loss/README.md`](evidence/a4-node-loss/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-a5-asymmetric-partition.md b/docs/experiment-a5-asymmetric-partition.md index 6420ece..ec3256a 100644 --- a/docs/experiment-a5-asymmetric-partition.md +++ b/docs/experiment-a5-asymmetric-partition.md @@ -137,6 +137,16 @@ kubectl -n keycloak-lab logs keycloak-0 --since=20m | grep ISPN000094 | awk '$2 suspected = 0 ``` +> **맥락 하나가 빠져 있었다** — +> [`06-view-history-and-cleanup.txt`](evidence/a5-asymmetric-partition/06-view-history-and-cleanup.txt) +> 를 보면 뷰 13 은 **주입(03:33:58)보다 9초 앞선 03:33:49 의 `MergeView`** 로 +> 만들어졌고, 그 직전에는 `|12] (1)` — 즉 **막 분단됐다가 합쳐진 직후**였다. +> `merge_events = 1.0` 도 그 병합의 것이다. +> +> **"주입 전부터 그대로" 는 맞지만, 그 "전" 이 9초였다.** +> 앞선 실패한 주입 시도들이 만든 흔들림이고, 주입 이후 뷰가 변하지 않았다는 +> 결론 자체는 유지된다. + ### 왜 안 갈라졌는가 — **연결 방향이 뒤집혔다** ``` @@ -281,6 +291,26 @@ JGroups 코디네이터는 **가장 오래된 멤버**다. 분단이 나면 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 12:29 – 16:34 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-injection.txt`](evidence/a5-asymmetric-partition/01-injection.txt) | 터미널 원문 | +| [`02-injection-verify.txt`](evidence/a5-asymmetric-partition/02-injection-verify.txt) | 터미널 원문 | +| [`03-raw-table-injection.txt`](evidence/a5-asymmetric-partition/03-raw-table-injection.txt) | 터미널 원문 | +| [`04-correct-direction.txt`](evidence/a5-asymmetric-partition/04-correct-direction.txt) | 터미널 원문 | +| [`05-reconnect-observed.txt`](evidence/a5-asymmetric-partition/05-reconnect-observed.txt) | 터미널 원문 | +| [`06-view-history-and-cleanup.txt`](evidence/a5-asymmetric-partition/06-view-history-and-cleanup.txt) | 터미널 원문 | +| [`07-bidirectional-block.txt`](evidence/a5-asymmetric-partition/07-bidirectional-block.txt) | 터미널 원문 | +| [`08-coordinator-and-recovery.txt`](evidence/a5-asymmetric-partition/08-coordinator-and-recovery.txt) | 터미널 원문 | +| [`a5-cluster-size-bidirectional-block.png`](evidence/a5-asymmetric-partition/a5-cluster-size-bidirectional-block.png) | 스크린샷 | + +파일별 상세는 [`evidence/a5-asymmetric-partition/README.md`](evidence/a5-asymmetric-partition/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-a6-latency-injection.md b/docs/experiment-a6-latency-injection.md index d3846e1..1b93e3f 100644 --- a/docs/experiment-a6-latency-injection.md +++ b/docs/experiment-a6-latency-injection.md @@ -130,10 +130,14 @@ qdisc netem 30: parent 1:3 limit 1000 delay 200ms ``` === 두 노드 지연 비교 (기준선: k0=70ms k1=66ms) === - keycloak-0 평균 41 ms 최대 57 ms ← 영향 없음 + keycloak-0 평균 41 ms 최대 57 ms ← 기준선 70ms 대비 -41% keycloak-1 평균 1872 ms 최대 1887 ms ← 28배 ``` +> **대조군도 변했다** — keycloak-0 은 기준선 70ms 에서 41ms 로 **41% 빨라졌다.** +> 주입과 무관한 변동(JIT 워밍업, 캐시)이며, **"영향 없음" 이라고 쓴 것은 +> 부정확했다.** 다만 keycloak-1 의 28배 증가와는 자릿수가 달라 결론은 유지된다. + ### 왜 200ms 가 1,872ms 가 되는가 A-0 에서 잡은 로그인 트랜잭션의 SQL 이 답이다. @@ -156,6 +160,10 @@ COMMIT 200 ms × 9 왕복 ≈ 1,800 ms 실측 1,872 ms ``` +> **9 는 SQL 목록을 센 것이고 패킷을 추적한 값이 아니다.** 자릿수가 맞는다는 +> 것까지가 이 계산이 말할 수 있는 범위이며, **왕복 수를 확정하려면 +> `tc -s` 나 패킷 캡처가 필요하다.** + > **네트워크 지연은 왕복 횟수만큼 증폭된다.** > "DB 가 200ms 느려졌다"는 "애플리케이션이 200ms 느려졌다"가 아니다. > **쿼리 수를 줄이는 것이 지연 환경에서 결정적인 이유**가 이것이다. @@ -296,6 +304,23 @@ histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 13:12 – 13:16 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-baseline.txt`](evidence/a6-latency-injection/01-baseline.txt) | 터미널 원문 | +| [`02-delay-injected.txt`](evidence/a6-latency-injection/02-delay-injected.txt) | 터미널 원문 | +| [`03-flannel-injection.txt`](evidence/a6-latency-injection/03-flannel-injection.txt) | 터미널 원문 | +| [`04-pool-under-load.txt`](evidence/a6-latency-injection/04-pool-under-load.txt) | 터미널 원문 | +| [`05-recovery.txt`](evidence/a6-latency-injection/05-recovery.txt) | 터미널 원문 | +| [`a6-connection-pool-blocking.png`](evidence/a6-latency-injection/a6-connection-pool-blocking.png) | 스크린샷 | + +파일별 상세는 [`evidence/a6-latency-injection/README.md`](evidence/a6-latency-injection/README.md). + ## 8. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-a7-volatile-comparison.md b/docs/experiment-a7-volatile-comparison.md index 73daa2c..768ea7b 100644 --- a/docs/experiment-a7-volatile-comparison.md +++ b/docs/experiment-a7-volatile-comparison.md @@ -233,6 +233,23 @@ kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 13:22 – 13:32 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-switch-to-volatile.txt`](evidence/a7-volatile-comparison/01-switch-to-volatile.txt) | 터미널 원문 | +| [`02-a0-rerun.txt`](evidence/a7-volatile-comparison/02-a0-rerun.txt) | 터미널 원문 | +| [`03-a8-rerun-restart.txt`](evidence/a7-volatile-comparison/03-a8-rerun-restart.txt) | 터미널 원문 | +| [`04-a1-rerun-partition.txt`](evidence/a7-volatile-comparison/04-a1-rerun-partition.txt) | 터미널 원문 | +| [`05-a2-rerun-db-loss.txt`](evidence/a7-volatile-comparison/05-a2-rerun-db-loss.txt) | 터미널 원문 | +| [`06-restore-persistent.txt`](evidence/a7-volatile-comparison/06-restore-persistent.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/a7-volatile-comparison/README.md`](evidence/a7-volatile-comparison/README.md). + ## 8. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-a8-rolling-restart.md b/docs/experiment-a8-rolling-restart.md index 0caf346..797f7f5 100644 --- a/docs/experiment-a8-rolling-restart.md +++ b/docs/experiment-a8-rolling-restart.md @@ -56,7 +56,14 @@ statefulset.apps/keycloak restarted 200 200 200 200 partitioned roll out complete: 2 new pods have been updated... ``` -**9번 찍어서 9번 다 `200`.** 한 번도 끊기지 않았다. +**9번 찍어서 9번 다 `200`.** + +> **표본은 9개다.** 5초 간격으로 찍었으므로 **5초보다 짧은 끊김은 이 측정으로 +> 잡히지 않는다.** 실제로 후속 작업에서 1초 간격·3초 타임아웃으로 재보니 +> 롤백 전환 순간에 `000` 이 한 번 잡혔다 +> ([`followup`](experiment-followup-untested-items.md) 2절). +> **"무중단" 은 관측 해상도에 달려 있으며, 여기서는 "5초 해상도에서 끊김이 +> 관측되지 않았다" 까지가 정확한 서술이다.** ### 왜 무중단이 되는가 @@ -163,6 +170,20 @@ A-0 에서 세운 모델이 여기서 그대로 확인된다. --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 13:19 – 13:20 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-restart-availability.txt`](evidence/a8-rolling-restart/01-restart-availability.txt) | 터미널 원문 | +| [`02-session-survival.txt`](evidence/a8-rolling-restart/02-session-survival.txt) | 터미널 원문 | +| [`a8-cache-reset-cluster-reformed.png`](evidence/a8-rolling-restart/a8-cache-reset-cluster-reformed.png) | 스크린샷 | + +파일별 상세는 [`evidence/a8-rolling-restart/README.md`](evidence/a8-rolling-restart/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-b0-bff-redis-deploy.md b/docs/experiment-b0-bff-redis-deploy.md index ac0612a..c49c72c 100644 --- a/docs/experiment-b0-bff-redis-deploy.md +++ b/docs/experiment-b0-bff-redis-deploy.md @@ -283,6 +283,22 @@ kubectl exec -- wget -qO- http://localhost:8083/actuator/beans --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 13:39 – 13:46 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-deploy.txt`](evidence/b0-bff-redis-deploy/01-deploy.txt) | 터미널 원문 | +| [`02-autoconfiguration.txt`](evidence/b0-bff-redis-deploy/02-autoconfiguration.txt) | 터미널 원문 | +| [`03-beans-analysis.txt`](evidence/b0-bff-redis-deploy/03-beans-analysis.txt) | 터미널 원문 | +| [`b0-bff-login-success-single-replica.png`](evidence/b0-bff-redis-deploy/b0-bff-login-success-single-replica.png) | 스크린샷 | +| [`b0-bff-token-boundary.png`](evidence/b0-bff-redis-deploy/b0-bff-token-boundary.png) | 스크린샷 | + +파일별 상세는 [`evidence/b0-bff-redis-deploy/README.md`](evidence/b0-bff-redis-deploy/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-b1-redis-session-store.md b/docs/experiment-b1-redis-session-store.md index 4508167..3530bea 100644 --- a/docs/experiment-b1-redis-session-store.md +++ b/docs/experiment-b1-redis-session-store.md @@ -269,6 +269,22 @@ Q3 는 *"저장소를 직접 열어 refresh token 이 평문으로 남는지 확 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 13:59 – 14:03 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-servicelinks-trap.txt`](evidence/b1-redis-session-store/01-servicelinks-trap.txt) | 터미널 원문 | +| [`02-autoconfig-after.txt`](evidence/b1-redis-session-store/02-autoconfig-after.txt) | 터미널 원문 | +| [`03-redis-contents.txt`](evidence/b1-redis-session-store/03-redis-contents.txt) | 터미널 원문 | +| [`b1-login-works-two-replicas.png`](evidence/b1-redis-session-store/b1-login-works-two-replicas.png) | 스크린샷 | +| [`b1-token-boundary-after-redis.png`](evidence/b1-redis-session-store/b1-token-boundary-after-redis.png) | 스크린샷 | + +파일별 상세는 [`evidence/b1-redis-session-store/README.md`](evidence/b1-redis-session-store/README.md). + ## 7. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-b2-multi-instance-session.md b/docs/experiment-b2-multi-instance-session.md index 4f35bcf..16ed842 100644 --- a/docs/experiment-b2-multi-instance-session.md +++ b/docs/experiment-b2-multi-instance-session.md @@ -182,6 +182,14 @@ access_token 헤더 : {"alg":"RS256","typ":"JWT","kid":"OY-caYDNGoP4HMAz-..."} 같은 사용자로 다시 로그인시키고 행을 비교했다. +> **실제로는 브라우저를 두 개 쓰지 않았다.** 증거 +> [`04-overwrite-test.txt`](evidence/b2-multi-instance-session/04-overwrite-test.txt) +> 에 `[모의 두 번째 브라우저] 세션만 지우고` 라고 적혀 있다. +> **세션을 지우고 같은 사용자로 다시 로그인시킨 것**이며, 조회 키가 +> `(clientRegistrationId, principalName)` 이므로 브라우저가 둘이든 하나든 +> **같은 행을 쓴다는 점에서 등가**다. 다만 "두 브라우저에서" 라고 쓴 것은 +> 측정하지 않은 것을 측정한 것처럼 적은 것이다. + ``` === 재로그인 전 === principal_name | access_token_issued_at | at_md5 @@ -285,7 +293,12 @@ PRIMARY KEY (client_registration_id, principal_name) "공유 저장소로 바꾼다" 와 "세션별로 구분한다" 는 다른 문제이며, 전자만 하면 인스턴스 간 공유는 되고 브라우저 간 격리는 안 된다. -### Liquibase 스키마의 방언 차이 +### 스키마 DDL 의 방언 차이 + +> **정정** — 이 절의 제목은 처음에 "Liquibase 스키마의 방언 차이" 였다. +> **Liquibase 가 아니다.** 여기서 스키마를 태우는 것은 Spring Boot 의 +> `spring.sql.init` 이고, DDL 은 `spring-security-oauth2-client` jar 가 +> 번들한 파일이다. (Liquibase 는 Keycloak 이 자기 스키마에 쓰며, D-2 의 주제다.) Spring Security 는 DDL 을 두 벌 제공한다. @@ -307,6 +320,24 @@ Spring Security 는 DDL 을 두 벌 제공한다. --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:09 – 14:13 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-jdbc-store-deploy.txt`](evidence/b2-multi-instance-session/01-jdbc-store-deploy.txt) | 터미널 원문 | +| [`02-schema.txt`](evidence/b2-multi-instance-session/02-schema.txt) | 터미널 원문 | +| [`03-plaintext-tokens.txt`](evidence/b2-multi-instance-session/03-plaintext-tokens.txt) | 터미널 원문 | +| [`04-overwrite-test.txt`](evidence/b2-multi-instance-session/04-overwrite-test.txt) | 터미널 원문 | +| [`05-logout-cleanup.txt`](evidence/b2-multi-instance-session/05-logout-cleanup.txt) | 터미널 원문 | +| [`b2-before-relogin.png`](evidence/b2-multi-instance-session/b2-before-relogin.png) | 스크린샷 | +| [`b2-tokens-shared-across-instances.png`](evidence/b2-multi-instance-session/b2-tokens-shared-across-instances.png) | 스크린샷 | + +파일별 상세는 [`evidence/b2-multi-instance-session/README.md`](evidence/b2-multi-instance-session/README.md). + ## 8. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-b3-refresh-token-contention.md b/docs/experiment-b3-refresh-token-contention.md index af13068..4ae6599 100644 --- a/docs/experiment-b3-refresh-token-contention.md +++ b/docs/experiment-b3-refresh-token-contention.md @@ -75,6 +75,21 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \ --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:16 – 14:17 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-concurrent-refresh.txt`](evidence/b3-refresh-contention/01-concurrent-refresh.txt) | 터미널 원문 | +| [`02-session-impact.txt`](evidence/b3-refresh-contention/02-session-impact.txt) | 터미널 원문 | +| [`03-client-session-removed.txt`](evidence/b3-refresh-contention/03-client-session-removed.txt) | 터미널 원문 | +| [`04-policy-comparison.txt`](evidence/b3-refresh-contention/04-policy-comparison.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/b3-refresh-contention/README.md`](evidence/b3-refresh-contention/README.md). + ## 2. 재현 — 진짜 동시성을 만든다 B-2 에서 토큰이 PostgreSQL 로 공유되므로 두 replica 가 같은 항목을 본다. diff --git a/docs/experiment-b4-edge-authorization-scope.md b/docs/experiment-b4-edge-authorization-scope.md index c2a2039..c24d363 100644 --- a/docs/experiment-b4-edge-authorization-scope.md +++ b/docs/experiment-b4-edge-authorization-scope.md @@ -260,6 +260,18 @@ Spring 의 `request.getHeader()` 는 **첫 번째**를 돌려주고, --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:23 – 14:23 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-header-handling.txt`](evidence/b4-edge-authorization/01-header-handling.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/b4-edge-authorization/README.md`](evidence/b4-edge-authorization/README.md). + ## 7. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-b5-redis-loss-persistence.md b/docs/experiment-b5-redis-loss-persistence.md index 7e40350..0b3708b 100644 --- a/docs/experiment-b5-redis-loss-persistence.md +++ b/docs/experiment-b5-redis-loss-persistence.md @@ -223,6 +223,21 @@ appendfsync everysec ← 기본값 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:24 – 14:28 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-baseline.txt`](evidence/b5-redis-loss/01-baseline.txt) | 터미널 원문 | +| [`02-redis-down.txt`](evidence/b5-redis-loss/02-redis-down.txt) | 터미널 원문 | +| [`03-health-groups.txt`](evidence/b5-redis-loss/03-health-groups.txt) | 터미널 원문 | +| [`04-persistence.txt`](evidence/b5-redis-loss/04-persistence.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/b5-redis-loss/README.md`](evidence/b5-redis-loss/README.md). + ## 5. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-b6-key-rotation.md b/docs/experiment-b6-key-rotation.md index 8f8ff28..a42eed3 100644 --- a/docs/experiment-b6-key-rotation.md +++ b/docs/experiment-b6-key-rotation.md @@ -203,6 +203,20 @@ kcadm.sh get components -r keycloak-patterns -q type=org.keycloak.keys.KeyProvid --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:30 – 14:32 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-before-rotation.txt`](evidence/b6-key-rotation/01-before-rotation.txt) | 터미널 원문 | +| [`02-rotation.txt`](evidence/b6-key-rotation/02-rotation.txt) | 터미널 원문 | +| [`03-old-key-removed.txt`](evidence/b6-key-rotation/03-old-key-removed.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/b6-key-rotation/README.md`](evidence/b6-key-rotation/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-b7-cookie-secret-rotation.md b/docs/experiment-b7-cookie-secret-rotation.md index cce4981..902b267 100644 --- a/docs/experiment-b7-cookie-secret-rotation.md +++ b/docs/experiment-b7-cookie-secret-rotation.md @@ -255,6 +255,21 @@ B-6 에서 Keycloak 은 `kid` 로 여러 키를 구분해 무중단 회전을 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:35 – 14:42 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-deploy.txt`](evidence/b7-cookie-secret/01-deploy.txt) | 터미널 원문 | +| [`02-cookie-portability.txt`](evidence/b7-cookie-secret/02-cookie-portability.txt) | 터미널 원문 | +| [`03-rotation.txt`](evidence/b7-cookie-secret/03-rotation.txt) | 터미널 원문 | +| [`b7-oauth2proxy-login-success.png`](evidence/b7-cookie-secret/b7-oauth2proxy-login-success.png) | 스크린샷 | + +파일별 상세는 [`evidence/b7-cookie-secret/README.md`](evidence/b7-cookie-secret/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-c1-multi-app-sso.md b/docs/experiment-c1-multi-app-sso.md index 3e61c02..cebd820 100644 --- a/docs/experiment-c1-multi-app-sso.md +++ b/docs/experiment-c1-multi-app-sso.md @@ -59,10 +59,15 @@ DB 를 직접 지우고 Keycloak 을 재시작해야 했다 — **캐시 때문 (A-1 에서 확인한 대로, DB 를 직접 지워도 캐시는 남는다). ``` - Keycloak 온라인 세션: 0 + Keycloak 온라인 세션: 4 ← 초기화가 안 먹었다 app1 HTTP 200 / app2 HTTP 200 ``` +> **정정** — 이 문서는 처음에 이 값을 `0` 으로 인쇄했다. 증거 +> [`01-baseline.txt`](evidence/c1-multi-app-sso/01-baseline.txt) 는 `4` 다. +> `logout-all` 이 듣지 않아 세션이 남아 있었고, 그래서 아래 절차(DB 직접 삭제 + +> Keycloak 재시작)가 필요했다. **`0` 은 그 다음 단계의 값이었다.** + --- ## 2. SSO 가 동작한다 @@ -216,6 +221,23 @@ select us.user_session_id, r.name as realm, ... --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:44 – 14:48 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-baseline.txt`](evidence/c1-multi-app-sso/01-baseline.txt) | 터미널 원문 | +| [`02-after-app1-login.txt`](evidence/c1-multi-app-sso/02-after-app1-login.txt) | 터미널 원문 | +| [`03-after-app2-visit.txt`](evidence/c1-multi-app-sso/03-after-app2-visit.txt) | 터미널 원문 | +| [`04-sso-session-killed.txt`](evidence/c1-multi-app-sso/04-sso-session-killed.txt) | 터미널 원문 | +| [`c1-apps-alive-after-idp-logout.png`](evidence/c1-multi-app-sso/c1-apps-alive-after-idp-logout.png) | 스크린샷 | +| [`c1-sso-app2-no-login-screen.png`](evidence/c1-multi-app-sso/c1-sso-app2-no-login-screen.png) | 스크린샷 | + +파일별 상세는 [`evidence/c1-multi-app-sso/README.md`](evidence/c1-multi-app-sso/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-c2-backchannel-logout.md b/docs/experiment-c2-backchannel-logout.md index 6156b98..6aee823 100644 --- a/docs/experiment-c2-backchannel-logout.md +++ b/docs/experiment-c2-backchannel-logout.md @@ -78,6 +78,14 @@ kcadm.sh update clients/ -r keycloak-patterns \ backchannel.logout.url = https://app1.hyeonworks.com/logout/connect/back-channel/keycloak ``` +> **출처 주의** — 위 확인 출력은 +> [`02-configure-idp.txt`](evidence/c2-backchannel-logout/02-configure-idp.txt) 가 +> 아니라 그 뒤 별도로 실행한 조회에서 나온 것이다. 그 파일에는 +> **`command terminated with exit code 1`** 이 남아 있다 — +> `-s "attributes.backchannel.logout.url=..."` 의 점 표기가 실패한 첫 시도이며, +> JSON 으로 다시 넣어 성공했다. **실패한 시도의 파일에 성공 출력을 붙여 +> 인쇄한 것은 잘못이었다.** + ### 살아 있는 세션에 로그아웃을 걸었다 ``` @@ -200,6 +208,21 @@ app2(oauth2-proxy)는 못 한다. **한 SSO 안에서 로그아웃 전파가 앱 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:50 – 14:53 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-current-state.txt`](evidence/c2-backchannel-logout/01-current-state.txt) | 터미널 원문 | +| [`02-configure-idp.txt`](evidence/c2-backchannel-logout/02-configure-idp.txt) | 터미널 원문 | +| [`03-logout-attempt.txt`](evidence/c2-backchannel-logout/03-logout-attempt.txt) | 터미널 원문 | +| [`04-reachability.txt`](evidence/c2-backchannel-logout/04-reachability.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/c2-backchannel-logout/README.md`](evidence/c2-backchannel-logout/README.md). + ## 7. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-d1-backup-restore.md b/docs/experiment-d1-backup-restore.md index 1fd3c2f..b76d802 100644 --- a/docs/experiment-d1-backup-restore.md +++ b/docs/experiment-d1-backup-restore.md @@ -25,7 +25,7 @@ | 복구 시간 | **1초** (`15:00:12 → 15:00:13`), **오류 0건** | | 서비스 회복 | **재시작 없이 15초 이내** (`restarts=0`) | | 데이터 일치 | **완전 일치** — realms 2 / clients 15 / users 2 / sessions 3 / authclients 1 | -| **RTO** | 약 **30초** (파괴 감지부터 서비스 복귀까지) | +| **RTO** | **41초** (14:59:47 파괴 → 15:00:28 서비스 확인) | | **RPO** | **마지막 덤프 시점** + A-3 의 `synchronous_commit OFF` 손실 | **그리고 예상 못 한 것 — 스키마를 통째로 지웠는데 서비스가 `200` 을 계속 냈다.** @@ -169,7 +169,7 @@ volatile 이었다면 세션은 애초에 DB 에 없으므로 **복구해도 전 15:00:13 복구 완료 ~15:00:28 서비스 정상 확인 - RTO ≈ 30초 (이 규모에서는 대부분이 사람의 판단 시간이다) + RTO = 41초 (이 규모에서는 대부분이 사람의 판단 시간이다) ``` ### RPO 는 두 겹이다 @@ -254,6 +254,20 @@ A-4 에서 PVC 가 노드에 묶인 것과 같은 문제이며, --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 14:57 – 14:58 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-backup.txt`](evidence/d1-backup-restore/01-backup.txt) | 터미널 원문 | +| [`02-destruction.txt`](evidence/d1-backup-restore/02-destruction.txt) | 터미널 원문 | +| [`03-restore.txt`](evidence/d1-backup-restore/03-restore.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/d1-backup-restore/README.md`](evidence/d1-backup-restore/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-d2-version-upgrade.md b/docs/experiment-d2-version-upgrade.md index 5cec053..25f8782 100644 --- a/docs/experiment-d2-version-upgrade.md +++ b/docs/experiment-d2-version-upgrade.md @@ -7,6 +7,20 @@ 선행: [`D-1`](experiment-d1-backup-restore.md) — **백업이 전제다** · [`A-8`](experiment-a8-rolling-restart.md) — 롤링 재시작이 안전하다는 것이 전제 +> ## ★ 정정 — 이 문서의 결론은 조건부다 +> +> 이 문서는 *"롤백이 안 된다"* 고 단정했다. 나중에 +> [`후속 문서`](experiment-followup-untested-items.md) 에서 26.7.0 ↔ 26.7.3 을 +> 시험하니 **롤백이 성공했다.** +> +> | 버전 차 | `databasechangelog` | 롤백 | +> |---|---|---| +> | 26.7.0 → 26.0 | 체크섬 불일치 | **불가** | +> | 26.7.0 ↔ 26.7.3 | **210 → 210, 변화 없음** | **가능** | +> +> **판단 기준은 버전 번호가 아니라 `databasechangelog` 의 행 수가 바뀌었는가다.** +> 아래 본문은 스키마가 바뀐 경우에 해당한다. + --- ## 0. 결론부터 @@ -176,6 +190,21 @@ kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 15:00 – 16:15 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-pre-upgrade.txt`](evidence/d2-version-upgrade/01-pre-upgrade.txt) | 터미널 원문 | +| [`02-rollback-attempt.txt`](evidence/d2-version-upgrade/02-rollback-attempt.txt) | 터미널 원문 | +| [`03-roll-forward.txt`](evidence/d2-version-upgrade/03-roll-forward.txt) | 터미널 원문 | +| [`d2-upgrade-window.png`](evidence/d2-version-upgrade/d2-upgrade-window.png) | 스크린샷 | + +파일별 상세는 [`evidence/d2-version-upgrade/README.md`](evidence/d2-version-upgrade/README.md). + ## 7. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-d3-secret-management.md b/docs/experiment-d3-secret-management.md index b516523..16bde82 100644 --- a/docs/experiment-d3-secret-management.md +++ b/docs/experiment-d3-secret-management.md @@ -193,6 +193,19 @@ A-0 의 관측 스택에서 `nodes/proxy` 서브리소스를 따로 줘야 했 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 15:05 – 15:06 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-base64-not-encryption.txt`](evidence/d3-secret-management/01-base64-not-encryption.txt) | 터미널 원문 | +| [`02-at-rest.txt`](evidence/d3-secret-management/02-at-rest.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/d3-secret-management/README.md`](evidence/d3-secret-management/README.md). + ## 6. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-d4-certificate-renewal.md b/docs/experiment-d4-certificate-renewal.md index 5332d59..4c313da 100644 --- a/docs/experiment-d4-certificate-renewal.md +++ b/docs/experiment-d4-certificate-renewal.md @@ -159,6 +159,18 @@ A-1 의 NetworkPolicy, A-3 의 `--grace-period=0`, B-5 의 AOF 모두 --- +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 15:09 – 15:09 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-certificate-state.txt`](evidence/d4-certificate-renewal/01-certificate-state.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/d4-certificate-renewal/README.md`](evidence/d4-certificate-renewal/README.md). + ## 5. 재현 절차 (명령어) ```bash diff --git a/docs/experiment-followup-untested-items.md b/docs/experiment-followup-untested-items.md index fb2ad15..e56d684 100644 --- a/docs/experiment-followup-untested-items.md +++ b/docs/experiment-followup-untested-items.md @@ -247,3 +247,18 @@ sudo certbot renew --force-renewal **0.2초 간격으로 재는 이유** — 2절에서 1초 간격으로는 전환을 거의 못 잡았다. nginx reload 는 그보다 훨씬 짧을 것이므로 해상도를 올려야 한다. + +--- + +## 증거 파일 + +**증거 수집 시각: 2026-09-04 15:23 – 16:17 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.) + +| 파일 | 종류 | +|---|---| +| [`01-d2-forward-upgrade.txt`](evidence/followup/01-d2-forward-upgrade.txt) | 터미널 원문 | +| [`02-d2-rollback-same-schema.txt`](evidence/followup/02-d2-rollback-same-schema.txt) | 터미널 원문 | +| [`03-b4-role-propagation.txt`](evidence/followup/03-b4-role-propagation.txt) | 터미널 원문 | +| [`04-observability-gap.txt`](evidence/followup/04-observability-gap.txt) | 터미널 원문 | + +파일별 상세는 [`evidence/followup/README.md`](evidence/followup/README.md). diff --git a/docs/experiment-index.md b/docs/experiment-index.md index dbfa785..3851796 100644 --- a/docs/experiment-index.md +++ b/docs/experiment-index.md @@ -38,8 +38,8 @@ | `diagrams/*.svg` | 실험별 구조도 12개 | | `evidence/*/*.png` | Grafana · 브라우저 스크린샷 | -**B층(B-1·B-3·B-4·B-5)에는 Grafana 증거가 없다.** 안 찍은 것이 아니라 -Prometheus 가 Redis·BFF·PostgreSQL 을 긁지 않는다 — +**스크린샷이 없는 실험은 11개다** — `A-3 A-7 B-3 B-4 B-5 B-6 C-2 D-1 D-3 D-4 후속`. +그중 B층은 **Prometheus 가 Redis·BFF·PostgreSQL 을 긁지 않아** 만들 수가 없다 — [`followup/04-observability-gap.txt`](evidence/followup/04-observability-gap.txt) 에 측정해 두었다. ## 문서 지도 @@ -51,6 +51,26 @@ Prometheus 가 Redis·BFF·PostgreSQL 을 긁지 않는다 — | [`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 에 명시했다. + ## 반복해서 배운 것 — 주입이 안 걸린 사례 **아홉 번 있었다. 전부 "아무 일도 없었다" 로 보였다.**