직접 잰 것
질문 하나, 숫자 하나
재현 조건을 만들고, 무너지는 순간을 계측하고, 고친 뒤 같은 조건에서 다시 잰 기록입니다. 설계 설명이 아니라 표가 결론이고, 각 저장소에 원본 결과와 재실행 명령이 있습니다. 실패한 가설과 측정 도구의 실수도 함께 적습니다.
- spring-ops-lab Spring Boot 서버가 무너지는 방식 느린 업스트림 하나가 그것을 호출하지도 않는 엔드포인트를 죽이는가 `/api/fast` p50 2.9ms → 30초(클라이언트 타임아웃), 실패 66.7%. 요청 스레드 200개 전부가 소켓 읽기에 정지. 조치 후 같은 조건에서 p50 2.9ms·실패 0% 무너지는 Spring 서버 1 - CPU는 놀고 있는데 API가 느리다: 업스트림 하나가 무관한 엔드포인트를 죽이는 과정을 thread dump 세 장으로 읽기실패율 66.7% 0%
- spring-ops-lab Spring Boot 서버가 무너지는 방식 가설이 틀림 pause가 300배 짧아지면 p99는 얼마나 좋아지는가 ZGC의 최악 pause는 0.07ms, G1은 24.13ms인데 피해자 p99는 13.62 → 12.07ms로 11%만 좋아졌다. 정작 이득은 할당하는 엔드포인트가 37%(17.95 → 11.26ms)로 더 컸고, 가장 느린 요청을 pause로 설명할 수 있는 수집기는 Parallel뿐이었다 G1과 ZGC가 p99에 남기는 차이 - 300배 짧은 pause가 꼬리의 11%를 가져갔다피해자 p99 13.62ms 12.07ms
- spring-ops-lab Spring Boot 서버가 무너지는 방식 핀닝은 얼마를 앗아가고 스레드 수가 상한이 아니면 무엇이 상한인가 경합이 전혀 없는 synchronized 하나가 가상 스레드에서 완료 14,398건을 392건으로 만들었다. 동시 처리는 100에서 1로, 캐리어 수와 같아졌다. 2부에서 세 조건의 성공 처리량은 모두 풀 용량이었고 달라진 것은 실패의 값이다(성공 p50 2,003 → 103ms) 가상 스레드를 켜서 37배 느려진 엔드포인트 - 핀닝과 상한 없는 admission을 재다완료 건수 14,398 392
- spring-ops-lab Spring Boot 서버가 무너지는 방식 가설이 틀림 CPU를 거의 안 쓰는 요청의 지연은 어디에 있고 CPU 프로파일러는 그것을 찾아 주는가 CPU 프로파일의 1위는 핸들러 샘플의 56.2%를 차지한 공용 구간이었는데 그것은 159.60ms 요청의 1.14ms였다. 그 대기를 완전히 없앴더니 평균이 108.45 → 110.52ms로 아무 일도 없었다. wall-clock이 지목한 항을 겹치자 평균 32% 감소, 처리량 47% 증가였고, 표는 그 크기를 1.9ms 오차로 미리 맞혔다 CPU 프로파일러가 1순위로 지목한 코드를 고쳤더니 아무 일도 없었다CPU 1위 구간 제거 후 평균 108.45ms 110.52ms
- data-ops-lab 데이터 계층이 틀리고 느려지는 방식 어느 이상 현상이 실제로 나고, 엔진은 어떻게 거절하는가 같은 REPEATABLE READ에서 lost update가 갈렸다. PostgreSQL 20/20 직렬화 실패, MySQL 20/20 조용히 덮어씀. SERIALIZABLE 비용은 22~36ms 대 3,051ms 격리 수준의 이상 현상을 직접 만들기 - 같은 REPEATABLE READ에서 PostgreSQL은 중단시키고 MySQL은 조용히 덮어썼다lost update, 20회 PG 20/20 중단 · MySQL 20/20 덮어씀
- data-ops-lab 데이터 계층이 틀리고 느려지는 방식 커넥션 풀은 실제로 무엇을 아끼고 언제 키우면 손해인가 커넥션 열기 5.48ms 대 질의 0.23ms, 유휴 커넥션 하나가 1.5MB. 처리량은 풀 10에서 정점이고 150에서 15% 낮은데, 그 구간에서 획득 대기는 89.3 → 38.8ms로 줄고 총 p50은 60.2 → 69.8ms로 늘었다 Postgres 커넥션 하나의 비용 - 풀을 키우면 대기가 사라지는 게 아니라 볼 수 없는 곳으로 옮겨간다풀 10 → 150, 총 p50 60.2ms 69.8ms
- data-ops-lab 데이터 계층이 틀리고 느려지는 방식 캐시가 만료되는 순간 몇 명이 원본까지 가고 방어는 무엇을 대가로 내는가 방어가 없으면 만료마다 200명 중 125.5명이 원본에 도달했다. 락 기반 single flight는 1.5회로 84배 줄이면서 처리량과 p99도 가장 좋았고, XFetch는 beta를 재계산 비용보다 키우면 오히려 나빠졌다 캐시 스탬피드와 single flight - 200명 중 125명이 원본까지 갔고, 예외를 삼키자 실패가 가장 빠른 요청이 됐다만료마다 원본 도달 (200명 중) 125.5 1.5
- data-ops-lab 데이터 계층이 틀리고 느려지는 방식 어떤 명령이 Redis의 단일 스레드를 얼마나 막는가 `SCAN`은 `KEYS`보다 3.7배 오래 걸리면서 피해자 p99를 기준선 이하로 유지했다. 100만 원소 리스트의 `DEL`은 1.3ms지만 100만 멤버 Set은 121.4ms로 약 93배였고, 그 120ms 멈춤은 p99에 잡히지 않고 max에만 남았다 Redis 단일 스레드가 멈추는 순간 - SCAN이 3.7배 오래 걸리면서 아무도 막지 않고, 120ms 멈춤이 p99에 안 잡히는 이유100만 멤버 Set의 DEL 121.4ms · 약 93배
- data-ops-lab 데이터 계층이 틀리고 느려지는 방식 순서를 지키는 설계와 느린 것을 격리하는 설계는 어디에서 갈라지는가 핫 키가 워커 하나분을 넘기 전에는 세 설계가 같았다. 넘는 순간 파티션의 피해자 p99가 116ms → 22,894ms로 발산했고, 훔치기는 그것을 507ms로 45배 줄이면서 순서 위반 188건을 만들었다 순서와 격리는 같은 자원을 반대로 당긴다 - 핫 키가 워커 하나를 넘는 순간 세 설계가 갈라지는 지점핫 키 피해자 p99 22,894ms 507ms
- data-ops-lab 데이터 계층이 틀리고 느려지는 방식 옵티마이저가 인덱스를 안 쓰는 것인가 못 쓰는 것인가 컬럼에 붙인 암묵적 형변환이 0.07ms를 208.7ms로, 추정은 실제의 417배. 낡은 통계는 31.7배 틀렸는데도 계획이 안 바뀌었다 인덱스가 선택되지 않는 순간 - 컬럼에 캐스팅 하나가 3,000배, 그리고 추정이 31.7배 틀려도 계획이 안 바뀌는 경우형변환 하나 0.07ms 208.7ms
- jvm-bench-lab 측정 자체가 틀리는 방식 손으로 만든 마이크로벤치마크는 어느 방향으로 몇 배 틀리고 언제는 안 틀리는가 결과를 지우는 결함은 유리한 방향으로 틀렸다. 제거 21.4배, 상수 접힘 12.3배. 일을 더하는 결함은 반대 방향으로 워밍업 없음 19.9배, 반복마다 타이머 14.8배. 그런데 620ns짜리 배열 합에서는 차이가 1.5%로 사라졌다 직접 만든 마이크로벤치마크는 얼마나 틀리는가 - 결함 넷을 하나씩 재봤다결과 제거 결함 21.4배 틀림
- ParityPay 장애 아래에서 금융 불변조건이 지켜지는가 같은 지갑 경합의 원인은 어디에 있는가 잠금 보유 17ms 중 DB 실행은 2.31ms. 순서를 바꿔 1ms로 줄이자 처리량 1.75배, p95 274ms → 144ms ParityPay로 검증하는 결제 정합성 2 - 잠금을 필요 이상으로 오래 쥐고 있었다: 추론을 측정으로 바꾼 기록잠금 보유 17ms 1ms
- ParityPay 장애 아래에서 금융 불변조건이 지켜지는가 락 lease가 트랜잭션보다 먼저 끝나면 정말 정합성이 깨지는가 락은 3회 모두 깨졌다(소유 겹침 1,697~1,714쌍, 초과 승인 340~350건). 락 없는 조건부 UPDATE는 여섯 번 전부 승인 정확히 240·drift 0, 처리량은 2배 ParityPay로 검증하는 결제 정합성 10 - 락 lease가 트랜잭션보다 먼저 끝나면 정말 정합성이 깨지는가: SETNX 락, Watchdog, fencing token, 그리고 락 없는 구조의 실측초과 승인 340~350건 0
- ParityPay 장애 아래에서 금융 불변조건이 지켜지는가 Kafka에서 중복·유실·순서 역전을 직접 만들면 무엇이 막히는가 유실을 가른 것은 커밋 방식이 아니라 처리와 커밋의 순서였다. `acks=1·0`은 발행 배치 단위로 잃고 `acks=all`은 0건. poison 1건이 파티션을 1.3~1.6초 멈추고 조용히 버려졌다 ParityPay로 검증하는 결제 정합성 8 - Kafka에서 중복·유실·순서 역전을 직접 만들어 보기: 멱등 소비자와 Outbox가 막는 것과 못 막는 것acks=all 유실 0건
- ParityPay 장애 아래에서 금융 불변조건이 지켜지는가 느린 외부기관 앞에서 결제 서버를 무엇이 지키는가 플랫폼 스레드 200개는 3회 모두 23.7~24.0초에 고갈. 재시도 3회는 기관 요청을 정확히 3.00배로 만들었고 지터는 봉우리를 낮추지 못했다. 벌크헤드 50은 무관한 API를 p95 10ms로 지켰다 ParityPay로 검증하는 결제 정합성 9 - 느린 기관 앞에서 결제 서버를 지키기: 타임아웃, 재시도, 차단기, 리미터, 벌크헤드가 각각 무엇을 막는가벌크헤드 50, 무관한 API p95 10ms
- ParityPay 장애 아래에서 금융 불변조건이 지켜지는가 지터는 언제 값을 하고 무엇을 하지 않는가 폭주를 만들자 지터가 재시도 봉우리를 100ms당 141건에서 38건으로 3.7배 낮췄다. 총량은 두 조건 모두 정확히 3.00배로 같다. 같은 데이터를 1초 눈금으로 보면 둘 다 150이고, 앞선 실험이 쓴 자가 현상보다 굵었던 것이다 ParityPay로 검증하는 결제 정합성 11 - 지터는 언제 값을 하는가: 봉우리를 만들어야 보이는 것재시도 봉우리 (100ms당) 141 38
- ParityPay 장애 아래에서 금융 불변조건이 지켜지는가 실험이 못 밟는 경로가 있는가, 있다면 무엇인가 TLC가 '청구됐는데 실패로 확정' 상태에 5단계 만에 도달했다. 연속 없음 임계치를 2에서 8로 올려도 위반은 남고 탐색만 깊어진다. 실험 41종이 못 찾은 이유는 운이 아니라 시험 대역에 '받았고 승인할 것이고 아직 기록하지 않은' 상태가 없어서였다 ParityPay로 검증하는 결제 정합성 12 - 실험 41종이 못 밟는 경로를 모델 검사가 5단계 만에 찾았다위반 상태 도달 5단계
- ParityPay 장애 아래에서 금융 불변조건이 지켜지는가 부하·장애 실험이 문서와 코드가 못 찾은 것을 찾는가 결함 12건 중 6건은 문서에 '대비되어 있다'고 적혀 있던 것이었고, 1건은 테스트가 있었는데 통과했다 ParityPay로 검증하는 결제 정합성 4 - 실험 27종이 찾아낸 결함 12건: 문서와 코드를 읽어서 나온 것은 하나도 없었다문서에 '대비됨'이던 결함 6 / 12건
- monticker 실시간 시세 파이프라인의 순서와 지연 WebSocket과 폴링은 클라이언트 1만에서 무엇이 갈리는가 WebSocket은 폴링의 CPU 1/10~1/20. 폴링은 5,300 req/s에서 천장을 쳐 요청의 95%를 버렸다. 느린 연결 25개가 모든 클라이언트를 p99 19초 멈췄고 발행 스레드 풀을 늘려 196ms로 WebSocket push와 REST polling을 클라이언트 1만 개에서 재다 — 지연과 서버 비용, Kafka 정지 45초, 느린 소비자 25개느린 연결 25개, p99 19초 196ms
- monticker 실시간 시세 파이프라인의 순서와 지연 키가 같은데도 순서가 깨지는 이유는 무엇인가 27회 중 9회에서 순서가 깨졌고 원인은 게이트웨이의 `acks=0`이었다. 고치자 45회 위반 0. 핫 종목이 스레드의 90%를 먹으면 같은 파티션 종목이 p99 13초로 함께 밀린다 market.ticks 파티션 × 컨슈머 격자 — 키가 같아도 순서가 깨진 이유, 크래시 하나가 시세 전체를 멈춘 이유, 파티션 이웃이 인질이 되는 순간순서 위반 9/27회 0/45회