[Concurrency] Connection Pool: DB, HTTP, gRPC 의 공통 패턴
정의
Connection Pool = 연결을 미리 만들어 두고 재사용. 매 요청마다 TCP + TLS + auth 비용을 회피.
Pool 의 동작 = producer (request) / consumer (worker thread) 의 blocking queue 직관과 일치.
왜 필요?
| 매 요청 새 연결 | Connection Pool |
|---|---|
| TCP handshake (~ms) | 0 (재사용) |
| TLS handshake (~수십 ms) | 0 |
| DB auth + session 설정 | 0 |
| OS file descriptor 한도 부담 | 제한적 |
| TIME_WAIT socket 폭증 | 없음 |
연결 생명주기
flowchart TD
Created["생성 (TCP + TLS + auth)"] --> Idle[Idle]
Idle -->|acquire| Active[Active]
Active -->|release| Idle
Active -->|에러| Expired[폐기]
Idle -->|idle timeout| Expired
Idle -->|max lifetime| Expired
Expired -->|"min_size 유지"| Created
연결이 max_lifetime 에 달하기 전에 교체되어야 LB / firewall 의 idle disconnect 로 인한 stale 에러를 피할 수 있다.
흐름
sequenceDiagram
autonumber
participant App
participant Pool
participant DB
App->>Pool: acquire()
alt 가용 연결 있음
Pool-->>App: connection
else 모두 사용 중
Pool->>Pool: 새 연결 생성 (max 까지)
alt max 미도달
Pool->>DB: 새 TCP/TLS/auth
Pool-->>App: 새 connection
else max 도달
Pool-->>App: wait queue
end
end
App->>DB: SELECT ...
DB-->>App: rows
App->>Pool: release()
Pool-->>Pool: 연결 풀에 반환
주요 파라미터
| 파라미터 | 의미 |
|---|---|
min_size / idle | 최소 idle 연결 |
max_size | 최대 연결 |
acquire_timeout | acquire 대기 한도 |
idle_timeout | idle 연결 만료 |
max_lifetime | 연결의 최대 수명 |
validation_query | health check (SELECT 1) |
leak_detection | 반환 안 된 연결 추적 |
Pool 크기 계산
Pool size ≈ (cores × 2) + effective_spindle_count
- CPU 가 N 코어 면 동시 처리 가능한 작업 ~ 2N.
- 너무 큼 = context switch / lock 경쟁 / DB 부담.
- 너무 작음 = wait queue 길어짐.
Little’s Law
L = λ × W
연결 수 = 처리량 (req/s) × 응답 시간 (s)
예: 1000 req/s, 100ms 응답 → 100 connections 가 충분.
IMPORTANT
대부분의 운영 사고는 pool size 가 너무 크기 때문. PG 의 max_connections=200 에 50 app instance × pool=10 = 500 시도 → DB 거절.
DB Connection Pool 구조
flowchart TB
subgraph App[App instances]
A1[App 1<br/>pool 10]
A2[App 2<br/>pool 10]
A3[App N<br/>pool 10]
end
App --> PgB[PgBouncer<br/>session/transaction pool]
PgB --> PG[(PostgreSQL<br/>max 100)]
PgBouncer 같은 external pooler 가 N × app pool 을 PG 의 작은 pool 로 압축.
| Mode | 의미 |
|---|---|
session | 클라이언트가 disconnect 할 때까지 연결 점유 |
transaction | 트랜잭션 동안만 (가장 효율) |
statement | 매 statement (위험, prepared 안 됨) |
HTTP Connection Pool
flowchart LR
Client["HTTP Client<br/>(axios, OkHttp, ...)"] -->|pool| Conn1[connection 1]
Client -->|pool| Conn2[connection 2]
Client -->|pool| Conn3[connection 3]
Conn1 & Conn2 & Conn3 --> Server[Server]
Note["HTTP/1.1: per-host pool<br/>HTTP/2: 1 connection 의 multiplexing"]
HikariCP 설정 (Java)
Java 생태계에서 가장 빠른 JDBC pool. Spring Boot 의 기본값이다.
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://host:5432/mydb");
config.setUsername("app_user");
config.setPassword("password");
// 핵심 파라미터
config.setMinimumIdle(5); // 최소 idle 연결
config.setMaximumPoolSize(20); // 최대 연결
config.setConnectionTimeout(3_000); // acquire 대기 최대 3s
config.setIdleTimeout(600_000); // idle 연결 10분 후 만료
config.setMaxLifetime(1_800_000); // 연결 최대 수명 30분
config.setLeakDetectionThreshold(2_000); // 2s 반환 안 되면 경고
HikariDataSource dataSource = new HikariDataSource(config);
TIP
maxLifetime 은 DB 또는 LB 의 idle timeout 보다 짧게 설정. 그렇지 않으면 LB 가 서버 측 연결을 끊었을 때 stale connection 에러가 난다.
# application.yml (Spring Boot)
spring:
datasource:
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 3000
idle-timeout: 600000
max-lifetime: 1800000
PgBouncer 설정
PostgreSQL 전용 외부 connection pooler. 여러 app 인스턴스의 연결을 PG 의 작은 pool 로 압축한다.
[databases]
mydb = host=postgres-host port=5432 dbname=mydb
[pgbouncer]
listen_port = 6432
listen_addr = *
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 600
server_lifetime = 3600
transaction mode 가 효율이 가장 높다. 단, prepared statement 와 일부 세션 변수 (SET LOCAL, LISTEN/NOTIFY) 는 transaction 경계를 넘으면 동작하지 않는다.
Python SQLAlchemy 풀 설정
from sqlalchemy import create_engine
engine = create_engine(
"postgresql+psycopg2://user:pass@host:5432/db",
pool_size=10, # 기본 pool 크기
max_overflow=5, # pool_size 초과 허용 (임시)
pool_timeout=3, # acquire 대기 최대 3s
pool_recycle=1800, # 30분마다 연결 재생성 (stale 방지)
pool_pre_ping=True, # acquire 전 SELECT 1 validation
)
pool_pre_ping=True 가 stale connection 에러를 대부분 제거한다. 비용은 acquire 시 SELECT 1 1회.
모니터링 지표
| 메트릭 | 의미 | 주의 신호 |
|---|---|---|
active_connections | 현재 사용 중인 연결 수 | max_size 의 90% 이상 지속 |
idle_connections | 대기 중인 연결 수 | min_size 아래로 떨어지면 부족 |
wait_queue_length | acquire 대기 요청 수 | 0 을 초과하면 pool 부족 |
connection_create_rate | 초당 새 연결 생성 수 | 높으면 pool 크기 부족 또는 leak |
checkout_timeout_rate | acquire 실패율 | 어느 값이든 즉각 조치 |
connection_error_rate | 연결 에러율 | DB 이슈 또는 stale 연결 |
HikariCP: Prometheus endpoint 또는 JMX 로 위 지표 모두 노출 가능.
gRPC 연결 풀
gRPC 는 HTTP/2 multiplexing 으로 단일 채널에서 수천 동시 스트림 처리가 가능하다. DB 처럼 대규모 pool 이 필요 없다.
// Go: grpc.ClientConn 재사용 (pool 없이도 됨)
conn, err := grpc.Dial("service:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// 여러 goroutine 에서 같은 conn 동시 사용 가능
client := pb.NewUserServiceClient(conn)
pool 이 필요한 경우는 백엔드 인스턴스가 여럿이라 여러 address 로 분산할 때다.
흔한 함정
WARNING
- Pool size 너무 큼 = DB 거절 + thrashing. Little’s Law 로 계산.
max_lifetime없음 = LB / firewall 의 idle disconnect 후 stale connection 시도 → 에러.- Leak (release 안 함) = pool 고갈. try-with-resources / context manager 강제.
- Validation query 부재 = stale 연결로 간헐적 에러.
SELECT 1또는 pool 자체의 keep-alive.
HikariCP / 표준 라이브러리
| 라이브러리 | 언어 |
|---|---|
| HikariCP | Java (가장 빠름) |
| pgx / pgxpool | Go |
| psycopg pool, SQLAlchemy | Python |
| node-postgres | Node |
| ActiveRecord | Ruby |
관련 위키
- postgresql, mysql-innodb
- Load Balancer
- backpressure
- redis, 외부 세션 store 및 connection pool의 대안
- stateless, 외부 store 에 의존하는 이유
이 글의 용어 (6개)
- [Concurrency] Backpressure: 흐름 제어로 시스템 보호concurrency
- 정의 Backpressure = 생산자가 소비자 속도에 맞춰 자기 속도 조절. 분산 시스템의 cascade failure 방지. [!IMPORTANT] Backpressure 의…
- [DB Internals] Redis 8 / Valkey 9: 라이센스 분기, 신 데이터 구조, 실전 운영database-internals
- 정의 Redis 는 in-memory key-value 데이터 구조 서버. 2009년 Salvatore Sanfilippo (antirez) 가 Lua Manuscripts 의 …
- [DB] MySQL / InnoDB: clustered index, redo log, MVCCdatabase-internals
- 정의 MySQL + InnoDB (기본 스토리지 엔진) 의 clustered index + redo log 기반 행 기반 RDBMS. 웹 / SaaS 의 가장 흔한 backbon…
- [DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성database-internals
- 정의 PostgreSQL 은 오픈소스 ORDBMS. 1986 UC Berkeley POSTGRES 의 후예. MVCC, 확장 가능 타입, JSONB, full-text searc…
- [Network] Load Balancer: L4 vs L7, 알고리즘, sticky sessionnetwork
- 정의 Load Balancer 는 트래픽을 여러 백엔드로 분산 하는 인프라. 수평 확장 + 가용성 + 무중단 배포 의 토대. L4 vs L7 | 구분 | L4 (Transport…
- Statelessconcurrency
- 정의 Stateless (무상태) 서버는 클라이언트의 요청 간 상태를 기억하지 않는다. 각 요청은 완전히 독립적이며, 처리에 필요한 모든 정보는 요청 자체에 담겨 있어야 한다. …
💬 댓글