infra
Platform

모듈 맵

[Database] DB 연결 지연을 없애는 HikariCP 설정과 대기 성능 튜닝

0 / 37 완료

펼치기
0 / 37 완료0%

Database · 37 / 37

[Database] DB 연결 지연을 없애는 HikariCP 설정과 대기 성능 튜닝

PgBouncer 운영 튜닝, HikariCP 정밀 설정, 풀 고갈 장애 사후 분석까지 — 프로덕션 DB 연결 관리 완전 정복

🚨INCIDENT ALERT
HIGH

트래픽이 갑자기 늘었는데 DB CPU보다 커넥션 수가 먼저 한계에 닿는 경우가 많습니다. 애플리케이션이 요청마다 새 연결을 만들면 DB는 쿼리보다 연결 관리에 시간을 씁니다. 커넥션 풀을 이해하면 API 서버와 DB 사이의 병목을 안정적으로 제어할 수 있습니다.

이번 챕터에서 배울 것

커넥션 풀을 설정하는 것과 운영하는 것은 다릅니다. 운영 단계에서는 풀 고갈, 누수(leak), 좀비 연결, 스케일아웃 시 연결 폭발 등 실제 장애 패턴을 다뤄야 합니다. 이 모듈은 모니터링 → 이상 감지 → 근본 원인 분석 → 설정 튜닝의 전체 사이클을 다룹니다.

  • 1커넥션 풀 고갈 패턴 — idle in transaction, 슬로우 쿼리 연결 점유
  • 2PgBouncer 운영 튜닝 — 모드별 트레이드오프와 정밀 설정
  • 3HikariCP 고급 설정 — connectionTimeout, leakDetection, keepalive
  • 4PgBouncer + HikariCP 2계층 아키텍처
  • 5Prometheus 기반 풀 메트릭 모니터링 & 알림 설계
  • 6RDS Proxy — 서버리스/컨테이너 환경의 관리형 풀링

Connection Pool 운영 & DB 성능 튜닝

2022년 여름, 서울 핀테크 스타트업 C사는 쿠버네티스 HPA가 트래픽 급증에 반응해 API 서버 파드를 5개에서 50개로 늘렸습니다. 10분 뒤 DB 알람이 울렸습니다. max_connections exceeded. 각 파드에는 HikariCP 풀이 20개 연결을 맺도록 설정되어 있었고, 50개 파드 × 20 = 1,000개 연결이 PostgreSQL(max_connections=200)로 몰려들었습니다.

개발팀은 급하게 max_connections=1000으로 올렸습니다. 그리고 10분 뒤 CPU 100%, 전체 처리량 이전의 30%로 추락. 연결 수가 늘수록 느려지는 역설이 발생했습니다.

PgBouncer를 이미 구성해 두었다면? 파드 50개가 PgBouncer로 연결하고, PgBouncer가 실제 DB 연결을 30개로 유지했을 것입니다. 장애 없이 끝났을 상황입니다.


실습 환경 준비

로컬 터미널
# PgBouncer 설치
sudo apt install -y pgbouncer

# pgbench 초기화 (부하 테스트용 샘플 데이터)
pgbench -i -s 100 -U postgres mydb
# -s 100: scale factor 100 = pgbench_accounts 테이블에 1000만 건

# 기본 부하 테스트 (PgBouncer 없이)
pgbench -U postgres -c 50 -j 4 -T 30 mydb
# -c 50: 클라이언트 50개, -j 4: 스레드 4개, -T 30: 30초

# Prometheus + Grafana 로컬 실행
docker run -d -p 9090:9090 prom/prometheus
docker run -d -p 3000:3000 grafana/grafana
OUTPUT
실행 완료 또는 조회 결과가 표시됩니다.
🔍실행 후 확인할 것
  • 활성 연결 수풀 크기 안에서 커넥션이 재사용되는지 확인합니다.
  • 대기 시간요청이 커넥션을 기다리느라 지연되지 않는지 봅니다.
  • DB 한계max_connections에 여유가 남는지 운영 기준으로 확인합니다.
INI
# /etc/pgbouncer/pgbouncer.ini 기본 설정
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
listen_port = 6432
listen_addr = 0.0.0.0
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction
max_client_conn = 2000
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3

# 타임아웃 설정 (장애 방지 핵심)
server_idle_timeout = 600
client_idle_timeout = 0
idle_transaction_timeout = 30    # 30초 이상 idle in transaction → 강제 해제
query_timeout = 0                # 0 = 무제한 (슬로우 쿼리 제어는 DB 측에서)
client_login_timeout = 60

# 연결 상태 모니터링
stats_period = 60
log_connections = 0
log_disconnections = 0
log_pooler_errors = 1

💡개념

풀 고갈의 3가지 패턴과 진단

트래픽이 갑자기 몰렸을 때 DB 연결이 부족해지면서 요청이 대기 상태에 들어갑니다. 서비스는 DB가 느린 게 아닌데 응답이 없습니다. 커넥션 풀이 고갈된 겁니다. 원인을 찾으려면 어디서 연결이 새고 있는지 진단할 수 있어야 합니다. 풀 고갈에는 반복되는 패턴이 있고, 패턴을 알면 빠르게 원인을 좁힐 수 있습니다.

풀 고갈의 3가지 패턴과 진단

패턴 1: idle in transaction 누적

가장 흔한 풀 고갈 원인. 애플리케이션 코드에서 트랜잭션을 열고 예외 발생 시 COMMIT/ROLLBACK 없이 연결이 풀로 반환되면, DB 서버에는 해당 연결이 'idle in transaction' 상태로 남습니다. 이 연결은 PgBouncer가 회수할 수 없어 점진적으로 쌓입니다.

SQL
-- PostgreSQL에서 현재 idle in transaction 세션 확인
SELECT
  pid,
  client_addr,
  application_name,
  state,
  now() - state_change AS idle_duration,
  LEFT(query, 80) AS last_query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY idle_duration DESC;

-- 5분 이상 idle in transaction 세션 강제 종료
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND now() - state_change > INTERVAL '5 minutes';
Java
// 나쁜 예: 예외 발생 시 트랜잭션 미처리
public void processOrder(Long orderId) {
    Connection conn = dataSource.getConnection();
    conn.setAutoCommit(false);
    // ... 쿼리 실행 ...
    // 예외 발생 시 conn.rollback() 없이 메서드 종료
    // → 연결이 풀에 반환되지만 DB에는 idle in transaction으로 남음
    conn.commit();
}

// 좋은 예: try-finally 또는 try-with-resources로 반드시 처리
public void processOrder(Long orderId) {
    try (Connection conn = dataSource.getConnection()) {
        conn.setAutoCommit(false);
        try {
            // ... 쿼리 실행 ...
            conn.commit();
        } catch (Exception e) {
            conn.rollback();  // 반드시 롤백
            throw e;
        }
    }  // 자동으로 conn.close() → 풀에 반환
}

패턴 2: 슬로우 쿼리의 연결 점유

쿼리 하나가 30초 걸리면, 그 연결은 30초 동안 풀에서 사라집니다. 풀 사이즈가 10이고 모두 느린 쿼리를 실행 중이면 풀은 고갈됩니다.

SQL
-- 현재 30초 이상 실행 중인 쿼리 확인
SELECT
  pid,
  now() - query_start AS duration,
  state,
  wait_event_type,
  wait_event,
  LEFT(query, 100) AS query_snippet
FROM pg_stat_activity
WHERE state = 'active'
  AND now() - query_start > INTERVAL '30 seconds'
ORDER BY duration DESC;

-- HikariCP에서 연결 점유 시간 분포 확인 (Prometheus 쿼리)
-- histogram_quantile(0.99, hikaricp_connection_usage_nanos_bucket) / 1e9
-- → p99 연결 점유 시간 (초)

패턴 3: 연결 누수 (Connection Leak)

연결을 가져갔지만 반환하지 않는 코드 버그. 조용히 축적되다가 어느 날 풀이 고갈됩니다.

YAML
# HikariCP 연결 누수 감지 설정 (application.yml)
spring:
  datasource:
    hikari:
      leak-detection-threshold: 5000  # 5초 이상 반환 안 된 연결 경고
      # 로그 출력 예:
      # Connection leak detection triggered for com.zaxxer.hikari.pool.ProxyConnection
      # Stack trace of leaked connection:
      #   at com.example.service.OrderService.processOrder(OrderService.java:47)
DB 클라이언트
# PgBouncer에서 풀 상태 실시간 확인
# PgBouncer 관리 DB 접속 (포트 6432, 데이터베이스명 'pgbouncer')
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer

SHOW POOLS;
-- database | user | cl_active | cl_waiting | sv_active | sv_idle | sv_used | sv_tested | sv_login | maxwait
-- mydb     | app  | 23        | 7          | 20        | 5       | 0       | 0         | 0        | 2
-- cl_waiting=7: 대기 중인 클라이언트 (풀 부족 신호)
-- maxwait=2: 최대 대기 시간(초)

SHOW STATS;
-- total_query_count, total_query_time, avg_query_time 등

SHOW CLIENTS;
-- 연결된 클라이언트 목록과 상태
💡개념

HikariCP 고급 설정 — 프로덕션 튜닝

앱 인스턴스를 늘렸는데 DB에서 too many connections 오류가 납니다. 인스턴스별로 커넥션 풀을 독립적으로 설정하다 보니 전체 DB 연결 수가 max_connections를 초과했습니다. 풀 사이즈를 무조건 크게 잡으면 DB가 연결 관리 오버헤드로 느려집니다. HikariCP 설정은 CPU 코어 수, 인스턴스 수, DB 설정을 함께 고려해야 합니다. 계산 공식과 주요 파라미터를 알면 환경에 맞는 값을 산출할 수 있습니다.

풀 사이즈 계산과 멀티 인스턴스 설계

단일 인스턴스 기준 공식: connections = (CPU 코어 수 × 2) + 유효 디스크 수 멀티 인스턴스 환경: 총 DB 연결 = 인스턴스 수 × 풀 사이즈 ≤ max_connections × 0.8

YAML
# Spring Boot application.yml — 프로덕션 권장 설정
spring:
  datasource:
    url: jdbc:postgresql://pgbouncer-host:6432/mydb?ApplicationName=order-service
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    hikari:
      # 풀 크기: (4코어 × 2) + 1 SSD = 9. 여유분 포함해 10
      maximum-pool-size: 10
      minimum-idle: 3          # 최소 유지 유휴 연결

      # 타임아웃 설정 (ms 단위)
      connection-timeout: 3000  # 3초: 풀에서 연결 못 받으면 즉시 실패 (기본 30초는 너무 김)
      idle-timeout: 600000      # 10분: 유휴 연결 제거 (PgBouncer server_idle_timeout보다 짧게)
      max-lifetime: 1800000     # 30분: 연결 최대 수명 (DB의 wait_timeout - 30초)
      keepalive-time: 60000     # 1분: 유휴 연결 유효성 확인 (방화벽 끊김 방지)

      # 연결 유효성 검사
      connection-test-query: SELECT 1
      validation-timeout: 2000  # 유효성 검사 타임아웃

      # 누수 감지 (개발/스테이징)
      leak-detection-threshold: 10000 # 10초 이상 반환 안 된 연결 경고

      pool-name: OrderService-HikariPool

      # Prometheus 메트릭 노출
      register-mbeans: true

connection-timeout 설정의 중요성

기본값 30초는 대부분의 상황에서 너무 깁니다. 풀이 고갈됐을 때 30초 동안 기다리는 요청들이 쌓이면 HTTP 타임아웃, 상위 서비스 연쇄 장애로 이어집니다.

Java
// connection-timeout = 3000 (3초) 설정 시 동작
// 풀에 연결이 없으면 3초 후 즉시 SQLException
// → 빠른 실패(fail-fast)로 상위 서비스에 즉시 에러 전파
// → Circuit Breaker가 빠르게 발동해 연쇄 장애 방지

// 응용: Circuit Breaker와 함께 사용
@CircuitBreaker(name = "database", fallbackMethod = "fallbackGetOrder")
public Order getOrder(Long orderId) {
    return orderRepository.findById(orderId).orElseThrow();
}

public Order fallbackGetOrder(Long orderId, Exception ex) {
    log.error("DB 연결 실패, 캐시에서 조회: {}", orderId);
    return orderCache.get(orderId);
}

max-lifetime과 DB wait_timeout 정합성

DB 서버의 wait_timeout(MySQL) 또는 tcp_keepalives_idle(PostgreSQL)보다 HikariCP의 max-lifetime이 길면, DB가 연결을 먼저 끊어버려 "Connection is closed" 에러가 발생합니다.

SQL
-- MySQL: wait_timeout 확인
SHOW VARIABLES LIKE 'wait_timeout';  -- 기본 28800초 (8시간)

-- PostgreSQL: 연결 유지 설정 확인
SHOW tcp_keepalives_idle;
SHOW tcp_keepalives_interval;
YAML
# 규칙: max-lifetime < DB wait_timeout - 30초
# MySQL wait_timeout = 28800초(8시간)이면
# max-lifetime = 28770000ms (8시간 - 30초)

# 실무 권장: 30분으로 설정 (대부분의 DB 설정보다 짧음)
max-lifetime: 1800000  # 30분
💡개념

2계층 풀링 아키텍처 & 모니터링

앱 인스턴스가 10개로 늘어났고 각 인스턴스 HikariCP 풀 크기가 10입니다. 이론상 DB에 최대 100개의 연결이 동시에 열릴 수 있습니다. PostgreSQL의 max_connections가 100이라면 가끔 연결 거부가 발생합니다. PgBouncer를 앞에 두면 실제 DB 연결을 25개로 제한하면서 앱 단의 100개 연결 요청을 다중화할 수 있습니다. 2계층 풀링은 대규모 앱 배포에서 DB 연결 수를 통제하는 표준 패턴입니다.

PgBouncer + HikariCP 2계층 아키텍처

앱 인스턴스 1 [HikariCP: 10개]
앱 인스턴스 2 [HikariCP: 10개]   →  PgBouncer [실제 DB 연결: 25개]  →  PostgreSQL
앱 인스턴스 3 [HikariCP: 10개]
              ...
앱 인스턴스 N [HikariCP: 10개]

클라이언트 연결: N × 10개  →  DB 실제 연결: 25개 (고정)

HikariCP는 각 JVM 인스턴스 내에서 빠른 연결 재사용을 처리하고, PgBouncer는 여러 인스턴스 전체의 DB 연결 총량을 통제합니다. 쿠버네티스에서 파드가 100개로 늘어나도 DB 연결은 25개로 유지됩니다.

로컬 터미널
# PgBouncer 부하 테스트 비교
# PgBouncer 없이 (PostgreSQL 직접 연결)
pgbench -U postgres -h postgres-host -p 5432 \
  -c 200 -j 8 -T 60 mydb
# 결과: TPS=823, 연결 에러 다수 (max_connections 초과)

# PgBouncer를 통한 연결
pgbench -U postgres -h pgbouncer-host -p 6432 \
  -c 200 -j 8 -T 60 mydb
# 결과: TPS=3847, 에러 0 (PgBouncer가 200개를 25개로 관리)

Prometheus 기반 모니터링 설정

YAML
# docker-compose.yml — PgBouncer exporter 포함
version: '3.8'
services:
  pgbouncer-exporter:
    image: prometheuscommunity/pgbouncer-exporter:latest
    environment:
      PGBOUNCER_EXPORTER_HOST: pgbouncer
      PGBOUNCER_EXPORTER_PORT: 6432
      PGBOUNCER_EXPORTER_USER: pgbouncer
      PGBOUNCER_EXPORTER_PASSWORD: ${PGBOUNCER_ADMIN_PASS}
    ports:
      - "9127:9127"
YAML
# Prometheus 알림 규칙 (alert.rules.yml)
groups:
  - name: connection_pool
    rules:
      - alert: HikariPoolPendingHigh
        expr: hikaricp_connections_pending > 3
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "HikariCP 풀 대기 세션 증가"
          description: "{{ $labels.pool }} 풀에서 {{ $value }}개 요청이 연결 대기 중"

      - alert: PgBouncerClientWaiting
        expr: pgbouncer_stats_clients_waiting > 10
        for: 30s
        labels:
          severity: critical
        annotations:
          summary: "PgBouncer 클라이언트 대기 급증"

      - alert: IdleInTransactionSpike
        expr: |
          pg_stat_activity_count{state="idle in transaction"} > 20
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "idle in transaction 세션 급증 — 연결 누수 가능성"
SQL
-- 핵심 모니터링 쿼리 (정기 실행 권장)
-- 1. 연결 상태 분포
SELECT state, count(*) AS cnt
FROM pg_stat_activity
WHERE datname = 'mydb'
GROUP BY state
ORDER BY cnt DESC;

-- 2. 애플리케이션별 연결 수
SELECT application_name, count(*) AS connections
FROM pg_stat_activity
WHERE datname = 'mydb'
GROUP BY application_name
ORDER BY connections DESC;

-- 3. 가장 오래 실행 중인 쿼리 TOP 5
SELECT
  pid,
  ROUND(EXTRACT(EPOCH FROM (now() - query_start))::NUMERIC, 1) AS seconds,
  state,
  LEFT(query, 100) AS query
FROM pg_stat_activity
WHERE state IN ('active', 'idle in transaction')
ORDER BY query_start ASC
LIMIT 5;

실습 — 풀 고갈 재현 및 복구

DB 클라이언트
# Step 1: 인위적으로 풀 고갈 상황 재현
# PgBouncer default_pool_size = 5로 임시 축소
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "
  SET default_pool_size = 5;
  RELOAD;
"

# Step 2: 동시 접속 50개로 부하 생성
pgbench -h 127.0.0.1 -p 6432 -U app -c 50 -j 4 -T 30 mydb &

# Step 3: 실시간 상태 모니터링 (다른 터미널)
watch -n 1 'psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "SHOW POOLS;"'
-- SHOW POOLS 출력 (풀 고갈 상태)
 database | user | cl_active | cl_waiting | sv_active | sv_idle | maxwait
 mydb     | app  | 5         | 45         | 5         | 0       | 8
-- cl_waiting=45: 45개 클라이언트가 연결 대기
-- maxwait=8: 최대 8초 대기 중
DB 클라이언트
# Step 4: 풀 사이즈 복구
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "
  SET default_pool_size = 25;
  RELOAD;
"

# 복구 후 SHOW POOLS
# cl_waiting=0, sv_active=25, maxwait=0
DB 클라이언트
# Step 5: idle in transaction 시뮬레이션
psql -h 127.0.0.1 -p 6432 -U app mydb -c "BEGIN;"
# 위 세션을 그대로 두고 다른 터미널에서 30초 후 확인

psql -U postgres -c "
  SELECT pid, state, now() - state_change AS idle_time
  FROM pg_stat_activity
  WHERE state = 'idle in transaction';
"
# PgBouncer의 idle_transaction_timeout=30 설정으로 자동 해제됨

상황: 갑자기 API 서버 전체에서 이 에러가 터지기 시작합니다. 풀 크기는 충분해 보이는데 모든 연결이 고갈됩니다.

원인 분석: 트랜잭션을 열어둔 채 반환하지 않은 연결이 풀을 고갈시킵니다.

SQL
-- 1. 현재 DB 연결 상태 확인
SELECT state, count(*)
FROM pg_stat_activity
WHERE datname = 'mydb'
GROUP BY state;

-- 결과:
-- idle in transaction | 47   ← 범인 발견
-- active              | 2
-- idle                | 1

47개의 'idle in transaction' 세션이 모든 풀 연결을 점유하고 있었습니다. 배포된 코드에서 특정 API 핸들러가 트랜잭션을 열고 외부 HTTP API를 호출한 뒤 타임아웃이 나도 롤백 없이 반환되는 버그가 있었습니다.

Java
// 버그 있는 코드
@Transactional
public void checkout(Long userId) {
    Order order = orderRepo.save(new Order(userId));  // 트랜잭션 시작
    paymentApi.charge(order.getId());  // 외부 API — 타임아웃 발생
    // @Transactional이 예외를 잡아 롤백하려 하지만
    // 외부 API 호출에서 RuntimeException이 아닌 checked exception이면
    // 기본 설정에서 롤백이 일어나지 않음
}

// 수정된 코드
@Transactional(rollbackFor = Exception.class)
public void checkout(Long userId) {
    Order order = orderRepo.save(new Order(userId));
    try {
        paymentApi.charge(order.getId());
    } catch (Exception e) {
        throw new RuntimeException("결제 실패", e);  // 롤백 트리거
    }
}

즉시 복구: 장시간 열려있는 트랜잭션을 강제 종료해 풀을 되살립니다.

SQL
-- 5분 이상 idle in transaction 강제 종료
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND now() - state_change > INTERVAL '5 minutes';

재발 방지: PgBouncer idle_transaction_timeout = 30 설정으로 30초 이상 idle in transaction은 자동 해제. HikariCP leak-detection-threshold = 10000 (10초)으로 누수 즉시 로그 출력.

상황: 애플리케이션 재배포 후 DB 연결이 전혀 되지 않습니다. PgBouncer 로그에 인증 실패가 반복됩니다. PostgreSQL에 직접 접속은 성공합니다.

원인: PgBouncer는 auth_file(userlist.txt)에 저장된 자체 사용자 목록으로 먼저 인증합니다. PostgreSQL 비밀번호를 변경했는데 PgBouncer의 userlist.txt를 업데이트하지 않은 경우, 또는 auth_type = scram-sha-256으로 변경했는데 userlist.txt가 md5 해시만 가진 경우에 발생합니다.

로컬 터미널
# PgBouncer userlist.txt 형식 확인
cat /etc/pgbouncer/userlist.txt
# "app" "md5해시값"  ← md5 방식
# "app" "SCRAM-SHA-256$......"  ← scram 방식

# PostgreSQL에서 SCRAM 해시 추출
psql -U postgres -c "
  SELECT usename, passwd
  FROM pg_shadow
  WHERE usename = 'app';
"
# passwd 값을 userlist.txt에 그대로 복사

# 또는 auth_query 방식으로 PgBouncer가 PostgreSQL에 직접 인증을 위임
# pgbouncer.ini에 추가:
# auth_query = SELECT usename, passwd FROM pg_shadow WHERE usename=$1
# auth_user = pgbouncer_auth  # pg_shadow 읽기 권한을 가진 전용 계정

# 설정 재로드
sudo systemctl reload pgbouncer
# 또는
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "RELOAD;"

교훈: 비밀번호 순환(rotation) 정책이 있는 환경에서는 PgBouncer auth_query 방식이 안전합니다. userlist.txt를 수동으로 관리하면 비밀번호 변경 때마다 PgBouncer도 업데이트해야 합니다.

💼
실무 맥락
현업 패턴

실무 시나리오: 쿠버네티스 스케일아웃 대응 DB 연결 설계

쿠버네티스 HPA가 부하에 따라 파드 수를 3~50개로 조정하는 환경에서 DB 연결 설계.

잘못된 설계 (흔한 실수):

  • 각 파드 HikariCP maximum-pool-size: 20
  • 50파드 × 20 = 1,000 연결 → PostgreSQL max_connections: 200 초과 → 장애

올바른 설계:

  1. PgBouncer를 DB 앞에 배치 (Deployment로 2개 레플리카, 각각 max_client_conn: 2000, default_pool_size: 15)
  2. 파드는 PgBouncer에 연결, HikariCP maximum-pool-size: 5 (파드 내 빠른 재사용만)
  3. 50파드 × 5 = 250개 → PgBouncer 실제 DB 연결: 30개 (고정)
  4. PostgreSQL max_connections: 100 — 여유분 충분

모니터링 체계:

  • hikaricp_connections_pending > 2이면 PgBouncer pool_size 검토
  • pg_stat_activity idle in transaction > 10이면 코드 버그 의심
  • 매주 pg_stat_statements로 슬로우 쿼리 TOP 10 리뷰

면접 포인트: "max_connections를 늘리면 되지 않나요?"에 대한 답: CPU 코어 수 이상의 연결은 컨텍스트 스위칭 비용으로 성능 역전이 발생합니다. 올바른 접근은 PgBouncer로 실제 DB 연결을 최소로 유지하면서 애플리케이션 쪽 동시 요청은 무제한에 가깝게 처리하는 것입니다.

여기까지 Database 트랙의 모든 모듈을 완료했습니다. SQL 기초부터 트랜잭션, 인덱스, 쿼리 최적화, NoSQL, 고가용성, 커넥션 풀까지 — 실무 백엔드 개발자가 DB와 직접 마주치는 거의 모든 상황을 다뤘습니다.

지식 확인

퀴즈 — 5문제

Q1

PgBouncer Transaction 모드에서 'idle in transaction' 상태 세션이 폭발적으로 증가했다. 원인과 해결책은?

Q2

HikariCP에서 connectionTimeout과 idleTimeout의 차이는?

Q3

Prometheus + Grafana로 모니터링할 때 'hikaricp_connections_pending' 메트릭이 지속적으로 5 이상이라면 첫 번째로 확인해야 할 것은?

Q4

PgBouncer와 HikariCP를 동시에 사용하는 아키텍처의 장점은?

Q5

PostgreSQL에서 max_connections를 500으로 올렸더니 오히려 전체 처리량이 줄었다. 이유는?

0 / 5 답변

🧪 실습으로 확인하기

PostgreSQL 설치 및 기본 설정

초급

Ubuntu 서버에 PostgreSQL을 설치하고, 데이터베이스와 사용자를 생성한 뒤 외부 접속이 가능하도록 설정한다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점