FOWOCO Server가 생성하는 PostgreSQL Runtime Hikari Connection에는 장시간 SQL과 lock 대기가 Connection Pool 전체 장애로 확산되지 않도록 Session 범위 안전선을 적용합니다.
| 환경변수 | 기본값 | 의미 |
|---|---|---|
DB_STATEMENT_TIMEOUT |
30s |
개별 SQL의 최대 실행시간 |
DB_LOCK_TIMEOUT |
3s |
row/table lock 획득 최대 대기시간 |
Spring Boot Duration 형식을 사용합니다. 30s, 3000ms, PT30S, 1000us를
사용할 수 있으며 접미사가 없는 정수는 millisecond입니다. 운영에서는 혼동을
막기 위해 단위를 명시합니다.
두 값은 다음 조건을 모두 만족해야 합니다.
- 0보다 크고 정확한 정수 millisecond로 표현 가능
2147483647ms이하DB_LOCK_TIMEOUT < DB_STATEMENT_TIMEOUT
1000us는 정확히 1ms이므로 허용하지만 1500us는 millisecond 미만 remainder가
있어 시작 단계에서 거부합니다. 0으로 guard를 비활성화할 수 없습니다.
PostgreSQL mode에서는 spring.datasource.url 기반의 직접적인
jdbc:postgresql: URL과 HikariCP만 지원합니다. URL, username, password와 선택적인
driver는 spring.datasource.*를 단일 설정 원본으로 사용합니다.
다음 구성은 중복 설정 또는 물리 Connection 초기화 보장 상실을 막기 위해 시작 단계에서 거부합니다.
- JNDI DataSource
- Hikari namespace의 JDBC URL, username, password, driver 중복 설정
- Hikari
data-source-class-name,data-source-j-n-d-i,connection-init-sql - Hikari 이외의 명시적인
spring.datasource.type - p6spy 등 wrapper/proxy JDBC URL
spring.datasource.connection-fetch=lazy
향후 DataSource proxy나 decorator를 도입하면 물리 Connection 생성 시점의 Session 초기화가 유지되는지 다시 검토해야 합니다.
Runtime Pool은 새 물리 Connection을 등록하기 전에 다음과 같은 하나의 초기화 SQL을 실행합니다.
SELECT
pg_catalog.set_config('statement_timeout', '30000ms', false),
pg_catalog.set_config('lock_timeout', '3000ms', false)환경변수 원문은 SQL에 들어가지 않으며 검증된 정수 millisecond만 사용합니다. 초기화 SQL이 실패한 Connection은 정상 Pool Connection으로 제공되지 않습니다.
- PostgreSQL Runtime Hikari Connection에만 적용
- Flyway 전용 Connection에는 미적용
- local/test H2에는 미적용
- 새 물리 Connection마다 한 번 실행
- transaction 또는 checkout마다 반복 실행하지 않음
HikariCP는 Connection 반환 시 PostgreSQL의 모든 임의 Session parameter를 reset하지
않습니다. Runtime production 코드에서 SET statement_timeout,
SET lock_timeout 또는 Session 범위 set_config(..., false)를 사용하면 다음
요청에 변경값이 남을 수 있으므로 금지합니다.
업무별 예외가 필요하면 별도 Issue에서 Spring Transaction 내부의 SET LOCAL만
사용합니다. SET LOCAL은 commit 또는 rollback 후 Session 기본값으로 돌아가야
합니다. 이번 구현에는 checkout interceptor나 Session reset proxy가 없습니다.
PostgreSQL의 SQLState만으로 원인을 확정하지 않습니다.
57014와 canonical statement-timeout diagnostic이 함께 있으면 confirmed statement timeout55P03과 canonical lock-timeout diagnostic이 함께 있으면 confirmed lock timeout- SQLState만 일치하면 ambiguous cancellation 또는 lock failure
- 권한·RLS
42501, bootstrap 인자22023은 timeout이 아님
Confirmed timeout은 안전한 공개 오류 503 SERVICE_TEMPORARILY_UNAVAILABLE로
응답합니다. Retry-After는 제공하지 않습니다. 복구 시점을 신뢰할 수 없기
때문입니다. Ambiguous cancellation은 기존 500 INTERNAL_SERVER_ERROR를 유지합니다.
PostgreSQL lc_messages가 영어가 아니면 canonical diagnostic을 확인하지 못해 실제
timeout도 ambiguous로 처리될 수 있습니다. False positive를 막기 위한 보수적
정책이며 confirmed metric이 과소 집계될 수 있습니다.
Client와 Gateway는 모든 503을 자동 재시도하지 않습니다. GET처럼 본질적으로
idempotent한 요청만 exponential backoff와 jitter로 제한적으로 재시도할 수 있습니다.
POST·PATCH 등 변경 요청은 Idempotency-Key로 동일 요청이 보장될 때만 재시도하며,
그렇지 않으면 자동 재시도하지 않습니다.
Confirmed 및 ambiguous 분류 로그에는 requestId, HTTP method, 안전한 route pattern,
classification, SQLState, exception type만 기록합니다. Raw URI, SQL, bind parameter,
PostgreSQL diagnostic message, credential, 개인정보와 stack trace는 기록하지 않습니다.
상세 원인은 request ID와 발생 시각을 기준으로 PostgreSQL 서버 로그에서 조사합니다.
Confirmed HTTP timeout은 다음 low-cardinality counter에 집계합니다.
fowoco.database.timeouts{type="statement"}
fowoco.database.timeouts{type="lock"}
Ambiguous cancellation과 Background Outbox failure는 이 metric 범위가 아닙니다.
DB Transaction과 metric 증가는 원자적이지 않으므로 사건당 exactly-once 집계를
보장하지 않습니다. Outbox의 retry, backoff, maxAttempts와 기존 metric 정책은
변경하지 않습니다.
환경변수 변경
→ 애플리케이션 전체 재시작
→ 새 Runtime Pool 생성
→ current_setting 확인
→ metric과 정상 Query 관측
기존 물리 Connection에는 환경변수 변경이 즉시 반영되지 않습니다. Staging에서는 confirmed timeout 횟수, 정상 Query 실행시간, Pool 사용량과 Outbox 지연을 함께 확인합니다.
정상 workload가 반복해서 timeout되면 다음 순서로 대응합니다.
- Query와 index 개선
- Transaction 범위 축소
- workload 분리
- 후속 Issue에서
SET LOCALoverride 검토 - 승인 후 전역 기본값 조정
전역값 상향을 첫 대응으로 사용하지 않습니다.
문제가 발생하면 이전 애플리케이션 버전을 재배포하고 Runtime Pool 전체를
재생성한 뒤 Session 적용값을 확인합니다. ALTER ROLE, ALTER DATABASE 또는
timeout 0 설정으로 우회하지 않습니다.
실제 배포 환경변수, Runtime/Flyway credential과 Secret 연결은 Issue #9의 범위입니다.