안동민 개발노트 아이콘

안동민 개발노트

15장 : 실무 데이터베이스 운영

커넥션 관리

개발 환경에서는 잘 동작하던 DB가 운영 환경에서 불안정해지는 이유 중 하나가 커넥션 관리입니다. 데이터베이스 커넥션은 네트워크, 인증, 세션 상태, 서버 메모리를 사용하는 리소스이므로, 요청마다 새로 만들기보다 풀링과 제한을 통해 관리해야 합니다.


커넥션의 생성 과정

데이터베이스 커넥션을 생성하려면 TCP 연결, TLS 협상(사용 시), DB 프로토콜 핸드셰이크, 인증, 세션 초기화 과정을 거칩니다. 이 비용은 요청 처리 시간뿐 아니라 DB 서버의 메모리와 프로세스/스레드 자원에도 영향을 줍니다.


커넥션 풀의 개념

커넥션 풀은 커넥션을 재사용하는 기법입니다. 매 요청마다 커넥션을 생성/종료하는 대신, 풀에서 빌리고 반환합니다. 핵심은 “많이 열어두기”가 아니라 DB가 감당 가능한 수 안에서 대기와 처리량을 균형 있게 맞추는 것입니다.


커넥션 풀 구조


HikariCP — 대표적인 JDBC 커넥션 풀

Spring Boot 2.0+에서 기본으로 쓰이는 JDBC 커넥션 풀입니다. 설정 기본값이 보수적으로 잡혀 있지만, 운영에서는 앱 인스턴스 수, DB max_connections, 쿼리 지연 시간, 트랜잭션 길이를 함께 보고 조정해야 합니다.

파라미터설명기본값운영 기준
minimumIdle최소 유휴 커넥션maximumPoolSize고정 크기 풀은 미설정 또는 maximumPoolSize와 동일
maximumPoolSize최대 커넥션 수10앱 인스턴스 수 × 풀 크기 ≤ DB 허용 연결 수 안에서 부하 테스트
connectionTimeout커넥션 획득 대기30초사용자 요청 SLA보다 짧게, 보통 빠르게 실패하도록 조정
idleTimeout유휴 커넥션 유지600초minimumIdle < maximumPoolSize일 때 의미가 큼
maxLifetime커넥션 최대 수명1800초DB/방화벽/프록시 idle 제한보다 짧게
keepaliveTime유휴 커넥션 keepalive120초maxLifetime보다 짧게, 네트워크 idle 종료가 있을 때 검토
validationTimeout커넥션 유효성 검사5초장애 감지 지연과 false positive를 함께 고려
leakDetectionThreshold리크 감지 임계값0 (비활성)운영 상시 설정보다 장애 분석/스테이징에서 우선 활용
HikariCP 설정 (Spring Boot)
spring:
  datasource:
    hikari:
      minimum-idle: 10
      maximum-pool-size: 10
      connection-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1740000
      keepalive-time: 120000
      leak-detection-threshold: 60000

minimumIdle vs maximumPoolSize


다른 커넥션 풀 비교

풀/프록시특징환경
HikariCPSpring Boot 기본 JDBC 풀, 설정 단순Java
Apache DBCP2오래된 Java 프로젝트에서 자주 사용Java
Tomcat PoolTomcat 내장 JDBC 풀Java/Tomcat
Oracle UCPOracle JDBC/UCP, RAC/FCF 같은 기능과 연계Oracle
c3p0레거시 프로젝트에서 발견됨Java
pgBouncerPostgreSQL 앞단의 경량 외부 풀러PostgreSQL
ProxySQLMySQL 앞단 프록시, 라우팅/풀링/HA 보조MySQL

Oracle UCP (Universal Connection Pool)

Oracle은 자체 커넥션 풀인 UCP를 제공합니다. RAC, Active Data Guard 같은 고가용성 구성에서는 Fast Connection Failover(FCF)가 FAN/ONS 이벤트를 이용해 죽은 커넥션을 감지하고 풀에서 제거하는 데 도움을 줍니다. 다만 이미 진행 중이던 트랜잭션은 예외를 받을 수 있으므로 애플리케이션 재시도 설계가 함께 필요합니다.

Oracle UCP 설정
oracle.ucp.minPoolSize=5
oracle.ucp.maxPoolSize=20
oracle.ucp.initialPoolSize=10
oracle.ucp.connectionWaitTimeout=3
oracle.ucp.inactiveConnectionTimeout=300

RAC/HA 환경 추가 설정:
  oracle.ucp.FastConnectionFailoverEnabled=true
  → FAN/ONS 이벤트로 장애 커넥션을 빠르게 감지·제거
  → 진행 중 트랜잭션은 예외 후 재시도 경로 필요

pgBouncer (PostgreSQL 외부 풀)


Oracle processes, sessions 파라미터

Oracle 서버 측에서도 프로세스와 세션 수를 제한합니다. 이 값은 버전, 코어 수, CDB/PDB 구성, shared server 사용 여부에 따라 달라질 수 있으므로 현재 값을 확인한 뒤 산정해야 합니다.

Oracle 세션 설정
-- 현재 설정 확인
SHOW PARAMETER processes;
SHOW PARAMETER sessions;

-- 현재 활성 세션 수 확인
SELECT COUNT(*) FROM v$session WHERE status = 'ACTIVE';

-- 전체 세션 현황
SELECT username, status, COUNT(*)
FROM v$session
WHERE username IS NOT NULL
GROUP BY username, status
ORDER BY COUNT(*) DESC;

-- 변경 (재시작 필요)
ALTER SYSTEM SET processes = 500 SCOPE = SPFILE;
-- sessions는 자동 계산: (1.5 * processes) + 22
파라미터기본값/산정 방식설명
processes환경에 따라 산정동시에 접속 가능한 OS 사용자 프로세스 한계
sessions기본 산식: (1.5 × processes) + 22동시에 생성 가능한 세션 수
MySQL 커넥션 설정
-- 현재 설정 확인
SHOW VARIABLES LIKE 'max_connections';  -- MySQL 기본값은 보통 151
SHOW STATUS LIKE 'Threads_connected';   -- 현재 연결 수
SHOW STATUS LIKE 'Max_used_connections'; -- 최대 동시 연결 수

-- 변경
SET GLOBAL max_connections = 500;
-- 영구 적용은 설정 파일 또는 SET PERSIST로 관리
-- 관리자/복제/모니터링용 예비 연결도 별도로 남겨 둠

-- wait_timeout: 유휴 커넥션 자동 종료 시간
SHOW VARIABLES LIKE 'wait_timeout';  -- MySQL 기본값은 보통 28800초(8시간)
SET GLOBAL wait_timeout = 600;       -- 10분

Connection Leak 진단

커넥션을 빌려가서 반환하지 않으면 커넥션 리크(Connection Leak)가 발생합니다. 풀이 고갈되면 새 요청은 커넥션을 기다리다가 connectionTimeout에 걸리고, 원인은 리크뿐 아니라 긴 트랜잭션, 느린 쿼리, 외부 API 대기일 수도 있습니다.

증상원인
커넥션 타임아웃 빈발close() 호출 누락
활성 커넥션 수가 계속 증가try-with-resources 미사용
서버 재시작하면 일시적 해소예외 발생 시 finally에서 반환 안 함
특정 기능 사용 후 발생해당 코드에서 커넥션 미반환
Connection Leak 방지
// [나쁜 예] 예외 발생 시 커넥션 미반환
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.executeQuery();  // 여기서 예외 발생하면?
conn.close();          // 이 줄이 실행되지 않음 → 리크 발생

// [좋은 예] try-with-resources로 자동 반환
try (Connection conn = dataSource.getConnection();
     PreparedStatement pstmt = conn.prepareStatement(sql);
     ResultSet rs = pstmt.executeQuery()) {
    while (rs.next()) {
        // 결과 처리
    }
}  // 자동으로 rs, pstmt, conn.close() → 풀에 반환
// 예외가 발생해도 반드시 반환됨

커넥션 풀 모니터링


커넥션 유효성 검사

네트워크 장애, DB 재시작 등으로 풀 안의 커넥션이 끊어져 있을 수 있습니다. 유효성 검사로 죽은 커넥션을 미리 제거합니다.


Oracle Shared Server vs Dedicated Server

Oracle은 Dedicated Server와 Shared Server 방식을 제공합니다. Dedicated Server는 클라이언트마다 서버 프로세스가 배정되는 기본적인 형태이고, Shared Server는 많은 동시 연결을 더 적은 서버 프로세스로 처리하도록 설계된 구조입니다.

Dedicated Server (기본)
클라이언트 1 ──→ 서버 프로세스 1
클라이언트 2 ──→ 서버 프로세스 2
클라이언트 3 ──→ 서버 프로세스 3

장점: 각 클라이언트에 전용 프로세스 → 세션 상태와 실행 흐름이 단순
단점: 커넥션당 1개 프로세스 → 메모리 소비 큼
적합: 커넥션 수가 적고 응답 시간이 중요한 OLTP
Oracle 서버 모드 확인/설정
-- 현재 모드 확인
SELECT server FROM v$session WHERE sid = SYS_CONTEXT('USERENV', 'SID');

-- Shared Server 설정 (init 파라미터)
SHARED_SERVERS = 5         -- 공유 서버 프로세스 수
MAX_SHARED_SERVERS = 20    -- 최대 공유 서버 수
DISPATCHERS = '(PROTOCOL=TCP)(DISPATCHERS=2)'

컨테이너/클라우드 환경의 커넥션 관리


커넥션 풀 트러블슈팅

자주 발생하는 문제와 해결
문제 1: "Cannot acquire connection from pool"
  원인: 풀 고갈 (리크 또는 풀 크기 부족)
  진단: ActiveConnections == maximumPoolSize인지 확인
  해결: leakDetectionThreshold 일시 활성화, Slow Query 확인

문제 2: "Connection is not available, request timed out"
  원인: connectionTimeout 내에 커넥션 획득 실패
  진단: PendingThreads 수 확인
  해결: 리크/느린 쿼리/긴 트랜잭션 확인 후 풀 크기 또는 timeout 조정

문제 3: "Connection reset by peer"
  원인: 방화벽/DB가 유휴 커넥션 끊음
  진단: maxLifetime/keepaliveTime이 DB 또는 네트워크 idle 제한과 맞는지 확인
  해결: maxLifetime을 제한보다 짧게, 필요 시 keepaliveTime 설정

문제 4: "Too many connections" (MySQL)
  원인: 모든 앱서버 풀 합 > max_connections
  진단: SHOW STATUS LIKE 'Max_used_connections';
  해결: 풀 크기 축소, max_connections 증가, pgBouncer 도입

문제 5: "ORA-12519: TNS: no appropriate service handler found"
  원인: Oracle processes/sessions 한계 초과
  진단: SELECT COUNT(*) FROM v$session;
  해결: processes 파라미터 증가, Shared Server 검토

문제 6: 간헐적 쿼리 실패
  원인: 풀 안의 커넥션이 DB 재시작으로 끊어짐
  진단: 앱 로그에서 "Broken pipe" 확인
  해결: validationTimeout 설정, maxLifetime 단축

정리

커넥션 풀 설정의 핵심은 적절한 크기, 짧고 안전한 점유 시간, 리크 방지입니다. 풀이 너무 작으면 대기가 발생하고, 너무 크면 DB 메모리와 스케줄링 비용이 커집니다. 공식 하나로 결정하기보다 앱 인스턴스 수, DB 연결 한계, 평균 쿼리 시간, 트랜잭션 길이를 측정해 부하 테스트로 조정해야 합니다.

모든 커넥션은 try-with-resources 또는 프레임워크의 트랜잭션 경계로 반드시 반환하고, 필요할 때 leakDetectionThreshold로 의심 구간을 찾아야 합니다. 여러 앱 서버의 풀 크기 합이 DB의 연결 한계와 운영 여유분을 초과하지 않도록 전체 아키텍처 관점에서 관리하는 것이 중요합니다.

시험에서는 커넥션 풀의 개념과 필요성, 주요 설정 파라미터(maximumPoolSize, connectionTimeout, maxLifetime), 커넥션 리크의 원인과 방지 방법이 자주 출제됩니다.

다음 절에서는 모니터링과 성능 진단을 다루겠습니다.