고성능 JPA & Hibernate - Connection Management and Hibernate Connection Providers

JPA Connection 연결과 공급자
김주혁's avatar
Jul 11, 2025
고성능 JPA & Hibernate - Connection Management and Hibernate Connection Providers
 
자바 애플리케이션에서 데이터베이스 연결은 필수적인 기능이지만, 그 과정에는 생각보다 많은 복잡성과 비용이 숨어 있습니다. 특히 고성능을 지향하는 시스템에서는 이 '연결'의 효율성이 전체 애플리케이션의 성능을 좌우하는 핵심 요소가 됩니다. JPA가 어떻게 데이터베이스 연결을 관리하는지, 그리고 그 하단에서 JDBC가 물리적 연결을 어떻게 설정하며 어떤 비용이 발생하는지에 대해서 알아보겠습니다.
 

1. JPA Connection 설정의 중심: persistence.xmlDataSource의 역할

  • JPA와 persistence.xml: JPA(Java Persistence API)는 자바 엔티티와 데이터베이스 테이블 매핑을 정의하는 표준이며, 모든 설정의 핵심은 persistence.xml 파일입니다.
  • 영속성 유닛 설정: persistence.xml은 애플리케이션의 '영속성 유닛'을 설정하며, 여기에 데이터베이스 연결 정보가 포함됩니다.
  • JNDI(Java Naming and Directory Interface) 활용 (권장):
    • 엔터프라이즈 환경에서는 데이터베이스 연결 정보를 persistence.xml에 직접 하드코딩하지 않습니다.
    • 대신, JNDI를 통해 미리 애플리케이션 서버에 설정된 DataSource (데이터베이스 연결 풀 객체)를 참조합니다.
    • 이는 애플리케이션의 유연성과 이식성을 크게 향상시킵니다.
  • 드라이버 기반 직접 구성 (JPA 2.0+): JPA 2.0부터는 JNDI 참조 외에 JDBCDriver, URL, Database Name, User, Password 등을 persistence.xml에서 직접 구성할 수도 있습니다. 하지만 이 경우 커넥션 풀링 등 추가적인 설정이 필요합니다.
 

2. 성능 관점: JPA Provider와 연결 획득/해제의 중요성

애플리케이션의 성능 측면에서 보면, 사용하고 있는 JPA Provider(Hibernate)가 데이터베이스 연결을 어떻게 획득하고 해제하는지 정확히 아는 것이 매우 중요합니다. 실제로 연결을 획득하는 과정 자체가 전체 트랜잭션 응답 시간에 상당한 영향을 미칠 수 있기 때문입니다.
그렇다면 JPA Provider는 어떻게 데이터베이스 연결을 획득하는 걸까요?
 
notion image
  • 연결 요청의 시작: JDBC DriverManager의 역할
    • 애플리케이션이 데이터베이스에 연결하고자 할 때, JDBC DriverManager에게 새로운 연결(Connection)을 요청합니다. JDBC DriverManager는 물리적인 데이터베이스 연결을 생성해주는 '팩토리(factory)' 역할을 수행합니다.
  • 네트워크 연결 확립: 소켓 및 TCP 핸드셰이크
    • DriverManager가 드라이버를 통해 새 연결을 얻으려고 하면, 가장 먼저 클라이언트 측(애플리케이션)과 데이터베이스 서버 사이에 소켓(Socket)이 열립니다.
    • 이후, 이 소켓을 통해 JDBC 클라이언트와 데이터베이스 서버 간에 TCP 연결이 설정됩니다. 이 과정에는 여러 단계의 메시지 교환을 포함하는 TCP 핸드셰이크(Handshake)가 따라옵니다.
  • 서버 측 자원 할당: 스레드 및 프로세스
    • TCP 연결이 성공적으로 수립되면, 데이터베이스 서버는 이 특정 연결(세션)을 처리하기 위해 서버 측 자원을 할당합니다. 일반적으로 이는 스레드 또는 프로세스 등의 CPU 자원 할당으로 이어집니다.
  • 추가적인 자원 할당: 버퍼
    • TCP 핸드셰이크와 새로운 스레드/프로세스 할당 외에도, 연결을 설정하는 과정에서 클라이언트 측과 서버 측 모두에서 데이터를 주고받기 위한 버퍼(Buffer)가 할당될 수 있습니다.
 
이러한 소켓 , TCP 핸드셰이크, 서버 측 스레드/프로세스 할당, 그리고 클라이언트/서버 버퍼 할당의 모든 과정은 상당히 자원 집약적인 작업이며, 네트워크 지연과 서버 자원 소모를 유발합니다.
 
따라서, 데이터베이스 연결을 설정하는 과정은 상당한 시간과 컴퓨팅 자원을 요구하며, 이는 곧 애플리케이션의 '응답 시간'에 직접적이고 상당한 영향을 미칠 수밖에 없습니다. 애플리케이션이 매번 트랜잭션마다 이러한 과정을 반복한다면, 성능 저하가 예상될 수 있습니다.
 
연결 획득 시간 메트릭의 중요성
추가적으로 연결 획득시간(최소/최대/평균) 등의 메트릭을 수집할 때는 응답 시간이 물론 중요한 영향을 미칠 수 있으나 평균값과 백분위수(P99) 또한 데이터베이스 연결이 시스템에 미치는 영향에 대해서 중요하게 사용될 수 있습니다.
  • min / max / mean / p99 …
    • 백분위 수 (P99) : 트랜잭션 처리 시간의 분포를 나타내는 통계 지표로서 기록된 값의 99%, 특정 백분위 수 이하 값을 알려줍니다. 만약 P99 값이 50ms라면, 100개의 트랜잭션 중 99개는 50ms 이내에 처리되었고, 가장 느린 1개만 50ms보다 오래 걸렸다는 뜻입니다.
 
 
어떻게 연결을 관리하느냐에 따라서 달라지는 시간을 특정 DB에 관한 JDBC Driver를 사용했을 때를 먼저 예로 들면
min
24.468 ms
max
74.634 ms
mean
28.910 ms
p99
54.952 ms
위의 지표를 예시로 든다면 최대 연결 획득 시간은 74.634ms, 99 백분위수는 54.952ms입니다. 만약 하나의 연결이 하나의 트랜잭션을 처리하는데 최소 50ms가 걸린다고 치면, 1초(1000ms) 동안 이 하나의 연결이 처리할 수 있는 트랜잭션 수는
  • 1000 ms (1초)/50 ms (트랜잭션당 소요 시간)=20 트랜잭션/초
즉 초당 20개의 트랜잭션을 처리할 수 있다는 결론을 내릴 수 있습니다.
 
만약 이를 HikariCP(Connection Pool)을 사용하면 어떻게 될까요?
 
min
0.001230 ms
max
1.014051 ms
mean
0.003458 ms
p99
0.010263 ms
측정 값의 p99값이 10 마이크로초 이하로 측정됩니다. 이는 커넥션 풀을 사용하지 않았을 때보다 세 자릿 수(3배 아님) 이상 빠른 속도로 볼 수 있습니다. 이렇게 훨씬 빠르게 초당 트랜잭션을 처리할 수 있게 되면, 커넥션 획득과 트랜잭션 병목 현상이 일어나지 않을 수 있습니다. 물론 상황에 따라 다르게 처리될 수 있겠지만 효율적으로 쓴다면 커넥션 풀을 쓰는 것이 더 이상적입니다.
 
notion image
 
커넥션 풀에서 커넥션을 획득하려고 할 때, 물리적 DB 커넥션에 비해 커넥션 풀이 오버헤드가 훨씬 적습니다. 이는 풀 커넥션이 반복 재사용되기 때문이며, 커넥션 획득은 단지 스레드를 해당 커넥션을 위해 예약하는 문제에 불과하다는 것 입니다. 기본적으로 물리적 연결이 커넥션 풀을 사용하면 계속 열려있기 때문입니다.
 

3. Connection Pool의 동작과 역할

 
  • 커넥션 획득 과정
    • getConnection() -> 커넥션 있나요? YES -> 커넥션 획득 getConnection() -> 커넥션 있나요? NO -> 커넥션 풀이 최대 인가요? YES -> 대기 -> 획득 OR 타임아웃 getConnection() -> 커넥션 있나요? NO -> 커넥션 풀이 최대 인가요? NO -> 풀 사이즈 확장
    • getConnection() 요청 시:
      • 풀에 사용 가능한 커넥션이 있으면 즉시 획득합니다.
      • 없으면:
        • 풀이 최대 크기에 도달했는지 확인합니다.
        • 최대 크라면 일정 시간 대기 후 재시도하거나 타임아웃 예외를 발생시킵니다.
        • 최대 크가 아니라면 풀 사이즈를 확장하고 새로운 연결을 생성하여 반환합니다.
 
  • 리소스 제한 및 확장성 법칙
    •  
    • 데이터베이스 연결은 CPU, 메모리, I/O와 마찬가지로 제한된 시스템 리소스입니다. 무작정 늘릴 수 없습니다.
    • 특정 임계값을 초과하여 연결 수를 늘리면 오히려 연결 관리 오버헤드, 경합 등으로 인해 성능 문제가 악화될 수 있습니다.
    • 보편적 확장성 법칙(Universal Scalability Law)과 암달의 법칙(Amdahl's Law)은 시스템의 용량과 연결 수의 관계를 설명합니다. 시스템의 확장성은 병렬 처리 가능한 정도에 상대적입니다.
    • 일관성 비용(Coherency Cost)은 여러 동시 요청이 데이터 일관성을 유지하기 위해 발생하는 락킹, 동기화 등의 오버헤드를 의미하며, 이는 시스템이 최대로 활용할 수 있는 연결 수를 제한합니다.
    • DoS (서비스 거부) 공격 방어:
      • 커넥션 풀이 없을 때의 취약점: DoS 공격 시 모든 트래픽이 데이터베이스로 직접 전달되어, 데이터베이스 연결이 고갈되고 서비스가 마비될 수 있습니다 (예: PostgreSQL의 연결 제한). DBA조차 문제 해결이 어려워질 수 있습니다.
      • 커넥션 풀의 보호 효과: 커넥션 풀은 각 애플리케이션 노드별로 데이터베이스 연결 수를 제한합니다. 공격으로 인한 비정상 트래픽이 풀의 최대 연결 수를 넘어서면, 풀이 더 이상 데이터베이스로 새로운 연결 요청을 보내지 않고 프론트엔드/웹 서버 단에서 이를 차단하거나 대기시킵니다.
      • 이로써 공격받은 해당 노드만 영향을 받고, 데이터베이스와 나머지 시스템은 안정적으로 유지됩니다. 커넥션 풀은 안정성을 보호하는 필수적인 보안 메커니즘으로 기능합니다.
 

4. Hibernate가 제공하는 Connection Provider 구현체

 
Hibernate는 Connection을 획득할 때 4가지 구현체를 제공합니다.
  • DriverManager
  • C3P0
  • Hikari CP
  • DataSource Connection Provider
이 중 고성능 Application에 적합한 것은 무엇일까요?
 
  • DriverManagerConnectionProvider
    • 기능: 기본 JDBC DriverManager를 통해 데이터베이스 연결을 가져옵니다. 단순한 풀링(재사용) 구현으로 연결 획득 오버헤드를 줄이려 시도합니다. 실제 복잡한 커넥션 풀처럼 정교한 알고리즘을 사용하지는 않지만, 연결을 매번 새로 생성/종료하는 비효율을 피하기 위해 간단한 재사용 메커니즘을 내장하고 있다는 의미입니다. 즉, 개발자가 별도의 커넥션 풀 라이브러리를 설정하지 않아도 Hibernate가 최소한의 연결 재사용을 시도한다는 것입니다.
    • 용도 및 한계: 가장 중요한 점은 이것이 실제 운영 환경에서 사용하기 위한 것이 아니다라는 것입니다. 이는 이 Provider가 트랜잭션 관리, 동시성 제어, 연결 유효성 검사, 풀 크기 조절, 연결 타임아웃 처리 등 복잡한 운영 환경에서 필요한 기능들을 제대로 제공하지 못하기 때문입니다. 주로 초기 개발 단계나 간단한 테스트 용도로 사용되며, Hibernate는 프로덕션 환경에서 사용 시 로그에 경고 메시지를 출력하여 부적절함을 알립니다. Hibernate가 이를 제공하는 이유는 프레임워크 자체의 핵심 기능을 테스트하거나 데모를 만들 때, 불필요한 외부 커넥션 풀 라이브러리에 대한 의존성 없이 빠르게 시작할 수 있도록 하기 위함입니다.
  • C3P0
    • 특징: C3P0은 오랫동안 사용되어 오면서 프로덕션 환경에서 그 안정성과 신뢰성이 검증된 오픈소스 커넥션 풀입니다. 풍부한 설정 옵션을 제공하며, 다양한 상황에 맞게 튜닝이 가능합니다. 과거에는 가장 널리 사용되던 커넥션 풀 중 하나였습니다.
    • 추가 설명: C3P0은 견고하고 많은 기능을 제공하지만, 때로는 설정이 다소 복잡하고 다른 최신 커넥션 풀에 비해 성능 면에서 아주 미세하게 뒤쳐질 수 있다는 평가도 있습니다. 그럼에도 불구하고 여전히 많은 레거시 시스템이나 안정성을 중시하는 환경에서 활발히 사용되고 있습니다.
  • HikariConnectionProvider (HikariCP)
    • 특징: HikariCP는 현재 가장 빠르고 효율적인 커넥션 풀 라이브러리 중 하나로 꼽힙니다. 커넥션 풀을 네이티브(Native)로 지원한합니다. 이는 Hibernate가 HikariCP 라이브러리를 직접 통합하여 사용할 수 있는 Provider를 제공한다는 의미입니다. 즉, Hibernate 설정에서 HikariCP를 쉽게 연동하여 사용할 수 있다는 것입니다.
    • 추가 설명: HikariCP는 경량화되고 최적화된 내부 구조로 인해 매우 낮은 오버헤드를 자랑합니다. 최소한의 코드와 단순한 설정으로도 뛰어난 성능을 발휘하여, 고성능을 요구하는 최신 애플리케이션에서 매우 인기가 많습니다. 대부분의 자바 기반 웹 애플리케이션에서 커넥션 풀로 HikariCP를 선택하는 경우가 많습니다.
  • DataSourceConnectionProvider (권장)
    • 특징: 이 Provider는 Hibernate가 직접 커넥션 풀을 구현하거나 특정 커넥션 풀 라이브러리에 의존하는 방식이 아닙니다. 대신, 표준 JDBC DataSource 인터페이스를 통해 데이터베이스 연결을 획득합니다. 여기서 중요한 것은 이 DataSource 객체가 외부에서 관리되는 커넥션 풀 (예: WAS(Web Application Server)에 내장된 커넥션 풀, Spring Boot의 HikariCP 기본 설정)이라는 점입니다.
    • 트랜잭션 지원: 리소스 로컬 트랜잭션과 JTA(Java Transaction API) 트랜잭션 모두와 함께 작동할 수 있다는 큰 장점입니다.
      • 리소스 로컬 트랜잭션: 단일 데이터베이스 자원 내에서 관리되는 트랜잭션입니다 (예: JDBC Connection.commit(), rollback()). 대부분의 소규모 애플리케이션에서 사용됩니다.
      • JTA 트랜잭션: 여러 개의 분산된 자원(예: 여러 데이터베이스, 메시지 큐)에 걸쳐 하나의 트랜잭션을 관리하는 복잡한 트랜잭션 방식입니다. 엔터프라이즈 환경에서 분산 트랜잭션이 필요할 때 사용됩니다. DataSourceConnectionProvider는 이러한 복잡한 트랜잭션 시나리오를 지원할 수 있습니다.
    • 유연성:
      • DataSource Proxy 체인 연결: 이는 로깅(SQL 쿼리 로깅, 바인딩 파라미터 값 출력 등), 모니터링, 성능 통계 수집 등 다양한 목적으로 DataSource 객체 주위에 프록시를 래핑하여 추가 기능을 쉽게 삽입할 수 있다는 의미입니다. FlexyPool과 같은 커넥션 풀 모니터링 도구도 이런 방식으로 연동됩니다.
      • 이러한 유연성은 복잡한 엔터프라이즈 환경에서 데이터베이스 연결 관리에 대한 세밀한 제어와 모니터링이 가능하게 합니다.
 
가장 권장하는 것은?
 
현재 일반적인 자바 웹 애플리케이션 및 마이크로서비스 환경에서 가장 널리 권장되는 조합은 HikariCP를 커넥션 풀로 사용하고, 이를 DataSourceConnectionProvider를 통해 Hibernate에 연동하는 것입니다.
 
  • 성능: HikariCP의 압도적인 성능은 대부분의 경우 최상의 응답 시간과 처리량을 제공합니다.
  • 유연성 및 표준화: DataSourceConnectionProvider를 사용하면 JDBC 표준 DataSource 인터페이스를 따르기 때문에, 특정 커넥션 풀 라이브러리(HikariCP)에 대한 직접적인 의존성을 Hibernate 설정에서 분리할 수 있습니다. 또한, 애플리케이션 서버(WAS)가 제공하는 DataSource를 활용하거나, Spring Boot와 같이 내부적으로 HikariCP를 기본 DataSource로 사용하는 프레임워크와도 자연스럽게 통합됩니다.
  • 관리 용이성: DataSourceConnectionProvider는 JTA 트랜잭션과 같은 고급 기능을 지원하고, 프록시를 통한 모니터링 및 로깅 기능을 쉽게 추가할 수 있어 운영 환경에서의 관리 용이성도 뛰어납니다.
Share article

vlogue