2026-09-28-TIL: 통과하는 테스트가 무엇을 잡는가 TIL 지난 TIL 이후를 묶어서 정리한다. 기능을 새로 넣기보다 이미 만든 것을 검증하고, 처음 보는 사람이 읽을 수 있게 정리한 작업이 많았다. 코드 작업은 대부분 Claude Code 세션에서 진행했고, neon-diary만 Codex로 진행했다. 요즘 한 작업 proofu: 생성에서 제출, 내보내기까지 proofu는 이력서 생성 흐름의 뒷부분... 2026/09/28 2026-TIL
ParityPay로 검증하는 결제 정합성 12 - 실험 41종이 못 밟는 경로를 모델 검사가 5단계 만에 찾았다 개인 프로젝트 Notes 이 시리즈는 실험으로 결함을 찾아 왔다. 41종을 돌려 14건을 찾았고 9편에서 격리 장치를 채택했으며 11편에서 지터를 다시 쟀다. 그런데 실험에는 구조적인 한계가 하나 있다. 시각이 맞아떨어지는 순간에만 드러나는 경로는 우연에 기대서만 밟힌다. 41종을 백 번 돌려도 그 순간이 안 오면 안 온다. 그리고 안 왔다는 것과 없다는 것을 구별할 방법이... 2026/09/26 ParityPay ParityPay로 검증하는 결제 정합성 12편
ParityPay로 검증하는 결제 정합성 11 - 지터는 언제 값을 하는가: 봉우리를 만들어야 보이는 것 개인 프로젝트 Notes 9편은 지터(재시도 대기 시간에 섞는 난수)가 총량을 줄이지 않았고 봉우리도 낮추지 못했다고 적은 뒤, 그 이유를 이렇게 추정했다. 지터가 값을 하는 조건은 동기화된 폭주, 장애 복구 직후 수천 클라이언트가 같은 순간에 재시도하는 것인데, 이 부하는 초당 10건이 고르게 도착하는 것이라 펼칠 봉우리가 없었다. 가설을 적어 두고 재지 않은 것... 2026/09/25 ParityPay ParityPay로 검증하는 결제 정합성 11편
ParityPay로 검증하는 결제 정합성 10 - 락 lease가 트랜잭션보다 먼저 끝나면 정말 정합성이 깨지는가: SETNX 락, Watchdog, fencing token, 그리고 락 없는 구조의 실측 개인 프로젝트 Notes 2편에서 잔액 차감에 SELECT FOR UPDATE와 조건부 원자 UPDATE를 비교해 후자를 골랐다고 썼다. ADR-004의 대안 목록에는 하나가 더 있었다. 분산락. “DB 업데이트와 원자성이 자동 보장되지 않고 운영 실패 모드가 추가된다”는 이유로 뺐는데, 그 문장은 추론이지 측정이 아니었다. 이 질문은 개인적으로도 답해야 할 것이었다. 실무... 2026/09/24 ParityPay ParityPay로 검증하는 결제 정합성 10편
무너지는 Spring 서버 1 - CPU는 놀고 있는데 API가 느리다: 업스트림 하나가 무관한 엔드포인트를 죽이는 과정을 thread dump 세 장으로 읽기 실무 사례 Notes 계획에만 있고 아직 하지 않은 실험이 다섯 개 있었다. 이 글은 그중 첫 번째다. ParityPay 9편에서는 타임아웃 없는 외부 호출이 Tomcat 스레드 200개를 24초 만에 모두 묶어 버린다는 것을 계산으로 보였다. 다만 그 글은 한계 두 가지를 남겼다. 컨테이너 리소스 제한을 걸지 않았다는 것, 그리고 thread dump(JVM의 모든 스레... 2026/09/23 SpringOpsLab 무너지는 Spring 서버를 재현하고 고치기 1편
ParityPay로 검증하는 결제 정합성 9 - 느린 기관 앞에서 결제 서버를 지키기: 타임아웃, 재시도, 차단기, 리미터, 벌크헤드가 각각 무엇을 막는가 개인 프로젝트 Notes 3편에서 외부 승인 응답이 사라진 거래를 UNKNOWN으로 보존해 조회로 수렴시켰다. 그 글의 장애는 거래 하나의 응답이 사라지는 것이었다. 이 글의 장애는 다르다. 기관이 40초 동안 모든 요청에 30초씩 응답을 붙잡으면, 그동안 들어오는 결제 수백 건과 결제와 무관한 잔액 조회는 어떻게 되는가. 설계 문서에는 답이 절반만 있었다. 읽기 타임아웃이... 2026/09/23 ParityPay ParityPay로 검증하는 결제 정합성 9편
ParityPay로 검증하는 결제 정합성 8 - Kafka에서 중복·유실·순서 역전을 직접 만들어 보기: 멱등 소비자와 Outbox가 막는 것과 못 막는 것 개인 프로젝트 Notes 5편에서 at-least-once(유실은 없지만 중복은 있을 수 있는 전달)는 버그가 아니라 전제라고 썼고, 그 전제 위에서 멱등 소비자(같은 이벤트를 두 번 받아도 결과가 한 번 처리한 것과 같은 소비자)가 실제 중복 1,934건을 흡수하는 것을 봤다. 그런데 5편의 장애는 전부 우리가 만든 것이었다. 소비자 프로세스를 죽였고, 발행기를 네 대 띄웠... 2026/09/22 ParityPay ParityPay로 검증하는 결제 정합성 8편
ParityPay로 검증하는 결제 정합성 7 - 애플리케이션 코드를 믿지 않는 원장: 이중부기를 계정 체계, DB 제약, 속성 테스트로 강제하기 개인 프로젝트 Notes 1편에서 원장 불변조건 둘(차변=대변, 확정 원장 불변)을 PostgreSQL 트리거로 내렸다고 썼고, 6편에서 보정은 새 분개(journal entry, 한 거래를 차변과 대변 항목들로 나눠 적은 기록)로만 한다고 썼다. 두 글 모두 원장이 이미 있다는 전제 위에 있다. 이 글은 그 전제를 다룬다. 왜 잔액 컬럼이 아니라 이중부기인지, 계정 체계와 ... 2026/09/20 ParityPay ParityPay로 검증하는 결제 정합성 7편
CDC의 원리와 한계 - 로그 기반 변경 포착, 초기 스냅샷, 스키마 변경 Notes DB의 변경을 다른 시스템에 전달하는 방법으로 CDC가 널리 쓰인다. “애플리케이션 코드를 건드리지 않고 변경을 가져온다”는 점이 매력인데, 그 대가가 어디에 있는지는 덜 이야기된다. 원리를 보면 무엇이 공짜이고 무엇이 아닌지가 갈린다. 세 가지 방식 폴링은 updated_at > ?로 주기적으로 조회한다. 구현이 단순하고 DB 기능에 의... 2026/09/19 Database
ParityPay로 검증하는 결제 정합성 6 - 대사: "기관에 물어보지 못했다"와 "기관에 기록이 없다"는 다른 상태다 개인 프로젝트 Notes 3편은 외부 응답이 사라진 거래 하나를 어떻게 확정하는지에 관한 글이었다. 복구 작업이 기관에 물어보고, 답에 따라 성공·실패로 수렴시키고, 답을 못 들으면 사람에게 넘긴다. 그 절차가 끝나면 그 거래는 확정된 상태다. 그런데 확정됐다는 것과 맞다는 것은 다르다. 우리가 성공으로 확정한 거래가 기관에는 실패로 남아 있을 수 있고, 기관이 처리한 거래... 2026/09/19 ParityPay ParityPay로 검증하는 결제 정합성 6편