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 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-04 16:35:49 +09:00
co-authored by Claude Opus 5
parent 78b270559c
commit e0d27d47ce
32 changed files with 678 additions and 16 deletions
@@ -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 [ 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 [ 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 [ 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
@@ -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)
@@ -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 최상단에 유지한다는 구조적 사실만 보여준다 — 그것이 실패 원인이었다.)
@@ -16,3 +16,10 @@
1. **세션은 옮겨졌고 토큰은 안 옮겨졌다.** 빈 81개가 늘었는데 authorized client 관련은 하나도 안 바뀌었다. 1. **세션은 옮겨졌고 토큰은 안 옮겨졌다.** 빈 81개가 늘었는데 authorized client 관련은 하나도 안 바뀌었다.
2. **refresh token 은 Redis 에 평문으로 있는 게 아니라 아예 없다.** 암호화를 고민하기 전에 이걸 알아야 한다. 2. **refresh token 은 Redis 에 평문으로 있는 게 아니라 아예 없다.** 암호화를 고민하기 전에 이걸 알아야 한다.
3. **"로그인은 되어 있는데 아무것도 못 하는" 상태가 만들어진다** — 완전 로그아웃보다 나쁘다. 3. **"로그인은 되어 있는데 아무것도 못 하는" 상태가 만들어진다** — 완전 로그아웃보다 나쁘다.
## 스크린샷 주의
`b1-login-works-two-replicas.png``b1-token-boundary-after-redis.png`
**동일 파일**이며 `b2-before-relogin.png` 와도 같다 (md5 `6de826a7…`).
세 시점 모두 `accessTokenStoredOnServer: false` 인 같은 화면이었다.
**시점 구별은 터미널 출력과 Redis/DB 조회가 한다.**
@@ -19,3 +19,14 @@
2. **refresh token 은 평문이다.** DB 읽기 권한이면 작동하는 토큰을 얻는다. 2. **refresh token 은 평문이다.** DB 읽기 권한이면 작동하는 토큰을 얻는다.
3. **같은 사용자의 두 번째 로그인이 첫 번째를 덮어쓴다.** 기본키에 session id 가 없어 구조적으로 그렇다. 3. **같은 사용자의 두 번째 로그인이 첫 번째를 덮어쓴다.** 기본키에 session id 가 없어 구조적으로 그렇다.
4. **로그아웃은 셋 중 하나만 지운다.** 평문 토큰과 Keycloak SSO 세션이 남는다. 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 의 캡처와 동일 파일이다.
+8
View File
@@ -17,3 +17,11 @@
1. **SSO 는 user session 1개에 client session N개** 구조다 — A-3(전체 소실)과 B-3(client 만 제거)의 차이가 여기서 의미를 갖는다. 1. **SSO 는 user session 1개에 client session N개** 구조다 — A-3(전체 소실)과 B-3(client 만 제거)의 차이가 여기서 의미를 갖는다.
2. **IdP 세션을 죽여도 두 앱은 계속 동작한다.** 세 층(IdP·앱·토큰)의 수명이 각자이기 때문이다. 2. **IdP 세션을 죽여도 두 앱은 계속 동작한다.** 세 층(IdP·앱·토큰)의 수명이 각자이기 때문이다.
3. **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 키 잔존)이 한다.
+2 -1
View File
@@ -7,10 +7,11 @@
|---|---| |---|---|
| `01-pre-upgrade.txt` | 백업 396KB · 이미지 26.7.0 · **마이그레이션 210건** · 세션 4 | | `01-pre-upgrade.txt` | 백업 396KB · 이미지 26.7.0 · **마이그레이션 210건** · 세션 4 |
| `02-rollback-attempt.txt` | 26.0 으로 내리자 `Running(0/1) → Error → CrashLoopBackOff`. **`liquibase.exception.ValidationFailedException`** | | `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 그대로 | | `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 이었다면 전면 장애다. 2. **StatefulSet 이 사고를 절반에서 멈춰줬다.** 한 파드가 남아 외부 200 을 유지했다. replica 1 이었다면 전면 장애다.
3. **실패한 기동은 스키마를 안 건드렸다.** 그래서 이미지만 되돌려도 복구됐다 — 이미 적용된 뒤였다면 DB 복구(D-1)가 필요하다. 3. **실패한 기동은 스키마를 안 건드렸다.** 그래서 이미지만 되돌려도 복구됐다 — 이미 적용된 뒤였다면 DB 복구(D-1)가 필요하다.
+17
View File
@@ -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. 재현 ## 11. 재현
```bash ```bash
+46 -3
View File
@@ -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:41 conntrack 삭제
11:44 cluster_size 2 → 1 ← 약 3분 뒤 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. 재현 절차 (명령어) ## 11. 재현 절차 (명령어)
```bash ```bash
+25 -1
View File
@@ -103,12 +103,19 @@ kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s
① 캐시를 가진 노드(keycloak-0)에서 refresh HTTP 500 ① 캐시를 가진 노드(keycloak-0)에서 refresh HTTP 500
② 캐시가 없는 노드(keycloak-1)에서 refresh HTTP 500 ② 캐시가 없는 노드(keycloak-1)에서 refresh HTTP 500
③ 새 로그인 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."} {"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 인 것이 중요하다 ### ① 이 500 인 것이 중요하다
**캐시에 세션을 들고 있어도 refresh 는 실패한다.** **캐시에 세션을 들고 있어도 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. 재현 절차 (명령어) ## 9. 재현 절차 (명령어)
```bash ```bash
+36 -2
View File
@@ -181,8 +181,23 @@ database system is ready to accept connections
★ p5XybeQIYmAs818gO4Vl_5ea ★ p5XybeQIYmAs818gO4Vl_5ea
``` ```
**약 2.6% 유실.** 초당 19건 정도 로그인하던 중이었으므로 **약 2.6% 유실.**
**대략 마지막 0.2초 분량**이다 — `wal_writer_delay` 기본값(200ms)과 맞는다.
처음 이 문서는 *"초당 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. 재현 절차 (명령어) ## 8. 재현 절차 (명령어)
```bash ```bash
+30 -1
View File
@@ -24,7 +24,7 @@
| 외부 응답 | **503** | **000** (연결 자체가 안 됨) | | 외부 응답 | **503** | **000** (연결 자체가 안 됨) |
| `kubectl` | 정상 | **불통** | | `kubectl` | 정상 | **불통** |
| 살아 있는 워크로드 | keycloak-1 (하지만 DB 없음) | **keycloak-0 은 계속 돌고 있다** | | 살아 있는 워크로드 | keycloak-1 (하지만 DB 없음) | **keycloak-0 은 계속 돌고 있다** |
| 복구 시간 | **60초** | **60초** | | **`virsh start` 이후** 복구 | **60초** | **60초** |
**둘 다 전면 장애**지만 이유가 다르다. 4a 는 **DB 가 같이 죽어서**, 4b 는 **둘 다 전면 장애**지만 이유가 다르다. 4a 는 **DB 가 같이 죽어서**, 4b 는
**들어갈 길이 없어서**다. **들어갈 길이 없어서**다.
@@ -204,6 +204,15 @@ virsh start kc-lab-2
**60초.** 사람 개입 없이 전부 제자리로 돌아왔다. **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`) ## 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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
@@ -137,6 +137,16 @@ kubectl -n keycloak-lab logs keycloak-0 --since=20m | grep ISPN000094 | awk '$2
suspected = 0 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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
+26 -1
View File
@@ -130,10 +130,14 @@ qdisc netem 30: parent 1:3 limit 1000 delay 200ms
``` ```
=== 두 노드 지연 비교 (기준선: k0=70ms k1=66ms) === === 두 노드 지연 비교 (기준선: 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-1 평균 1872 ms 최대 1887 ms ← 28배
``` ```
> **대조군도 변했다** — keycloak-0 은 기준선 70ms 에서 41ms 로 **41% 빨라졌다.**
> 주입과 무관한 변동(JIT 워밍업, 캐시)이며, **"영향 없음" 이라고 쓴 것은
> 부정확했다.** 다만 keycloak-1 의 28배 증가와는 자릿수가 달라 결론은 유지된다.
### 왜 200ms 가 1,872ms 가 되는가 ### 왜 200ms 가 1,872ms 가 되는가
A-0 에서 잡은 로그인 트랜잭션의 SQL 이 답이다. A-0 에서 잡은 로그인 트랜잭션의 SQL 이 답이다.
@@ -156,6 +160,10 @@ COMMIT
200 ms × 9 왕복 ≈ 1,800 ms 실측 1,872 ms 200 ms × 9 왕복 ≈ 1,800 ms 실측 1,872 ms
``` ```
> **9 는 SQL 목록을 센 것이고 패킷을 추적한 값이 아니다.** 자릿수가 맞는다는
> 것까지가 이 계산이 말할 수 있는 범위이며, **왕복 수를 확정하려면
> `tc -s` 나 패킷 캡처가 필요하다.**
> **네트워크 지연은 왕복 횟수만큼 증폭된다.** > **네트워크 지연은 왕복 횟수만큼 증폭된다.**
> "DB 가 200ms 느려졌다"는 "애플리케이션이 200ms 느려졌다"가 아니다. > "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. 재현 절차 (명령어) ## 8. 재현 절차 (명령어)
```bash ```bash
+17
View File
@@ -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. 재현 절차 (명령어) ## 8. 재현 절차 (명령어)
```bash ```bash
+22 -1
View File
@@ -56,7 +56,14 @@ statefulset.apps/keycloak restarted
200 200 200 200 partitioned roll out complete: 2 new pods have been updated... 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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
+16
View File
@@ -283,6 +283,22 @@ kubectl exec <pod> -- 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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
+16
View File
@@ -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. 재현 절차 (명령어) ## 7. 재현 절차 (명령어)
```bash ```bash
+32 -1
View File
@@ -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 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 을 두 벌 제공한다. 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. 재현 절차 (명령어) ## 8. 재현 절차 (명령어)
```bash ```bash
@@ -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. 재현 — 진짜 동시성을 만든다 ## 2. 재현 — 진짜 동시성을 만든다
B-2 에서 토큰이 PostgreSQL 로 공유되므로 두 replica 가 같은 항목을 본다. B-2 에서 토큰이 PostgreSQL 로 공유되므로 두 replica 가 같은 항목을 본다.
@@ -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. 재현 절차 (명령어) ## 7. 재현 절차 (명령어)
```bash ```bash
@@ -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. 재현 절차 (명령어) ## 5. 재현 절차 (명령어)
```bash ```bash
+14
View File
@@ -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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
@@ -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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
+23 -1
View File
@@ -59,10 +59,15 @@ DB 를 직접 지우고 Keycloak 을 재시작해야 했다 — **캐시 때문
(A-1 에서 확인한 대로, DB 를 직접 지워도 캐시는 남는다). (A-1 에서 확인한 대로, DB 를 직접 지워도 캐시는 남는다).
``` ```
Keycloak 온라인 세션: 0 Keycloak 온라인 세션: 4 ← 초기화가 안 먹었다
app1 HTTP 200 / app2 HTTP 200 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 가 동작한다 ## 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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
+23
View File
@@ -78,6 +78,14 @@ kcadm.sh update clients/<id> -r keycloak-patterns \
backchannel.logout.url = https://app1.hyeonworks.com/logout/connect/back-channel/keycloak 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. 재현 절차 (명령어) ## 7. 재현 절차 (명령어)
```bash ```bash
+16 -2
View File
@@ -25,7 +25,7 @@
| 복구 시간 | **1초** (`15:00:12 → 15:00:13`), **오류 0건** | | 복구 시간 | **1초** (`15:00:12 → 15:00:13`), **오류 0건** |
| 서비스 회복 | **재시작 없이 15초 이내** (`restarts=0`) | | 서비스 회복 | **재시작 없이 15초 이내** (`restarts=0`) |
| 데이터 일치 | **완전 일치** — realms 2 / clients 15 / users 2 / sessions 3 / authclients 1 | | 데이터 일치 | **완전 일치** — 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` 손실 | | **RPO** | **마지막 덤프 시점** + A-3 의 `synchronous_commit OFF` 손실 |
**그리고 예상 못 한 것 — 스키마를 통째로 지웠는데 서비스가 `200` 을 계속 냈다.** **그리고 예상 못 한 것 — 스키마를 통째로 지웠는데 서비스가 `200` 을 계속 냈다.**
@@ -169,7 +169,7 @@ volatile 이었다면 세션은 애초에 DB 에 없으므로 **복구해도 전
15:00:13 복구 완료 15:00:13 복구 완료
~15:00:28 서비스 정상 확인 ~15:00:28 서비스 정상 확인
RTO ≈ 30초 (이 규모에서는 대부분이 사람의 판단 시간이다) RTO = 41초 (이 규모에서는 대부분이 사람의 판단 시간이다)
``` ```
### RPO 는 두 겹이다 ### 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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
+29
View File
@@ -7,6 +7,20 @@
선행: [`D-1`](experiment-d1-backup-restore.md) — **백업이 전제다** · 선행: [`D-1`](experiment-d1-backup-restore.md) — **백업이 전제다** ·
[`A-8`](experiment-a8-rolling-restart.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. 결론부터 ## 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. 재현 절차 (명령어) ## 7. 재현 절차 (명령어)
```bash ```bash
+13
View File
@@ -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. 재현 절차 (명령어) ## 6. 재현 절차 (명령어)
```bash ```bash
+12
View File
@@ -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. 재현 절차 (명령어) ## 5. 재현 절차 (명령어)
```bash ```bash
@@ -247,3 +247,18 @@ sudo certbot renew --force-renewal
**0.2초 간격으로 재는 이유** — 2절에서 1초 간격으로는 전환을 거의 못 잡았다. **0.2초 간격으로 재는 이유** — 2절에서 1초 간격으로는 전환을 거의 못 잡았다.
nginx reload 는 그보다 훨씬 짧을 것이므로 해상도를 올려야 한다. 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).
+22 -2
View File
@@ -38,8 +38,8 @@
| `diagrams/*.svg` | 실험별 구조도 12개 | | `diagrams/*.svg` | 실험별 구조도 12개 |
| `evidence/*/*.png` | Grafana · 브라우저 스크린샷 | | `evidence/*/*.png` | Grafana · 브라우저 스크린샷 |
**B층(B-1·B-3·B-4·B-5)에는 Grafana 증거가 없다.** 안 찍은 것이 아니라 **스크린샷이 없는 실험은 11개다**`A-3 A-7 B-3 B-4 B-5 B-6 C-2 D-1 D-3 D-4 후속`.
Prometheus 가 Redis·BFF·PostgreSQL 을 긁지 않다 — 그중 B층은 **Prometheus 가 Redis·BFF·PostgreSQL 을 긁지 않** 만들 수가 없다 —
[`followup/04-observability-gap.txt`](evidence/followup/04-observability-gap.txt) 에 측정해 두었다. [`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개 대조 | | [`open-questions-coverage.md`](open-questions-coverage.md) | 공개 열린 질문 4개 대조 |
| [`session-lab-concepts.md`](session-lab-concepts.md) | 등장 개념 전체 (13층) | | [`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 에 명시했다.
## 반복해서 배운 것 — 주입이 안 걸린 사례 ## 반복해서 배운 것 — 주입이 안 걸린 사례
**아홉 번 있었다. 전부 "아무 일도 없었다" 로 보였다.** **아홉 번 있었다. 전부 "아무 일도 없었다" 로 보였다.**