Quick Reference
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxIdleTime(5 * time.Minute)
db.SetConnMaxLifetime(30 * time.Minute)stats := db.Stats()
log.Printf("open=%d inuse=%d idle=%d wait=%d",
stats.OpenConnections,
stats.InUse,
stats.Idle,
stats.WaitCount,
)pool 경계
sql.DB는 단일 연결이 아니라 connection pool이다
*sql.DB는 여러 goroutine이 함께 쓰는 database handle입니다. 쿼리를 실행할 때 pool에서 사용 가능한 connection을 가져오고, 작업이 끝나면 다시 pool로 돌려보냅니다. 그래서 요청마다 sql.Open을 호출하는 방식이 아니라 애플리케이션 수명 동안 하나의 handle을 공유하는 방식이 기본입니다.
db, err := sql.Open("postgres", dsn)
if err != nil {
return err
}
if err := db.PingContext(ctx); err != nil {
return err
}pool tuning은 성능을 올리는 마법 옵션이 아니라 DB 서버, 애플리케이션 병렬성, query latency 사이의 상한을 맞추는 작업입니다.
MaxOpenConns는 동시 DB 작업의 상한이다
SetMaxOpenConns는 열린 connection의 최대 수를 제한합니다. 이 값을 넘는 DB 작업은 connection이 반환될 때까지 기다립니다. DB 서버의 connection 제한을 보호하는 데 필요하지만, 너무 낮으면 애플리케이션 내부에서 대기열이 길어집니다.
db.SetMaxOpenConns(20)transaction이 connection을 오래 잡고 있거나, rows를 닫지 않아 connection이 반환되지 않으면 작은 MaxOpenConns에서 전체 요청이 막힐 수 있습니다. pool 제한은 semaphore처럼 동작하므로 대기와 deadlock 가능성을 함께 봐야 합니다.
MaxOpenConns의 0 이하 값은 열린 connection 수를 제한하지 않습니다. SetMaxOpenConns를 이미 설정한 MaxIdleConns보다 낮게 줄이면 idle 상한도 함께 낮아질 수 있으므로, replica 하나의 수치만 보지 말고 모든 replica의 합과 DB/proxy 상한을 먼저 계산합니다.
MaxIdleConns와 idle time은 재사용 비용을 조절한다
SetMaxIdleConns는 작업이 끝난 뒤 pool에 남겨 둘 idle connection 수를 정합니다. 너무 낮으면 매 요청마다 새 연결을 자주 만들고, 너무 높으면 DB 서버에 불필요한 idle connection이 많이 남습니다.
db.SetMaxIdleConns(10)
db.SetConnMaxIdleTime(5 * time.Minute)SetConnMaxIdleTime은 connection이 idle 상태로 너무 오래 남지 않게 합니다. 트래픽이 시간대별로 크게 출렁이는 서비스에서는 idle connection 정리가 DB 자원 사용량을 안정시키는 데 도움이 됩니다. 기본 idle 상한은 작고, MaxIdleConns가 MaxOpenConns보다 클 이유는 거의 없습니다.
ConnMaxLifetime은 오래된 연결을 교체한다
SetConnMaxLifetime은 connection이 재사용될 수 있는 최대 시간을 정합니다. 로드밸런서, DB proxy, 방화벽, 서버 측 connection rotation 정책이 있는 환경에서는 너무 오래 산 connection이 끊기는 문제가 발생할 수 있습니다.
db.SetConnMaxLifetime(30 * time.Minute)값을 너무 짧게 잡으면 connection churn이 늘어납니다. 새 연결 생성 비용, TLS handshake, DB 인증 비용이 커질 수 있으므로 timeout 오류와 connection 재생성 빈도를 함께 관찰해야 합니다.
운영 기준
| 상황 | 먼저 볼 것 |
|---|---|
| DB connection 초과 | SetMaxOpenConns와 DB 서버 limit |
| 요청이 DB 앞에서 대기 | DBStats.WaitCount, WaitDuration |
| 매번 연결 생성 비용이 큼 | SetMaxIdleConns 증가 |
| idle connection이 너무 많음 | SetConnMaxIdleTime |
| 오래된 연결이 자주 끊김 | SetConnMaxLifetime |
| transaction이 오래 잡힘 | transaction 범위와 rows close |
pool 설정은 부하 테스트 없이 숫자만 고정하면 위험합니다. 애플리케이션 replica 수와 MaxOpenConns를 곱한 값이 DB 전체 connection limit을 넘지 않는지 먼저 확인해야 합니다.
DBStats.WaitCount, WaitDuration, MaxIdleClosed, MaxLifetimeClosed 같은 counter는 DB handle 수명 동안 누적됩니다. 한 번의 큰 수치만 보지 말고 요청량·지연 시간과 같은 기간의 증가량을 관찰해야 pool 대기와 connection churn을 구분할 수 있습니다.
주의할 점
SetMaxOpenConns를 낮게 잡으면 DB를 보호할 수 있지만, 애플리케이션 내부에서는 connection 대기가 늘어납니다.
transaction이나 rows가 connection을 오래 붙잡는 코드와 만나면 장애가 대기열 형태로 증폭될 수 있습니다.
rows, err := db.QueryContext(ctx, query)
if err != nil {
return err
}
defer rows.Close()pool tuning 전에 rows.Close, rows.Err, transaction Commit/Rollback이 빠지지 않았는지 확인해야 합니다. connection 누수가 있는 상태에서 pool 숫자만 키우면 장애 시점만 늦출 뿐 원인은 남습니다.
참고 링크
2 sources