Skip to content

[Reliability] PostgreSQL Runtime Connection 기본 Timeout Guard 도입 #64

Description

@krestar

한 줄 목표

Server 애플리케이션이 생성하는 PostgreSQL Runtime Hikari Connection에 공통 statement_timeoutlock_timeout 안전선을 적용해,
장시간 SQL과 Lock 대기가 DB Connection을 과도하게 점유하지 않도록 합니다.


쉽게 설명하면

현재 Server의 DB 요청은 PostgreSQL이 응답하거나 외부에서 Transaction을 중단할 때까지 오래 대기할 수 있습니다.

느린 SQL 또는 비효율적인 조회
→ Runtime Connection 장시간 점유
→ Connection Pool의 사용 가능한 Connection 감소
→ 정상 API도 Connection을 얻지 못하고 지연

다른 Transaction이 Row Lock 보유
→ 후속 요청이 Lock을 계속 대기
→ Transaction과 Connection이 장시간 유지
→ 장애가 관련 없는 요청으로 확산

이번 작업에서는 Runtime Hikari Pool이 생성하는 각 물리 Connection에 공통 Timeout 기본값을 적용해 다음 원칙을 보장합니다.

개별 SQL은 정해진 시간 이상 실행되지 않습니다.
Lock 획득 대기시간은 SQL 전체 제한보다 짧게 설정합니다.
Timeout이 발생한 Transaction은 정상적으로 Rollback됩니다.
Rollback 후 Connection은 정상적으로 Pool에 반환되어 재사용되거나,
유효하지 않은 경우 안전하게 폐기됩니다.

배경과 현재 문제

현재 Server는 Spring Boot DataSource와 PostgreSQL Runtime 계정을 사용하지만,
Runtime Connection에 적용되는 공통 statement_timeoutlock_timeout 정책이 명시되어 있지 않습니다.

애플리케이션 코드의 성능 최적화와 별개로 DB 수준의 최소 보호선이 없으면 다음 위험이 있습니다.

  • 예상보다 오래 실행되는 SQL이 Runtime Connection을 장시간 점유
  • Lock 경합이 해소될 때까지 요청이 과도하게 대기
  • 일부 느린 요청이 Connection Pool 고갈로 확대
  • 장애 상황에서 API 응답 지연과 Timeout 원인을 구분하기 어려움
  • 로컬·CI·운영 환경별 Session 설정이 달라질 가능성

이번 Issue는 Query별 최적화가 아니라 Server Runtime Hikari Connection에 적용되는 공통 최소 안전선을 담당합니다.


책임 경계

#34: Runtime/Flyway Role 분리와 RLS 보안 경계

  • Flyway Migration Role과 Spring Boot Runtime Role의 분리
  • Runtime Role의 최소 권한과 NOSUPERUSER, NOBYPASSRLS, 비소유자 원칙
  • RLS Policy와 Tenant 격리
  • Transaction-local app.company_id
  • 제한된 bootstrap 함수와 권한 경계

#9: 분리된 DB Role의 배포 Provisioning과 Pool 운영

  • #34에서 정의한 Role·권한 모델을 Demo/Staging PostgreSQL에 실제 적용
  • 각 Role의 Credential 생성·회전과 배포 Secret 주입
  • prod Profile의 Runtime/Flyway Credential 연결
  • Hikari Pool 크기·수명·배포 설정
  • Hikari connectionTimeout 등 Pool 대여 대기 정책
  • 본 Issue가 정의한 Timeout 환경변수의 배포 연결

본 Issue: Runtime Connection의 PostgreSQL 실행 Timeout

  • Server Runtime Hikari Connection의 statement_timeout
  • Server Runtime Hikari Connection의 lock_timeout
  • 값의 의미·기본값·허용 범위·검증
  • Timeout 발생 후 Transaction Rollback
  • Connection의 정상 반환·후속 요청 재사용 또는 안전한 폐기
  • 실제 Session 적용값 검증

#9#34에서는 다음 설정을 추가하지 않습니다.

ALTER ROLE ... SET statement_timeout = ...;
ALTER ROLE ... SET lock_timeout = ...;
ALTER DATABASE ... SET statement_timeout = ...;
ALTER DATABASE ... SET lock_timeout = ...;

Server Runtime Connection에서는 본 Issue의 애플리케이션 설정을 두 Timeout의 단일 진실 공급원으로 사용합니다.
PostgreSQL 전역 설정으로도 같은 값을 별도 관리하지 않습니다.

Runtime Credential은 Server 애플리케이션 전용으로 사용합니다.
같은 Role을 사용하는 psql, 운영 스크립트 또는 별도 애플리케이션의 Connection은 본 Issue의 보장 범위에 포함하지 않으며,
필요한 경우 별도 Role과 Timeout 정책을 사용합니다.


확정한 적용 방식

HikariCP connectionInitSql을 사용합니다.

Hikari가 새 물리 Connection을 생성한 뒤 Pool에 등록하기 전에 하나의 초기화 SQL을 실행해 Session 범위 Timeout을 설정합니다.

SELECT
    pg_catalog.set_config('statement_timeout', '30000ms', false),
    pg_catalog.set_config('lock_timeout', '3000ms', false);

pg_catalog.set_config(..., false)는 현재 물리 Connection의 Session 범위에 적용됩니다.
Transaction마다 초기화 SQL을 반복 실행하지 않습니다.

선택 이유

  • Server 애플리케이션의 Runtime Hikari Connection에만 적용 가능
  • 새 물리 Connection 생성 시 동일 설정 적용
  • Runtime Role이나 Migration 변경 불필요
  • Flyway Connection과 책임 경계 유지
  • 로컬 및 PostgreSQL 통합 테스트에서 재현 가능
  • 설정 오류와 Session 적용 실패를 Connection 생성 단계에서 발견 가능

주의사항

  • connectionInitSql은 새 물리 Connection 생성 시 실행되며 기존 Session을 실시간으로 갱신하는 기능이 아닙니다.
  • 초기화 SQL이 실패한 Connection은 정상 Pool Connection으로 등록되어서는 안 됩니다.
  • 애플리케이션 설정은 시작 전에 확정하며 Runtime hot reload는 지원하지 않습니다.
  • 향후 Pool 구현이나 DB Proxy가 변경되면 Session 초기화 보장 여부를 다시 검토합니다.

설정 정책

  • statement_timeoutlock_timeoutDuration으로 입력받습니다.
  • PostgreSQL에 전달할 때 millisecond 단위의 명시적인 값으로 변환합니다.
  • 음수, 파싱 실패, 지원하지 않는 단위와 millisecond 미만 정밀도는 거부합니다.
  • lock_timeout < statement_timeout을 강제합니다.
  • 초기 MVP 기본값은 statement_timeout=30s, lock_timeout=3s로 설정합니다.
  • 0은 Timeout Guard 비활성화를 의미하므로 허용하지 않습니다.
  • 필수 설정 누락과 잘못된 값은 Connection 생성 전 애플리케이션 설정 검증에서 Fail-fast 처리합니다.
  • 초기화 SQL 실패 시 해당 Connection을 Pool에 등록하지 않습니다.
  • 정상 초기화된 Runtime Connection을 획득할 수 없는 상태를 정상 DB 상태로 판단하지 않습니다.
  • Runtime 코드에서 두 값을 SET SESSION으로 다시 변경하지 않습니다.
  • 업무별 예외가 필요하면 후속 Issue에서 SET LOCAL 기반 Transaction-local override만 허용합니다.

두 기본값은 영구적인 최적값이 아니며, Staging의 정상 Query 실행시간과 Lock 경합 관측 결과에 따라 후속 Issue에서 조정할 수 있습니다.


구현 범위

설정 모델

  • DB_STATEMENT_TIMEOUT 환경변수 주입, 기본값 30s
  • DB_LOCK_TIMEOUT 환경변수 주입, 기본값 3s
  • 시간 단위 및 millisecond 변환 계약 정의
  • 0·음수·파싱 실패·미지원 단위·millisecond 미만 값 거부
  • lock_timeout < statement_timeout 검증
  • 애플리케이션 시작 시 설정 검증

Runtime Connection 적용

  • HikariCP connectionInitSql 구성
  • pg_catalog.set_config를 사용하는 하나의 SQL 문으로 두 Session Timeout 설정
  • 새 Runtime 물리 Connection에 동일 설정 적용
  • Pool 재사용 중 Timeout 설정 유지
  • Transaction마다 반복 SQL 실행 없음
  • Flyway Connection에는 영향 없음
  • H2 테스트에는 영향 없음

오류 처리

  • Statement Timeout과 Lock Timeout을 진단 가능한 내부 오류로 구분
  • PostgreSQL SQLState 보존
  • RLS·권한·bootstrap 오류를 Timeout으로 오분류하지 않음
  • 외부 API 오류 응답에 SQL 원문·Bind Parameter·DB 구조·Credential·개인정보
    노출 금지
  • 운영 Profile의 애플리케이션 오류 로그에 SQL 원문·Bind Parameter·Credential·
    개인정보 노출 금지
  • Timeout을 성공 또는 빈 결과로 변환하지 않음
  • Timeout 발생 시 Transaction Rollback 보장
  • 무제한 자동 retry 금지

테스트 시나리오

1. 설정 유효성

  • 정상 설정으로 애플리케이션 시작
  • lock_timeout < statement_timeout 허용
  • 반대 조건 Fail-fast
  • 음수·파싱 실패·미지원 단위 거부
  • millisecond 미만 값 거부
  • 0 거부
  • 30s·3s 기본값 검증

2. Runtime Role과 Session 적용

  • 제한된 non-superuser Runtime Role로 Connection 생성
  • 별도 GRANT와 ALTER ROLE SET 없이 초기화 SQL 성공
  • pg_catalog.current_setting으로 두 Session 적용값 확인
  • 초기화 SQL 실패 Connection은 Pool에 등록되지 않음
  • 정상 초기화된 Runtime Connection을 획득할 수 없으면 정상 DB 상태로
    판단하지 않음

3. Statement Timeout

SELECT pg_sleep(...);
  • 제한시간 초과 시 Statement Timeout 발생
  • 해당 Transaction Rollback
  • 실패 Transaction의 Commit 금지
  • 후속 요청 정상 처리

4. Lock Timeout

  • A Transaction이 Row Lock 보유
  • B Transaction에서 Lock Timeout 발생
  • A Transaction에는 영향 없음
  • Lock 해제 후 후속 요청 정상 처리

5. Connection Pool

  • Rollback 후 Connection이 정상적으로 Pool에 반환되어 후속 요청에서 재사용되거나,
    유효하지 않은 경우 안전하게 폐기
  • Connection 누수 없음
  • 반복 Timeout에도 Pool 고갈 없음
  • 새 물리 Connection에도 동일 Timeout 적용

6. 기존 PostgreSQL 보안 기능 회귀

  • 기존 PostgreSQL Tenant Context 및 RLS 관련 테스트가 회귀 없이 통과

7. 기존 Outbox 회귀

  • 기존 Outbox 단위·통합 테스트가 회귀 없이 통과

8. 설정 격리

  • Runtime Hikari Connection에만 적용
  • Flyway 전용 DataSource에는 영향 없음
  • H2 테스트에는 영향 없음
  • PostgreSQL 17 CI 유지

9. 로그와 보안

  • 외부 API 오류 응답에 SQL 원문·Bind Parameter·DB 구조·Credential·개인정보·Stack trace 노출 금지
  • 운영 Profile의 오류 로그에 SQL 원문·Bind Parameter·Credential·개인정보 노출 금지
  • 개발 Profile의 의도적인 Hibernate SQL 출력 정책은 변경하지 않음

완료 조건

  • Runtime Hikari Connection에 두 Timeout 적용
  • 설정 유효성 및 Fail-fast 검증 완료
  • 제한 Runtime Role에서 적용 검증 완료
  • Statement·Lock Timeout 동작 검증 완료
  • Rollback 후 Connection의 정상 반환·재사용 또는 안전한 폐기 검증 완료
  • 새 물리 Connection 적용 확인
  • 기존 PostgreSQL Tenant Context 및 RLS 테스트 통과
  • Outbox 회귀 테스트 통과
  • Flyway DataSource 분리 유지
  • PostgreSQL 17 CI 통과
  • 로그·보안 기준 충족
  • 운영 설정과 Rollback 방법 문서화

관측과 보안 요구사항

  • Timeout 발생 횟수는 SQL이나 Parameter가 아닌 제한된 내부 코드로 구분합니다.
  • SQL·Parameter·개인정보를 Metric Label로 사용하지 않습니다.
  • DB Credential을 로그에 기록하지 않습니다.
  • 무제한 retry를 금지합니다.
  • 사용자 요청을 무기한 대기시키지 않습니다.
  • 개발 Profile의 의도적인 Hibernate SQL 출력 정책은 이번 Issue에서 변경하지 않습니다.

이번 Issue에서 하지 않는 것

  • ALTER ROLE·ALTER DATABASE·PostgreSQL 전역 설정으로 Timeout 구성
  • Flyway Connection에 Runtime Timeout 적용
  • Flyway Migration 파일 변경
  • transaction_timeout
  • idle_in_transaction_session_timeout
  • Hikari tuning 전체
  • Tenant Context 구현 또는 RLS Policy 변경
  • AOP·Annotation 기반 Timeout
  • Query 최적화, N+1 개선, Index 분석
  • Pool sizing과 max_connections 산정
  • Runtime Credential을 사용하는 외부 Client의 Timeout 보장
  • Runtime hot reload

후속 Issue 후보

PostgreSQL Session 종료 Guard

  • transaction_timeout
  • idle_in_transaction_session_timeout
  • Session 종료 감지
  • Pool recovery
  • Metric·Alert

업무별 Timeout 정책

  • Read/Write 분리
  • Batch 작업
  • Outbox 전용 Timeout
  • API별 Transaction-local override

선행·연결 관계

선행 Issue는 없습니다.

Metadata

Metadata

Assignees

Labels

area:infraServer Dockerfile·DB 설정·CI hook·배포 가능성 영역; 통합 인프라 운영은 infra 저장소와 조율area:serverSpring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외priority:P1핵심 작업 다음으로 처리할 중요 작업status:in-review구현을 마치고 리뷰 또는 병합을 기다리는 작업type:chore저장소 설정·의존성·유지보수 작업

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions