문제 정의
PATCH /api/v1/care-plans/{carePlanId}/status는 CarePlan 상태 변경 과정에서 User Service를 Feign으로 호출하고 있었다.
정상 환경에서 40 Threads × Loop Count 5, 총 200건의 반복 부하를 발생시켰지만 Average 20ms, p95 28ms로 처리가 매우 빨라 HikariCP Pool 포화가 발생하지 않았다.
하지만 코드상 User Service 호출이 DB 트랜잭션 내부에 존재했기 때문에 외부 서비스가 느려질 경우 DB Connection까지 장시간 점유할 가능성이 있었다.
이를 검증하기 위해 User Service 내부 API에 300ms 지연을 주입했다.
동일한 200건 부하에서:
- Active = Max(10)
- Idle = 0
- Pending ≈ 14
- Average: 771ms
- p95: 1175ms
- Max: 1669ms
- Throughput: 39.31 → 24.39 req/s
로 Connection Pool 포화와 요청 대기를 재현했다.
즉 외부 서비스의 응답 지연이 CarePlan의 DB 자원 고갈로 전파되고 있었다.
원인 분석
기존 흐름에서는 User Service 호출이 @Transactional 범위 안에 포함되어 있었다.
@Transactional 시작
↓
CarePlan DB 처리
↓
Feign → User Service
↓
CarePlan 상태 변경
↓
Outbox 저장
↓
Commit
따라서 외부 서비스 응답을 기다리는 시간까지 DB Transaction 범위에 포함되었고, 동시 요청이 증가할 경우 Connection이 반환되지 않은 채 Pool을 점유했다.
300ms 지연 조건에서 Active = Max, Idle = 0, Pending > 0이 동시에 발생한 것이 이를 뒷받침했다.
해결 방안
| 방안 | 장점 | 트레이드오프 |
| Feign Timeout 설정 | 외부 서비스 대기시간의 상한을 제한 | Timeout 발생 전까지 DB Connection을 계속 점유해 근본 구조는 유지됨 |
| 트랜잭션 범위 축소 | 외부 호출 대기와 DB Transaction을 분리 | 외부 호출 이후 상태가 변경될 수 있어 DB 재조회·재검증 필요 |
Connection을 오래 점유하는 구조 자체를 제거하기 위해 트랜잭션 범위 축소를 선택했다.
User Service 호출은 쓰기 트랜잭션 밖으로 이동하고, 실제 CarePlan 상태 변경·검증·Outbox 저장 구간만 @Transactional로 묶었다.
Feign → User Service
↓
@Transactional 시작
↓
CarePlan 재조회 및 상태 재검증
↓
상태 변경
↓
Outbox 저장
↓
Commit
외부 호출과 트랜잭션 시작 사이에 상태가 변경될 가능성이 생기는 트레이드오프가 있으므로, 트랜잭션 진입 후 CarePlan을 다시 조회하고 상태 전이를 재검증하도록 했다.
DB 조회가 한 번 증가하지만 Connection 장기 점유를 제거하는 것이 더 중요하다고 판단했다.
성과
동일한 User Service 300ms 지연 / 40 Threads / 200건 조건에서 개선 전후를 재측정했다.
| 지표 | 개선 전 | 개선 후 | 변화 |
| Average | 771ms | 615ms | 약 20% 감소 |
| p95 | 1175ms | 924ms | 21.4% 감소 |
| Max | 1669ms | 958ms | 42.6% 감소 |
| Pending Max | 약 14 | 0 | 대기 제거 |
| Active Max | 10 | 1 | Pool 압력 감소 |
| Idle Min | 0 | 5 | 여유 Connection 확보 |
| Throughput | 24.39 req/s | 25.48 req/s | 증가 |
외부 서비스의 300ms 지연 자체는 유지했지만 Connection Pool 포화가 해소되었다.
즉 외부 서비스 자체를 빠르게 만든 것이 아니라 외부 서비스 지연이 DB Connection Pool 고갈로 전파되지 않도록 트랜잭션 경계를 재설계했다.
'Todak-Todag' 카테고리의 다른 글
| [Troubleshooting] Outbox Relay 다중 인스턴스 환경의 중복 발행 방지 (0) | 2026.09.20 |
|---|---|
| [Troubleshooting] Docker Volume에 남은 PostgreSQL 인증 정보로 인한 DB 연결 실패 해결 (0) | 2026.09.20 |
| [Troubleshooting] RabbitMQ Consumer의 외부 호출과 DB Transaction 분리 (0) | 2026.09.20 |
| [Troubleshooting] RabbitMQ 비동기 이벤트 구간의 Trace 단절 개선 (0) | 2026.09.20 |