실측 기록

직접 잰 것

실측 기록

재현 조건을 만들고, 무너지는 순간을 계측하고, 고친 뒤 같은 조건에서 다시 잰 기록입니다. 설계 설명이 아니라 표가 결론이고, 각 저장소에 원본 결과와 재실행 명령이 있습니다. 실패한 가설과 측정 도구의 실수도 함께 적습니다.

저장소 4 · 실험 글 16

spring-ops-lab

저장소와 원본 결과

Spring Boot 서버가 무너지는 방식

  1. 느린 업스트림 하나가 그것을 호출하지도 않는 엔드포인트를 죽이는가

    무너지는 Spring 서버 1 - CPU는 놀고 있는데 API가 느리다: 업스트림 하나가 무관한 엔드포인트를 죽이는 과정을 thread dump 세 장으로 읽기

    `/api/fast` p50 2.9ms → 30초(클라이언트 타임아웃), 실패 66.7%. 요청 스레드 200개 전부가 소켓 읽기에 정지. 조치 후 같은 조건에서 p50 2.9ms·실패 0%

  2. pause가 300배 짧아지면 p99는 얼마나 좋아지는가

    G1과 ZGC가 p99에 남기는 차이 - 300배 짧은 pause가 꼬리의 11%를 가져갔다

    ZGC의 최악 pause는 0.07ms, G1은 24.13ms인데 피해자 p99는 13.62 → 12.07ms로 11%만 좋아졌다. 정작 이득은 할당하는 엔드포인트가 37%(17.95 → 11.26ms)로 더 컸고, 가장 느린 요청을 pause로 설명할 수 있는 수집기는 Parallel뿐이었다

  3. 핀닝은 얼마를 앗아가고 스레드 수가 상한이 아니면 무엇이 상한인가

    가상 스레드를 켜서 37배 느려진 엔드포인트 - 핀닝과 상한 없는 admission을 재다

    경합이 전혀 없는 synchronized 하나가 가상 스레드에서 완료 14,398건을 392건으로 만들었다. 동시 처리는 100에서 1로, 캐리어 수와 같아졌다. 2부에서 세 조건의 성공 처리량은 모두 풀 용량이었고 달라진 것은 실패의 값이다(성공 p50 2,003 → 103ms)

data-ops-lab

저장소와 원본 결과

데이터 계층이 틀리고 느려지는 방식

  1. 어느 이상 현상이 실제로 나고

    격리 수준의 이상 현상을 직접 만들기 - 같은 REPEATABLE READ에서 PostgreSQL은 중단시키고 MySQL은 조용히 덮어썼다

    같은 REPEATABLE READ에서 lost update가 갈렸다. PostgreSQL 20/20 직렬화 실패, MySQL 20/20 조용히 덮어씀. SERIALIZABLE 비용은 22~36ms 대 3,051ms

  2. 커넥션 풀은 실제로 무엇을 아끼고 언제 키우면 손해인가

    Postgres 커넥션 하나의 비용 - 풀을 키우면 대기가 사라지는 게 아니라 볼 수 없는 곳으로 옮겨간다

    커넥션 열기 5.48ms 대 질의 0.23ms, 유휴 커넥션 하나가 1.5MB. 처리량은 풀 10에서 정점이고 150에서 15% 낮은데, 그 구간에서 획득 대기는 89.3 → 38.8ms로 줄고 총 p50은 60.2 → 69.8ms로 늘었다

  3. 캐시가 만료되는 순간 몇 명이 원본까지 가고 방어는 무엇을 대가로 내는가

    캐시 스탬피드와 single flight - 200명 중 125명이 원본까지 갔고, 예외를 삼키자 실패가 가장 빠른 요청이 됐다

    방어가 없으면 만료마다 200명 중 125.5명이 원본에 도달했다. 락 기반 single flight는 1.5회로 84배 줄이면서 처리량과 p99도 가장 좋았고, XFetch는 beta를 재계산 비용보다 키우면 오히려 나빠졌다

  4. 어떤 명령이 Redis의 단일 스레드를 얼마나 막는가

    Redis 단일 스레드가 멈추는 순간 - SCAN이 3.7배 오래 걸리면서 아무도 막지 않고, 120ms 멈춤이 p99에 안 잡히는 이유

    `SCAN`은 `KEYS`보다 3.7배 오래 걸리면서 피해자 p99를 기준선 이하로 유지했다. 100만 원소 리스트의 `DEL`은 1.3ms지만 100만 멤버 Set은 121.4ms로 약 93배였고, 그 120ms 멈춤은 p99에 잡히지 않고 max에만 남았다

  5. 순서를 지키는 설계와 느린 것을 격리하는 설계는 어디에서 갈라지는가

    순서와 격리는 같은 자원을 반대로 당긴다 - 핫 키가 워커 하나를 넘는 순간 세 설계가 갈라지는 지점

    핫 키가 워커 하나분을 넘기 전에는 세 설계가 같았다. 넘는 순간 파티션의 피해자 p99가 116ms → 22,894ms로 발산했고, 훔치기는 그것을 507ms로 45배 줄이면서 순서 위반 188건을 만들었다

  6. 옵티마이저가 인덱스를 안 쓰는 것인가 못 쓰는 것인가

    인덱스가 선택되지 않는 순간 - 컬럼에 캐스팅 하나가 3,000배, 그리고 추정이 31.7배 틀려도 계획이 안 바뀌는 경우

    컬럼에 붙인 암묵적 형변환이 0.07ms를 208.7ms로, 추정은 실제의 417배. 낡은 통계는 31.7배 틀렸는데도 계획이 안 바뀌었다

ParityPay

저장소와 원본 결과

장애 아래에서 금융 불변조건이 지켜지는가

  1. 같은 지갑 경합의 원인은 어디에 있는가

    ParityPay로 검증하는 결제 정합성 2 - 잠금을 필요 이상으로 오래 쥐고 있었다: 추론을 측정으로 바꾼 기록

    잠금 보유 17ms 중 DB 실행은 2.31ms. 순서를 바꿔 1ms로 줄이자 처리량 1.75배, p95 274ms → 144ms

  2. 락 lease가 트랜잭션보다 먼저 끝나면 정말 정합성이 깨지는가

    ParityPay로 검증하는 결제 정합성 10 - 락 lease가 트랜잭션보다 먼저 끝나면 정말 정합성이 깨지는가: SETNX 락, Watchdog, fencing token, 그리고 락 없는 구조의 실측

    락은 3회 모두 깨졌다(소유 겹침 1,697~1,714쌍, 초과 승인 340~350건). 락 없는 조건부 UPDATE는 여섯 번 전부 승인 정확히 240·drift 0, 처리량은 2배

  3. Kafka에서 중복·유실·순서 역전을 직접 만들면 무엇이 막히는가

    ParityPay로 검증하는 결제 정합성 8 - Kafka에서 중복·유실·순서 역전을 직접 만들어 보기: 멱등 소비자와 Outbox가 막는 것과 못 막는 것

    유실을 가른 것은 커밋 방식이 아니라 처리와 커밋의 순서였다. `acks=1·0`은 발행 배치 단위로 잃고 `acks=all`은 0건. poison 1건이 파티션을 1.3~1.6초 멈추고 조용히 버려졌다

  4. 느린 외부기관 앞에서 결제 서버를 무엇이 지키는가

    ParityPay로 검증하는 결제 정합성 9 - 느린 기관 앞에서 결제 서버를 지키기: 타임아웃, 재시도, 차단기, 리미터, 벌크헤드가 각각 무엇을 막는가

    플랫폼 스레드 200개는 3회 모두 23.7~24.0초에 고갈. 재시도 3회는 기관 요청을 정확히 3.00배로 만들었고 지터는 봉우리를 낮추지 못했다. 벌크헤드 50은 무관한 API를 p95 10ms로 지켰다

  5. 부하·장애 실험이 문서와 코드가 못 찾은 것을 찾는가

    ParityPay로 검증하는 결제 정합성 4 - 실험 27종이 찾아낸 결함 12건: 문서와 코드를 읽어서 나온 것은 하나도 없었다

    결함 12건 중 6건은 문서에 '대비되어 있다'고 적혀 있던 것이었고, 1건은 테스트가 있었는데 통과했다

monticker

저장소와 원본 결과

실시간 시세 파이프라인의 순서와 지연

  1. WebSocket과 폴링은 클라이언트 1만에서 무엇이 갈리는가

    WebSocket push와 REST polling을 클라이언트 1만 개에서 재다 — 지연과 서버 비용, Kafka 정지 45초, 느린 소비자 25개

    WebSocket은 폴링의 CPU 1/10~1/20. 폴링은 5,300 req/s에서 천장을 쳐 요청의 95%를 버렸다. 느린 연결 25개가 모든 클라이언트를 p99 19초 멈췄고 발행 스레드 풀을 늘려 196ms로

  2. 키가 같은데도 순서가 깨지는 이유는 무엇인가

    market.ticks 파티션 × 컨슈머 격자 — 키가 같아도 순서가 깨진 이유, 크래시 하나가 시세 전체를 멈춘 이유, 파티션 이웃이 인질이 되는 순간

    27회 중 9회에서 순서가 깨졌고 원인은 게이트웨이의 `acks=0`이었다. 고치자 45회 위반 0. 핫 종목이 스레드의 90%를 먹으면 같은 파티션 종목이 p99 13초로 함께 밀린다