글
지금까지 작성한 글들을 모아봤습니다.
-
[JPA + Spring Mastery 08] 트랜잭션 분리 패턴 — Saga / Outbox / REQUIRES_NEW, 학술 기원부터 EXP-09b 9 시나리오 실측까지
트랜잭션 안에서 외부 API 호출하지 마라 - 격언은 들어봤지만 어떻게 풀어야 하는지는 잘 다뤄지지 않습니다. 본 글은 PROPAGATION 7종의 정확한 의미부터 2PC (XA) 의 한계, Garcia-Molina 의 1987년 Sagas 논문, Pat Helland 의 CIDR 2005 Data on the Outside, Vogels 의 ACM Queue 2008 Eventually Consistent 까지 학술 기원을 짚고, 토스 SLASH24 SAGA, 29CM/리디 Outbox 한국 운영 사례를 거쳐, EXP-09b 9 시나리오 실측 매트릭스 - 패턴 A/B/C × OFF/DB_FAIL/EXT_FAIL - 로 검증한 기록입니다. 결제 도메인은 Saga, 알림은 Outbox, 캐시류만 단순 분리 - 이 매핑의 학술 + 운영 + 실측 3박자.
-
[JPA + Spring Mastery 07] Spring AOP self-invocation — @Transactional 이 작동하지 않는 진짜 이유, TransactionInterceptor.invoke 6단계까지 분해
낙관락 측정에서 successes=100인데 잔액이 그대로 100. 코드 logic 은 멀쩡한데 잔액이 안 줄어들었습니다. 원인은 같은 클래스 내부 호출이 Spring AOP 프록시를 우회한 것 — @Transactional 이 발동하지 않아 flush 가 일어나지 않았습니다. 이 글은 TransactionInterceptor.invoke 의 6단계 / AOP Alliance MethodInvocation.proceed() 가 raw target 을 호출하는 위치 / 같은 함정에 걸리는 6 어노테이션 (@Async / @Cacheable / @Validated / @Retryable / @PreAuthorize) / 4 가지 워크어라운드 (분리 빈 / getBean(self) / AopContext.currentProxy / AspectJ weaving) 를 Spring 6 / Hibernate 6 소스 라인 단위로 분해한 기록.
-
프로덕션이 'Check failed: node->IsInUse()' 한 줄로 죽었습니다 (1) — V8 GlobalHandles 해부
프로덕션 컨테이너가 평범한 새벽에 V8 fatal 한 줄을 남기고 죽었습니다. 어플 코드는 단 한 줄도 안 들어간 스택, Datadog 프로파일러의 마지막 호출, 그리고 V8 내부의 'Check failed: node->IsInUse()'. 이게 무슨 뜻인지부터 풀어봅니다 — 1편: 사건 재현과 V8 GlobalHandles의 해부.
-
MySQL 크레딧 차감 락 4종 비교 — 비관락 180ms / 100% 정확, 그리고 측정 도중 발견한 self-invocation 함정
잔액 100 인 계정에서 100 worker 가 동시에 1씩 차감하는 흔한 시나리오. 4 락 (낙관/비관/MySQL GET_LOCK/Redisson) 의 결과가 모두 다릅니다 — 비관락 180ms / 100% / 잔액 0, 낙관락 549ms (contention 시 재시도 폭증), GET_LOCK 5015ms (advisory lock 의 cost), Redisson 53/100 (단일 인스턴스 한계). 그리고 측정 도중 발견한 self-invocation 함정 — successes=100 인데 잔액 그대로 유지된 case. JPA / Spring 의 진짜 함정은 logic 이 아니라 AOP proxy 우회였습니다. GET_LOCK 의 connection-bound 함정 4 시나리오까지 직접 시연한 기록.