Skip to content

コネクションプールと DNS

コネクションプール設定

コネクションプールは HTTP クライアントのパフォーマンスを左右する重要な要素です。HTTPC は ConnectionConfig でコネクションプールを管理します。

go
cfg := httpc.DefaultConfig()

// コネクションプールパラメータ
cfg.Connection.MaxIdleConns = 100         // グローバル最大アイドル接続数
cfg.Connection.MaxConnsPerHost = 20       // ホストあたりの最大接続数
cfg.Timeouts.IdleConn = 120 * time.Second // アイドル接続の維持時間

パラメータの説明

パラメータデフォルト説明
MaxIdleConns50グローバル最大アイドル接続数
MaxConnsPerHost10ホストあたりの最大接続数(アクティブ + アイドルを含む)
IdleConn90sアイドル接続タイムアウト。超過するとクローズ
Dial10s接続確立のタイムアウト
TLSHandshake10sTLS ハンドシェイクタイムアウト
ResponseHeader0無効(Request タイムアウトを使用)
MaxResponseHeaderBytes0レスポンスヘッダーのサイズ上限。0 = Go 標準ライブラリのデフォルト 10MB

シナリオ別推奨

シナリオMaxIdleConnsMaxConnsPerHostIdleConn
高並列 API10020120s
通常サービス501090s
低頻度リクエスト10230s
マイクロサービス内部501060s

TIP

MaxConnsPerHost はアクティブ接続とアイドル接続の両方を含みます。この制限を超える新しいリクエストは、接続の解放を待ってキューに入ります。

派生パラメータと内部上限

一部の接続パラメータには独立した設定項目がなく、エンジンが既存のパラメータから導出するか、固定値を使用します:

パラメータ出所
MaxIdleConnsPerHostclamp(MaxConnsPerHost/2, 2, 10)MaxConnsPerHost=0 の場合は 10MaxConnsPerHost から導出、独立フィールドなし
1 クライアントあたりの総接続上限1000(アクティブ + アイドル)固定値。超過するとコネクションプール枯渇エラーを返す
TCP KeepAlive プローブ間隔30s固定値
ExpectContinueTimeout1s固定値(Expect: 100-continue の待機時間)

総接続上限の挙動

総接続上限の 1000 に達すると、新規接続はエラーで終了し、ClientErrorErrorTypeNetwork、Message は connection pool exhausted)に分類されます。デフォルトの MaxIdleConns=50 / MaxConnsPerHost=10 ではほぼ発生しません。極端な並列時の最終防線と考えてください。

アイドル接続の管理

  • IdleConn(デフォルト 90s):アイドル接続はトランスポート層のタイムアウト後に自動的にクローズされます。大きくすれば接続の再利用率が上がり、小さくすれば対向側のリソースをより速く解放できます(高頻度の短命接続シナリオでは TIME_WAIT の蓄積に注意)。
  • プロキシローテーション時のアクティブクリーンアップProxyRotatePerRequest やステータスコードローテーションのリトライパスを有効にすると、すべてのアイドル接続が自動的にクローズされ、次のリクエストでプロキシが再選択されます——そうしないと、HTTP/2 の CONNECT トンネル経由の接続再利用がプロキシ選択をバイパスし、ローテーションを無効化してしまうためです。プロキシ設定とローテーション戦略の詳細は プロキシとプロキシプール を参照してください。
  • client.Close():すべてのアイドル接続、DoH リゾルバー、内部リソースをクローズします。以降のリクエストは ErrClientClosed を返します。
  • ホストごとの統計の自動クリーンアップ:内部ではホストごとに接続カウントを維持し、30 分間アクティビティがなくアクティブ接続もないエントリが周期的にクリーンアップされます(最大で毎分 1 回、ホストエントリの上限は 10000)。長期実行でメモリが無限に増えることはありません。

レスポンス解凍の特別な扱い

トランスポート層では標準ライブラリの自動解凍を無効化しており、HTTPC がレスポンス処理層で gzip / deflate を手動で処理します。解凍は Security.MaxDecompressedBodySize(デフォルト 100MB)の制約を受け、解凍爆弾攻撃を防ぎます。日常的にこの層を意識する必要はありません——Result.RawBody() が返すのはすでに解凍済みのバイトであることだけ知っておけば十分です。

DNS-over-HTTPS

DoH を有効にすると、DNS 解決は暗号化された HTTPS 経路で行われ、キャリアによるハイジャックや DNS ポイズニングを防止できます。さらに、複数プロバイダーによる耐障害性を内蔵しています:

go
cfg := httpc.DefaultConfig()
cfg.Connection.EnableDoH = true
cfg.Connection.DoHCacheTTL = 5 * time.Minute // 0 を渡しても 5 分にフォールバック

デフォルトの DoH プロバイダー(優先度順):

プロバイダーアドレス説明
Cloudflare1.1.1.1/dns-query最速、プライバシー優先
Googledns.google/resolveグローバルカバレッジ
AliDNSdns.alidns.com/resolve中国地域向けに最適化

仕組み

  • A + AAAA の並行クエリ:毎回の解決で IPv4 と IPv6 のレコードを同時にクエリし、結果をマージします。
  • デュアルフォーマット解析:レスポンスの Content-Type に応じて JSON(Google/AliDNS 方式)か RFC 1035 wire フォーマット(Cloudflare 方式)を自動選択します。欠落または未識別の場合は先に JSON、次に wire を試み、設定に異常があるサーバーとも互換性を保ちます。
  • 並行マージ:同一ホストの並行キャッシュミスは 1 回のネットワーク往復にマージされ(singleflight)、キャッシュスタンプでプロバイダーを圧倒しません。
  • 独立した内部クライアント:DoH リクエストは独立した HTTP クライアント(5s タイムアウト、HTTP/2 有効)で行われ、業務のコネクションプールもリクエストタイムアウトのバジェットも消費しません。

フォールバックチェーン

text
Cloudflare (1.1.1.1) → Google (dns.google) → AliDNS → システム DNS リゾルバー

いずれか 1 つのプロバイダーが成功すればその結果を返します。すべて失敗した場合は自動的にシステム DNS リゾルバーへフォールバックし、両層のエラーをエラーメッセージに統合します。単一プロバイダーの障害でリクエストが失敗することはありません。

キャッシュ

項目
TTLDoHCacheTTL(デフォルト 5 分)
容量上限1000 件。満杯時はまず期限切れエントリを、次に期限切れが最も近いエントリを退避
レスポンスサイズ上限64KB(悪意のある DNS レスポンスによるメモリ圧迫を防止)

SSRF 防護との連携

DoH で解決された IP は、まず SSRF フィルタを通過し(プライベート/予約アドレスの除去、SSRFExemptCIDRs の尊重)、その後検証済みの IP に直接ダイヤルします——検証とダイヤルの間に 2 回目の DNS 解決は存在しないため、DNS rebinding 攻撃を根元から防げます。プロキシアドレスは DoH 解決の対象外です(プロキシは開発者が明示的に設定するため、プロキシホストへ直接ダイヤルします。プロキシとプロキシプール を参照)。詳細は SSRF 防護 を参照してください。

HTTP/2

デフォルトで HTTP/2 が有効です(TLS が必要):

go
cfg := httpc.DefaultConfig()
cfg.Connection.EnableHTTP2 = false // HTTP/2 を無効化

HTTP/2 の特徴:

  • 多重化:単一接続で複数の並列リクエストを処理
  • ヘッダー圧縮:重複ヘッダーの転送を削減
  • サーバープッシュ

無効化すると、トランスポート層は HTTP/2 ネゴシエーションを一切試行しなくなります(カスタム TLS 設定シナリオでの強制試行を含む)。平文 HTTP/2(h2c)はサポートされません。HTTP/2 の多重化により「同一ホストへの並列リクエストが 1 本の接続を共有する」点に注意——これがプロキシローテーションのシナリオでアイドル接続をクローズする必要がある理由です(プロキシとプロキシプール のリクエストごとのローテーションを参照)。

オブジェクトプールの再利用

HTTPC は内部で、エンジンのレスポンスオブジェクトと文字列ビルダーを sync.Pool で再利用し、GC 負荷を削減します。Result はリクエストごとに新規作成され、GC が自動的に回収します。

go
result, err := client.Get(url)
if err != nil {
    return err
}
// Result はリクエストごとに新規作成、GC が自動回収、手動解放は不要

高並列シナリオでは、内部オブジェクトプールの再利用により GC 負荷を大幅に削減できます。

並列リクエストパターン

go
func fetchAll(ctx context.Context, urls []string) ([]*httpc.Result, error) {
    results := make([]*httpc.Result, len(urls))
    errs := make([]error, len(urls))

    var wg sync.WaitGroup
    for i, url := range urls {
        wg.Add(1)
        go func(idx int, u string) {
            defer wg.Done()
            result, err := client.Request(ctx, "GET", u)
            results[idx] = result
            errs[idx] = err
        }(i, url)
    }
    wg.Wait()

    for _, err := range errs {
        if err != nil {
            return nil, err
        }
    }
    return results, nil
}

よくある問題

問題原因解決策
大量の TIME_WAITアイドル接続タイムアウトが短すぎるIdleConn タイムアウトを増やす
接続が拒否されるホストあたりの接続数が不足MaxConnsPerHost を増やす
リクエストがキューで待機コネクションプールが小さすぎるMaxIdleConns を増やす
ホストあたりのアイドル接続数を制御したい独立した設定フィールドがないMaxConnsPerHost から導出(÷2、2–10 の間にクランプ)
DoH 有効後に解決が失敗するすべての DoH プロバイダーが到達不能/タイムアウトシステム DNS へのフォールバックを内蔵済み。出口ネットワークを確認するかデフォルトのままにする
プロキシが効かない・頻繁にサーキットブレークプロキシ設定と接続再利用の相互作用プロキシとプロキシプール のよくある問題を参照

パフォーマンスアンチパターンと最適化提案の全貌は パフォーマンス最適化 を参照してください。

次のステップ