--- kind: CONCEPT slug: requires-new-connection-cost title: REQUIRES_NEW의 커넥션 비용과 풀 사이징 제약 topic: transaction-deadline-and-pool project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 basisVersion: Spring Boot 4.0.8 · PostgreSQL 16 · 리비전 21234e38 rootTreeNode: concept:requires-new-connection-cost evidenceCapturedOn: 2026-09-01 assets: - key: requires-new-connection-cost file: ../../../final/evidence/rendered/requires-new-connection-cost.svg evidence: - ../../../final/evidence/raw/requires-new-connection-cost.txt source: - 원본 분석 절은 final/document.md#3-2 · final/document.md#a05 §3.1, §13.2 이다. --- # REQUIRES_NEW의 커넥션 비용과 풀 사이징 제약 새 트랜잭션은 새 커넥션을 요구하고 바깥 커넥션은 반납되지 않는다. 그래서 풀 크기 하한이 동시 스레드 수와 중첩 깊이의 곱에 묶인다. ## 관계 - **REQUIRES_NEW가 바깥 커넥션을 핀한 채 새 커넥션을 딴다** 이 제약이 실제로 나타나는 사례다. - **호출 예산에서 DB 로컬 타임아웃까지의 데드라인 전파** 풀 획득이 예산의 일부라는 점에서 연결된다. - **풀 계약 레인이 실행되지 않아 포화 동작이 확인되지 않았다** 이 제약의 검증에 대한 미해결 질문이다. ## 본문 `REQUIRES_NEW`는 바깥 트랜잭션의 리소스를 유지한 채 독립적인 inner transaction 리소스를 요구할 수 있다. 이 프로젝트는 그 concurrency model을 기준으로 `maximumPoolSize >= (concurrent_threads × (1 + max_inNew_depth)) + 1`을 보수적 sizing rule로 사용한다. Spring API 전체에 성립하는 보편 법칙으로 읽어서는 안 된다. ## 커넥션 비용이 곱셈이 되는 이유 :::evidence key="requires-new-connection-cost" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true" ::: ## 레코드마다 inNew 를 도는 루프가 금지인 이유 모든 worker가 outer connection을 잡은 채 inner connection을 기다리고 추가 여유가 없을 때 pool exhaustion이 생기며, 대기가 서로의 반납을 전제로 하면 교착 형태로 진행할 수 있기 때문이다. ## 같은 곱셈 함정이 database-per-tenant에서 반복된다 각 tenant 풀은 개별적으로 합리적이고 그 합이 아니다 — 50 tenant × 10 = 서버 `max_connections` 100에 500 커넥션. 실패는 idle이던 것 포함 모든 tenant에 동시에 도착한다. :::note 풀 계약 레인 미실행 ::: ## 일시 중단은 커넥션을 놓지 않는다 새 트랜잭션 모드는 바깥 트랜잭션을 일시 중단한다. 그 일시 중단은 트랜잭션 경계에 대한 것이다. 바깥 커넥션은 유지된다. 나중에 재개해야 하기 때문이다. ## 그래서 하한이 생긴다 설정 파일이 그 제약을 수식으로 적는다. ```text maxPoolSize >= concurrent_threads * (1 + max_inNew_depth) + 1 ``` 이 프로젝트가 가정한 모델에서 중첩 깊이 1이면 worker 하나가 outer와 inner connection을 동시에 필요로 할 수 있다. 예를 들어 동시 worker 100을 최대 동시성으로 잡고 여유 1을 두는 이 규칙이라면 201을 산정한다. 실제 필요한 값은 transaction manager 동작과 workload의 동시성으로 검증해야 한다. ## 여유가 없으면 pool exhaustion과 교착 위험이 생긴다 모든 worker가 바깥 커넥션을 잡고 안쪽 커넥션을 기다리는 동시에 pool에 남은 connection이 없으면 획득 대기가 누적된다. 이 대기가 서로가 반납해야 할 connection을 기다리는 구조가 되면 acquisition timeout과 교착 형태로 이어질 수 있다. 실제 발생 여부와 임계점은 workload와 pool 설정으로 확인해야 한다. ## 풀 사이징의 다른 근거와 충돌한다 같은 주석 블록이 반대 방향의 근거도 적는다. ```text D1 (feature-database-connection-pool-contract): small-pool axiom + PostgreSQL formula starting point (maximumPoolSize = cores * 2 + effective_spindle_count, adjust via load test). Fixed-size pool recommended (minimumIdle = maximumPoolSize). ``` 작은 풀이 낫다는 공리와 코어 수 기반 시작점이다. 그 값은 대개 동시 스레드 수보다 훨씬 작다. :::warning 두 기준은 서로 다른 질문에 답한다. 일반적인 pool sizing은 불필요하게 큰 pool을 피하려는 기준이고, 중첩 transaction 규칙은 특정 동시성에서 추가 connection 수요가 생길 수 있음을 반영한다. 먼저 불필요한 `REQUIRES_NEW` 중첩을 줄이고, 남은 동시성 모델을 부하 테스트로 확인한 뒤 `maximumPoolSize`를 정한다. ::: ## 고정 크기 풀 `minimumIdle = maximumPoolSize`인 fixed-size 설정은 connection creation 지연을 피하고 응답성을 예측하기 쉽게 하려는 운영 선택이다. `minimumIdle`이 더 작다고 `maximumPoolSize` 용량 자체가 사라지는 것은 아니다. `REQUIRES_NEW` 안전 여유는 `maximumPoolSize`와 실제 concurrency model로 판단한다. ## 멀티테넌시에서의 확장 테넌트별 풀 예산 타입이 이 제약을 테넌트 단위로 다시 적용한다. 테넌트 하나가 풀 전체를 소진하는 것을 막으면서도 각 테넌트의 중첩 하한을 만족해야 한다.