refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
@@ -30,7 +30,7 @@ source:
- **로컬이 다른 DB면 로컬 테스트는 다른 시스템에 대한 진술이다**
통과한 레인이 다른 값을 보고 있었다.
- **validator가 요청을 서비스하지 않는 datasource를 검증하고 있었다**
같은 app-bootstrap 모듈에서 시작 검증기가 다른 것을 보고 있던 사례다.
같은 app-bootstrap 모듈에서 startup validator가 실제 바인딩 단계와 다른 파서를 사용해 형식 오류를 잡지 못했다.
## 문제
@@ -42,7 +42,7 @@ source:
프로파일 오버라이드가 형식 결함을 가렸다. 로컬에는 오버라이드가 있었고 나머지에는 없었다.
풀 제약 검증기는 같은 키를 DurationStyle 로 읽어서 5s 를 5000 밀리초로 받는다. 만 이 검증기는 모든 싱글턴이 만들어진 뒤에 도는 자리에 있어서, 이 값으로는 그 전에 빈 생성이 실패한다.
풀 제약 검증기는 같은 키를 `DurationStyle`로 읽어서 `5s`를 5000밀리초로 해석한다. 하지만 이 검증기는 `SmartInitializingSingleton` 단계에서 실행되므로, `HikariDataSource` 빈 바인딩이 먼저 실패하면 검증기까지 도달하지 않는다.
지금은 출하 기본값이 정수로 고쳐져 있고, 밀리초 키 일곱 개의 출하 기본값이 정수인지 확인하는 정적 가드가 있다.
@@ -65,7 +65,7 @@ OpenJDK : 21.0.12
<!-- body:start -->
`spring.datasource.hikari.connection-timeout` `HikariConfig#setConnectionTimeout(long)` 에 바인딩된다. 밀리초 정수만 받는 자리다.
`spring.datasource.hikari.connection-timeout``HikariConfig#setConnectionTimeout(long)`에 바인딩되므로 Spring은 이 값을 `long` 밀리초 값으로 변환해야 한다.
기본값이 `5s` 로 출하됐다. 지금은 `${APP_DATASOURCE_CONNECTION_TIMEOUT:5000}` 로 고쳐져 있다.
@@ -80,11 +80,11 @@ OpenJDK : 21.0.12
:::evidence key="a-five-second-string-that-broke-every-prod-deploy-bind" alt="저장소가 쓰는 Spring Boot 와 HikariCP 산출물로 같은 속성 키에 두 형식을 넣어 실제로 바인딩한 실행 결과. 지속 시간 문자열은 예외 사슬 세 단계로 거절되고 정수는 값이 그대로 들어간다." caption="Spring Boot 4.0.8 · HikariCP 7.0.2 — 같은 키, 두 형식, 실제 바인딩" zoom="true"
:::
예외가 세 단계로 내려간다. 바인딩 실패, 문자열에서 `long` 으로 변환 실패, 그리고 `"5s"` 에 대한 숫자 형식 예외다. 같은 자리`5000` 을 넣으면 값이 그대로 들어간다.
예외 바인딩 실패 문자열 `long`으로 변환하는 실패 `"5s"` 숫자 형식 예외 순으로 이어진다. 같은 프로퍼티`5000`을 넣으면 `long` 값으로 정상 바인딩된다.
## 같은 파일에 같은 이름이 두 형식으로 있다
:::evidence key="a-five-second-string-that-broke-every-prod-deploy" alt="코드베이스에서 출하 설정 파일의 두 네임스페이스에 같은 이름의 키가 다른 형식으로 있는 것과, 앞쪽이 바인딩되는 세터의 long 시그니처, 같은 키를 읽는 시작 검증기의 인터페이스와 파싱 함수, 그리고 그 형식을 막는 정적 가드의 키 목록과 그 가드가 한 번 헛짚었던 자리를 뽑은 출력. 한 파일 안에서 같은 마지막 단어가 정수와 지속 시간 두 형식으로 쓰인다는 것이 그 출력에 그대로 보인다." caption="같은 파일의 두 네임스페이스 · long 세터 · 검증기의 다른 파서 · 정적 가드의 키 목록" zoom="true"
:::evidence key="a-five-second-string-that-broke-every-prod-deploy" alt="코드베이스에서 출하 설정 파일의 두 네임스페이스에 같은 이름의 키가 다른 형식으로 있는 것과, 앞쪽이 바인딩되는 세터의 long 시그니처, 같은 키를 읽는 시작 검증기의 인터페이스와 파싱 함수, 그리고 그 형식을 막는 정적 가드의 키 목록과 그 가드가 한 번 잘못 탐지했던 설정을 뽑은 출력. 한 파일 안에서 같은 마지막 단어가 정수와 지속 시간 두 형식으로 쓰인다는 것이 그 출력에 그대로 보인다." caption="같은 파일의 두 네임스페이스 · long 세터 · 검증기의 다른 파서 · 정적 가드의 키 목록" zoom="true"
:::
`hikari` 아래의 키는 정수를 받고, `tomcat` 아래의 키는 `20s` 로 적혀 있다. 전체 경로는 다르고 마지막 단어만 같다.
@@ -93,7 +93,7 @@ OpenJDK : 21.0.12
풀 제약 검증기가 같은 키를 읽는다. 파서는 `DurationStyle.detectAndParse` 다. 그 규칙에서 `5s` 는 5000 밀리초이고 하한을 넘으므로 유효한 값이다.
다만 이 검증기는 `SmartInitializingSingleton` 이라 모든 싱글턴이 만들어진 뒤에 다. `5s`는 그 전에 `HikariDataSource` 빈 생성에서 바인딩이 죽는다. 검증기가 잘못된 값을 통과시킨 것이 아니라, 이 값에 대해 발언할 수 있는 자리에 있지 않았다.
이 검증기는 `SmartInitializingSingleton`이라 모든 singleton 생성 뒤에 실행된다. `5s`는 그보다 앞선 `HikariDataSource` 빈 생성 바인딩 단계에서 실패하므로 검증기가 값을 판정할 기회가 없다.
## 두 문서가 같은 관용을 반대로 부른다
@@ -109,7 +109,7 @@ OpenJDK : 21.0.12
부팅이 아니라 스캔이라 타입 목록을 손으로 들고 있다. 밀리초 키가 새로 생기면 그 목록에 줄을 더하는 것이 유일한 편입 경로다.
가드 자신의 주석이 초기 버전의 실패도 적어 둔다. 키의 마지막 단어 정규식으로 맞춰서, 진짜 지속 시간`server.tomcat.connection-timeout: 20s` 를 결함으로 보고했다는 것이다. 실제 로더로 평탄화하도록 고친 이유가 그것이다.
가드 주석에는 초기 구현이 키의 마지막 단어 정규식으로 비교해 실제 duration 설정`server.tomcat.connection-timeout: 20s`까지 잘못 거부했다고 적혀 있다. 이후 실제 설정 로더로 값을 펼쳐 검사하도록 바꿨다.
## 확인하지 못한 것
@@ -42,11 +42,9 @@ source:
중첩 깊이 1 이면 스레드당 두 개다. 바깥 하나와 안쪽 하나다.
그래서 풀 크기가 동시 스레드 수보다 커야 한다. 그 제약이 설정 파일 주석에 수식으로 적혀 있다.
그래서 이 프로젝트는 동시 worker 수와 최대 `inNew` 중첩 깊이를 이용한 보수적 풀 사이징 규칙을 설정 주석에 둔다. 이것은 Spring의 보편 불변식이 아니라 이 프로젝트의 concurrency model과 안전 여유를 반영한 운영 규칙이다.
최대 풀 크기가 동시 스레드 수 곱하기 중첩 깊이에 1 을 더한 값 이상, 그리고 거기에 1 을 더한 값 이상이어야 한다는 형태다.
이 수식이 지켜지지 않으면 데드락이 난다. 모든 스레드가 바깥 커넥션을 잡고 안쪽 커넥션을 기다리는 상태가 되고, 아무도 반납하지 않으므로 풀 획득 타임아웃까지 전부 대기한다.
위험은 조건부다. 모든 worker가 outer connection을 점유한 채 inner connection을 기다리고 pool에 추가 connection 여유가 없으면 pool exhaustion이 발생한다. 이 대기가 서로가 놓을 connection을 기다리는 형태가 되면 교착처럼 진행되고 acquisition timeout까지 이어질 수 있다.
같은 주석 블록이 풀 사이징의 다른 근거도 함께 적는다. 작은 풀 공리와 PostgreSQL 의 시작점 수식이다. 코어 수의 두 배에 유효 스핀들 수를 더한 값에서 시작해 부하 테스트로 조정하라는 것이고, 고정 크기 풀을 권장한다.
@@ -78,7 +76,7 @@ Spring Boot : 4.0.8
## 무엇이 금지되고 무엇으로 대신하나
풀 사이징 부등식이 명시돼 있고, 레코드마다 `inNew`를 도는 루프가 금지이며(풀 고갈 + 데드락), 배치로 묶거나 루프를 트랜잭션 밖으로 빼한다.
이 프로젝트의 보수적 풀 사이징 부등식이 명시돼 있고, 레코드마다 `inNew`를 도는 루프는 pool exhaustion과 교착 위험을 키우므로 금지한다. 배치로 묶거나 루프를 트랜잭션 밖으로 빼는 쪽을 택한다.
## 같은 이유의 다른 결정