빠른 설정
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 가능성을 함께 봐야 합니다.
MaxIdleConns와 idle time은 재사용 비용을 조절한다
SetMaxIdleConns는 작업이 끝난 뒤 pool에 남겨 둘 idle connection 수를 정합니다. 너무 낮으면 매 요청마다 새 연결을 자주 만들고, 너무 높으면 DB 서버에 불필요한 idle connection이 많이 남습니다.
db.SetMaxIdleConns(10)
db.SetConnMaxIdleTime(5 * time.Minute)SetConnMaxIdleTime은 connection이 idle 상태로 너무 오래 남지 않게 합니다. 트래픽이 시간대별로 크게 출렁이는 서비스에서는 idle connection 정리가 DB 자원 사용량을 안정시키는 데 도움이 됩니다.
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을 넘지 않는지 먼저 확인해야 합니다.
주의할 점
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