The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
107 lines
4.4 KiB
Markdown
107 lines
4.4 KiB
Markdown
---
|
||
kind: CONCEPT
|
||
slug: requires-new-connection-cost
|
||
title: REQUIRES_NEW의 커넥션 비용과 풀 사이징 제약
|
||
topic: transaction-deadline-and-pool
|
||
project: clean-architecture-backend-template
|
||
status: 게시 전
|
||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||
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 · analysis/05 §3.1, §13.2 이다.
|
||
---
|
||
|
||
# REQUIRES_NEW의 커넥션 비용과 풀 사이징 제약
|
||
|
||
새 트랜잭션은 새 커넥션을 요구하고 바깥 커넥션은 반납되지 않는다. 그래서 풀 크기 하한이 동시 스레드 수와 중첩 깊이의 곱에 묶인다.
|
||
|
||
## 관계
|
||
|
||
- **REQUIRES_NEW가 바깥 커넥션을 핀한 채 새 커넥션을 딴다**
|
||
이 제약이 실제로 나타나는 사례다.
|
||
- **호출 예산에서 DB 로컬 타임아웃까지의 데드라인 전파**
|
||
풀 획득이 예산의 일부라는 점에서 연결된다.
|
||
- **풀 계약 레인이 실행되지 않아 포화 동작이 확인되지 않았다**
|
||
이 제약의 검증에 대한 미해결 질문이다.
|
||
|
||
## 본문
|
||
|
||
<!-- body:start -->
|
||
|
||
`REQUIRES_NEW`는 바깥 트랜잭션의 커넥션을 **핀한 채로** 새 물리 JDBC 커넥션을 딴다. 그래서 풀 사이징 제약이 곱셈이 된다 — `maximumPoolSize >= (concurrent_threads × (1 + max_inNew_depth)) + 1`.
|
||
|
||
## 커넥션 비용이 곱셈이 되는 이유
|
||
|
||
:::evidence key="requires-new-connection-cost" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
|
||
:::
|
||
|
||
## 레코드마다 inNew 를 도는 루프가 금지인 이유
|
||
|
||
풀 고갈과 데드락이다.
|
||
|
||
## 같은 곱셈 함정이 database-per-tenant에서 반복된다
|
||
|
||
각 tenant 풀은 개별적으로 합리적이고 그 합이 아니다 — 50 tenant × 10 = 서버 `max_connections` 100에 500 커넥션. 실패는 idle이던 것 포함 모든 tenant에 동시에 도착한다.
|
||
|
||
:::note
|
||
|
||
풀 계약 레인 미실행
|
||
|
||
:::
|
||
|
||
## 일시 중단은 커넥션을 놓지 않는다
|
||
|
||
새 트랜잭션 모드는 바깥 트랜잭션을 일시 중단한다. 그 일시 중단은 트랜잭션 경계에 대한 것이다.
|
||
|
||
바깥 커넥션은 유지된다. 나중에 재개해야 하기 때문이다.
|
||
|
||
## 그래서 하한이 생긴다
|
||
|
||
설정 파일이 그 제약을 수식으로 적는다.
|
||
|
||
```text
|
||
maxPoolSize >= concurrent_threads * (1 + max_inNew_depth) + 1
|
||
```
|
||
|
||
중첩 깊이 1 이면 스레드당 두 커넥션이다. 동시 스레드가 100 이면 최소 201 이 필요하다.
|
||
|
||
## 지켜지지 않으면 데드락이다
|
||
|
||
모든 스레드가 바깥 커넥션을 잡고 안쪽 커넥션을 기다린다. 아무도 반납하지 않으므로 전부 풀 획득 타임아웃까지 대기한다.
|
||
|
||
이 상태는 부하가 임계를 넘는 순간 한꺼번에 나타난다. 그 전까지는 아무 증상이 없다.
|
||
|
||
## 풀 사이징의 다른 근거와 충돌한다
|
||
|
||
같은 주석 블록이 반대 방향의 근거도 적는다.
|
||
|
||
```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
|
||
|
||
두 근거가 같은 손잡이를 반대 방향으로 민다. 작은 풀 공리는 값을 줄이라 하고 중첩 하한은 늘리라 한다. 해소하는 방법은 풀을 키우는 것이 아니라 중첩 깊이를 줄이는 것이다.
|
||
|
||
:::
|
||
|
||
## 고정 크기 풀
|
||
|
||
최소 유휴를 최대 크기와 같게 두는 것을 권장한다. 풀이 줄었다가 늘어나는 동안 중첩 하한이 일시적으로 깨지는 것을 막는다.
|
||
|
||
## 멀티테넌시에서의 확장
|
||
|
||
테넌트별 풀 예산 타입이 이 제약을 테넌트 단위로 다시 적용한다. 테넌트 하나가 풀 전체를 소진하는 것을 막으면서도 각 테넌트의 중첩 하한을 만족해야 한다.
|
||
|
||
<!-- body:end -->
|