결제 흐름을 짜고 나서 "이 정도면 됐다" 싶었다.
@Transactional 하나가 전체를 감싸고, PG 호출이 실패하면 자동으로 롤백된다.
구조도 단순하고, 동작도 맞다.
k6를 돌리기 전까지는 그랬다.
DB 커넥션은 유한하다.
HikariCP 기본 풀 사이즈는 10이다.
트랜잭션이 커넥션을 들고 있는 동안 다른 요청은 기다린다.
평소엔 트랜잭션이 짧아서 문제가 없다. PG처럼 응답이 100~600ms씩 걸리는 외부 호출이 트랜잭션 안에 있으면 얘기가 다르다.
커넥션 10개가 전부 PG 응답을 기다리는 순간, 나머지 요청들은 줄을 선다.
k6로 80명의 가상 유저를 붙여봤다.
pool_timeout_rate가 7.02%였다. 100건 중 7건이 커넥션을 얻지 못하고 503으로 떨어지고 있었다.
트랜잭션을 나눴다.
PG 호출 전에 커밋하고, 결과가 돌아오면 새 트랜잭션을 열어 저장하는 방식이다.
같은 조건에서 다시 돌렸다.
| 지표 | 분리 전 | 분리 후 |
|---|---|---|
| pool_timeout_rate | 7.02% (48 / 683건) | 2.37% (16 / 674건) |
| payment_duration p(95) | 3.57s | 2.55s |
| payment_duration max | 5.52s | 3.94s |
66% 감소.
이론으로는 알고 있었다.
"외부 호출을 트랜잭션 안에 두면 커넥션 점유 시간이 길어진다." 수치로 보는 건 다르다.
7.02%가 2.37%가 되는 걸 직접 확인하고 나서야 이론이 실제가 됐다.
트레이드오프가 생긴다.
나누고 나면 이미 커밋된 상태를 되돌리는 보정 코드를 직접 짜야 한다.
그리고 그 보정 자체가 실패할 수 있다.
하나를 고치면 다른 곳이 복잡해진다.
"완벽한 설계"를 고르는 게 아니라 어떤 리스크를 감수할지 고르는 것이다. 수치가 그 판단의 근거가 됐다.
CB 설정을 다룰 때도 비슷한 일이 있었다.
"slowCallDurationThreshold를 넘으면 CB가 열린다"는 걸 알고 있었다.
설정하고 돌렸는데 CB가 열리지 않았다. TimeLimiter가 먼저 호출을 끊어버렸기 때문이다.
TimeLimiter가 끊은 호출은 slowCallRate가 아닌 failureRate로 집계된다.
두 채널이 다르다.
문서가 틀린 게 아니었다.
직접 돌려보기 전까지는 이해한 게 아니었다.
이론과 실측 사이에는 항상 간격이 있다.
설정 파일은 의도를 적은 것이지, 동작을 보장하는 게 아니다.
수치가 나오기 전까지 나는 이해했다고 생각했을 뿐이다.
'Loopers > WIL' 카테고리의 다른 글
| 머물러 있던 자리를 벗어난 10주 (0) | 2026.07.23 |
|---|---|
| Week 5. 인덱스와 캐시 (0) | 2026.06.19 |
| Week 4. 동시성 제어 (0) | 2026.06.12 |
| Week 3. 도메인 주도 설계 (0) | 2026.05.29 |
| Week 2. 설계는 선택의 연속이다. (0) | 2026.05.22 |