Go: 同じホストへ大量に呼ぶなら MaxIdleConnsPerHost を上げる(既定は 2)
http.DefaultTransport は 1 ホストあたり 2 本しかアイドル接続を残さない。同じ API へ並列 50 本で叩くと、バーストが終わるたびに 48 本が閉じられ、次のバーストで TCP + TLS のハンドシェイクをやり直すことになる。負荷に合わせた自前の http.Transport を使う。
どんな時に気にする?
- 決済 API・社内マイクロサービス・LLM API など、特定の 1 ホストへ高い並列度でリクエストする
- バーストのたびにレイテンシが跳ねる、
TIME_WAITのソケットが大量に溜まる httptraceで見るとGotConnのReused=falseが多い
設定例
go
// 起動時に 1 回だけ作り、使い回す(リクエストごとに Transport を作ると接続を一切再利用できない)
var apiClient = &http.Client{
Timeout: 10 * time.Second,
Transport: func() *http.Transport {
t := http.DefaultTransport.(*http.Transport).Clone() // プロキシや TLS の既定値を引き継ぐ
t.MaxIdleConns = 200 // 全ホスト合計のアイドル接続(既定 100)
t.MaxIdleConnsPerHost = 64 // ホストごとのアイドル接続(既定 2)。想定並列度くらいに
t.MaxConnsPerHost = 128 // 同時接続の上限(既定 0=無制限)。相手を潰さない保険
t.IdleConnTimeout = 90 * time.Second
return t
}(),
}MaxIdleConnsPerHostは「残しておく」本数で、同時に張れる本数の上限ではない。上限を付けたいときはMaxConnsPerHost。MaxIdleConnsがMaxIdleConnsPerHostより小さいと、そちらが先に効く。両方上げる。- 再利用されているかは
net/http/httptraceのGotConnInfo.Reusedで確かめられる。
注意点
- 接続の再利用には、レスポンスボディを読み切って閉じることも必要(Go: resp.Body は読み切ってから閉じる(keep-alive 接続を再利用させる)※Go 1.27 で自動化)。
- HTTP/2 で話す相手なら 1 本の接続で多重化されるので、この問題はほぼ起きない。効くのは HTTP/1.1 の相手(社内 API、プロキシ経由など)。
- 値を上げすぎると、相手側のコネクション上限やロードバランサのアイドルタイムアウトに当たる。相手の keep-alive タイムアウトより
IdleConnTimeoutを短くしておくと、切れた接続を掴みにくい。
出典
- @amitsinha__07 · 2026-10-19
- net/http ドキュメント(
DefaultMaxIdleConnsPerHost = 2): pkg.go.dev