Skip to content

パフォーマンス最適化

HTTPC は設計段階から高性能を目指しています:コネクションプールの再利用、HTTP/2 多重化、オブジェクトプーリング、単一割り当ての結果オブジェクト。ほとんどのシナリオでは、プリセット設定をそのまま使うだけで優れたパフォーマンスが得られます。さらにチューニングが必要な場合は、基盤のメカニズムを理解して的確に対処することが重要です。

プリセット設定の比較

HTTPC は 5 種類のプリセット設定を提供し、それぞれが異なるシナリオ向けに体系的に調整されています。以下はカテゴリ別に主要フィールドの正確な値を示すもので、選定時の比較に役立ちます。

タイムアウト設定

フィールドDefaultSecurePerformanceTestingMinimal
Timeouts.Request180s15s60s180s180s
Timeouts.Dial10s5s15s5s5s
Timeouts.TLSHandshake10s5s15s5s5s
Timeouts.ResponseHeader0(無効)10s0(無効)0(無効)0(無効)
Timeouts.IdleConn90s30s120s30s30s

接続設定

フィールドDefaultSecurePerformanceTestingMinimal
MaxIdleConns50201001010
MaxConnsPerHost1052052
EnableHTTP2有効有効有効無効有効
EnableCookies無効無効有効有効無効
EnableDoH無効無効無効無効無効

セキュリティ設定

フィールドDefaultSecurePerformanceTestingMinimal
MaxResponseBodySize10MB5MB50MB10MB1MB
MaxDecompressedBodySize100MB100MB100MB100MB100MB
ValidateURL有効有効有効無効有効
ValidateHeaders有効有効有効無効有効
StrictContentLength有効有効無効有効有効
AllowPrivateIPsfalsefalsefalsetruefalse
InsecureSkipVerifyfalsefalsefalsetruefalse

リトライ設定

フィールドDefaultSecurePerformanceTestingMinimal
MaxRetries31310
Delay1s2s500ms100ms0
BackoffFactor2.02.01.52.01.0
MaxRetryDelay30s30s30s30s30s
EnableJitter有効有効有効無効無効

リクエストデフォルト値

フィールドDefaultSecurePerformanceTestingMinimal
FollowRedirects有効無効有効有効無効
MaxRedirects1010101010
UserAgenthttpc/1.0httpc/1.0httpc/1.0httpc-test/1.0httpc/1.0

TestingConfig の本番使用は禁止

TestingConfig() は URL/Header 検証、TLS 証明書検証、SSRF 防護を無効化しており、ローカル開発とテスト専用です。テスト以外の環境で呼び出すとセキュリティ警告が出力されます。本番環境では SecureConfig() または DefaultConfig() を使用してください。

シナリオ別選択

シナリオ推奨プリセット調整の提案
汎用 Web サービスDefault
ユーザー提供の URL を扱うSecure
内部マイクロサービスの高並列Performanceバックエンド数に合わせて MaxIdleConns を増やす
一回限りのスクリプトMinimal
ファイルダウンロードサービスPerformanceMaxResponseBodySize を増やす
金融/医療 APISecure + カスタム監査ミドルウェアを追加
ローカル開発/ユニットテストTesting本番にデプロイしない
go
// 高スループットシナリオではプリセットをそのまま使用
client, _ := httpc.New(httpc.PerformanceConfig())

// プリセットをベースに個別フィールドを微調整
cfg := httpc.PerformanceConfig()
cfg.Timeouts.Request = 120 * time.Second
cfg.Connection.MaxIdleConns = 200
client, _ := httpc.New(cfg)

並列モデル:1 つの Client ですべての goroutine に対応

HTTPC の ClientDomainClient はいずれも並行セーフです——任意のメソッドを複数の goroutine から同時に呼び出せます。ライブラリ内部には専用の並行セーフ統合テスト(internal/concurrency)があり、高並列シナリオで公開 API をカバーしています。そのため、正しい並列パターンはごくシンプルです:

グローバル/サービスレベルで 1 つの Client を作成

        ├── goroutine 1 ──┐
        ├── goroutine 2 ──┼── 同じコネクションプールとオブジェクトプールを共有
        └── goroutine N ──┘

並列容量とコネクションプールの関係:

シナリオ挙動
並列数 ≤ MaxConnsPerHost(HTTP/1.1)各リクエストが 1 本の接続を専有し、互いに待たない
並列数 > MaxConnsPerHost(HTTP/1.1)余ったリクエストはトランスポート層でアイドル接続を待ってキューに入る(エラーにはならないがレイテンシが上昇)
HTTP/2 有効(デフォルト)同一ホストのリクエストが単一接続の多重化を共有。MaxConnsPerHost がボトルネックになることはまれ

並列上限の 2 つの調整方法

  • クライアント側を制御Connection.MaxConnsPerHost をピーク並列数以上に引き上げる(HTTP/1.1 シナリオ)。
  • 呼び出し側を制御:バッファ付き channel をセマフォとして並列を制限する(下の完全な例)。呼び出し先のサービスを能動的に保護します。 両者は併用が基本です:セマフォで呼び出し先の処理能力に合わせて流量を絞り、コネクションプールはセマフォの上限に合わせて接続を用意します。
go
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"sync"
	"sync/atomic"
	"time"

	"github.com/cybergodev/httpc"
)

func main() {
	// ローカルモックサーバー:各リクエストに固定 50ms 要する
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(50 * time.Millisecond)
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	cfg := httpc.DefaultConfig()
	cfg.Security.AllowPrivateIPs = true // 127.0.0.1 のローカルテストサーバーへの接続を許可
	client, err := httpc.New(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	const (
		total       = 20
		maxInFlight = 5 // セマフォ:同時にインフライトのリクエストは最大 5 つ
	)

	sem := make(chan struct{}, maxInFlight)
	var wg sync.WaitGroup
	var okCount int64
	start := time.Now()

	for i := 0; i < total; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			sem <- struct{}{}                // セマフォを取得
			defer func() { <-sem }()         // セマフォを解放

			result, err := client.Get(server.URL)
			if err != nil {
				return
			}
			if result.IsSuccess() {
				atomic.AddInt64(&okCount, 1)
			}
		}()
	}
	wg.Wait()

	fmt.Printf("%d/%d 成功、所要時間 %v(直列なら約 %v\n",
		okCount, total, time.Since(start), total*50*time.Millisecond)
	// 出力例:20/20 成功、所要時間約 250ms(直列なら約 1s)——5 路並列で約 5 倍のスループット
}

大量の独立した URL を一括取得する場合のもう一つの定番パターンが ワーカープール(worker pool) です:固定数の worker goroutine が jobs channel からタスクを消費するため、並列度が自然に worker 数に抑えられ、セマフォは不要です。完全な実装は高度な使用例を参照してください。

コネクションプールチューニングの原理

コネクションプールは HTTP クライアントのパフォーマンスの中核です。HTTPC のコネクションプールは Go 標準ライブラリの http.Transport をベースに、その上に自動計算ロジックと安全なデフォルト値を加えています。

アイドル接続の自動算出

MaxIdleConnsPerHost(ホストあたりのアイドル接続上限)は手動設定不要です——HTTPC が MaxConnsPerHost から自動的に導出します:

アイドル接続数 = MaxConnsPerHost / 2、[2, 10] の区間にクランプ

具体的なルール(calculateIdleConnsPerHost):

MaxConnsPerHost自動アイドル接続数説明
0(無制限)10上限のデフォルト値を使用
11まず下限の 2 を取り、その後「最大接続数を超えない」制約で 1 に引き戻される
22下限にちょうど一致
52半分が下限に切り上げ
105Default プリセット
2010Performance プリセット、上限を取る
10010上限超過は 10

なぜ MaxConnsPerHost / 2 なのか

アイドル接続は「接続のキャッシュ」です——確立済みだが一時的に未使用の接続。最大接続数の半分に設定することで、「既存接続の再利用」(キャッシュヒット)と「新規接続の確立」(キャッシュミス時に再ハンドシェイクが必要)のバランスを取り、アイドル接続の過多によるサーバー側リソースの占有を防ぎます。

TCP Keep-Alive

HTTPC のコネクションプールは 30 秒の TCP keep-alive 間隔を固定で使用します(defaultKeepAlive = 30 * time.Second)。この値に基づき、接続確立後に OS が周期的に keep-alive プローブパケットを送信し、死んだ接続を検出します。IdleConn タイムアウトはアイドル接続のプール内生存時間を制御し(Default では 90s)、両者が協調して動作します。

go
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/cybergodev/httpc"
)

func main() {
    // マイクロサービスの高 QPS シナリオ:コネクションプールを拡大
    cfg := httpc.PerformanceConfig()
    cfg.Connection.MaxIdleConns = 200   // グローバルアイドル接続上限
    cfg.Connection.MaxConnsPerHost = 50 // ホストあたりの最大接続(アイドルは自動算出で 10)
    cfg.Timeouts.IdleConn = 300 * time.Second // アイドル接続をより長く存続させ、再利用率を向上

    client, err := httpc.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // ホットパスのリクエストはコネクションプール内の接続を直接再利用
    for i := 0; i < 100; i++ {
        result, err := client.Get("https://api.example.com/data")
        if err != nil {
            log.Printf("リクエスト %d 失敗: %v", i, err)
            continue
        }
        fmt.Printf("リクエスト %d: %d\n", i, result.StatusCode())
    }
}

HTTP/2 のパフォーマンス上の利点

HTTP/2 はデフォルトで有効です(EnableHTTP2 = true)。3 つの大きな性能向上をもたらします:

特徴HTTP/1.1HTTP/2
多重化各リクエストが接続を専有複数リクエストが単一接続を共有
ヘッダー圧縮平文で重複送信HPACK によるヘッダー圧縮
接続再利用Keep-alive による直列並行ストリーム(stream)

HTTP/2 とコネクションプールの関係

HTTP/2 の多重化により、単一の TCP 接続で複数のリクエストを同時に運べるため、接続確立のオーバーヘッドが大幅に減ります。同一ホストへの高並列シナリオでは、HTTP/2 のスループットは HTTP/1.1 を大きく上回ります。TestingConfig()(明示的に HTTP/2 を無効化)を使用するか、接続が ALPN ネゴシエーションをサポートしない場合にのみ HTTP/1.1 へフォールバックします。

go
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/cybergodev/httpc"
)

func main() {
    // デフォルト設定で HTTP/2 は有効
    cfg := httpc.DefaultConfig()
    cfg.Connection.EnableHTTP2 = true // デフォルトで true。明示するとより明確

    client, err := httpc.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // HTTP/2 対応サイト(大半の CDN/クラウドサービスなど)へ並列リクエストを発行
    // 単一の TCP 接続を再利用でき、リクエストごとに新規接続は不要
    start := time.Now()
    for i := 0; i < 10; i++ {
        result, err := client.Get("https://http2.golang.org/")
        if err != nil {
            log.Printf("リクエスト %d 失敗: %v", i, err)
            continue
        }
        // Proto() はプロトコルバージョンを返す(例:"HTTP/2.0")
        fmt.Printf("リクエスト %d: %s, ステータスコード %d\n", i, result.Proto(), result.StatusCode())
    }
    fmt.Printf("10 リクエストの所要時間: %v\n", time.Since(start))
}

メモリ最適化メカニズム

HTTPC はメモリ管理で多層の最適化を行っており、中核の考え方はヒープ割り当ての削減とオブジェクトの再利用です。

resultBundle の単一割り当て

毎回のリクエストが返す *Result は 3 つのネスト構造体を持ちます:RequestInfo(リクエスト情報)、ResponseInfo(レスポンス情報)、RequestMeta(所要時間などのメタデータ)。伝統的な作りでは Result と 3 つのネスト構造体をそれぞれ割り当てる必要があり——4 回のヒープ割り当てです。HTTPC はこれらを 1 つの resultBundle にパックし、1 回のヒープ割り当てで全部を賄います:

伝統方式:4 回の独立割り当て(Result + RequestInfo + ResponseInfo + RequestMeta)
HTTPC:1 回の割り当て(resultBundle)。Result の 3 つのポインタは同一メモリを指す

呼び出し側が受け取るのは *Result で、その RequestResponseMeta フィールド(ポインタ)は bundle 内の対応する構造体を指しており、完全に透過です。呼び出し側が *Result を長期保持する可能性があるため、ここはオブジェクトプールに向きません(プーリングはデータ競合を招く)し、GC が自動的に回収します。

エンジンのオブジェクトプール

HTTPC のエンジン層では sync.Pool を広く使い、短命オブジェクトを再利用して GC 負荷を削減します:

プール対象用途説明
engine.Responseレスポンスオブジェクトリクエスト完了後にプールへ返却し、次のリクエストで再利用
engine.Requestリクエストオブジェクト同上
strings.Builder文字列構築URL 構築、エラーフォーマット、Config シリアライズ
http.HeaderHTTP ヘッダー mapリクエスト/レスポンスヘッダー処理
bytes.BufferJSON/multipart エンコード初期容量で事前割り当て
time.Timerリトライタイマータイマーの頻繁な生成を回避
gzip/flate reader解凍解凍器を再利用

オブジェクトプールと resultBundle の分担

エンジン内部のオブジェクト(Response/Request/Builder)はライフサイクルが短く、リクエスト内部で borrow-return の循環が完結するため、プーリングに向きます。呼び出し側へ返す *Result はライフサイクルが不確定なため、単一割り当て + GC 回収に向きます。両者は補完し合い、それぞれの長所を活かします。

低アロケーションのホットパス

オブジェクトプールに加え、リクエストのホットパスには的を絞った割り当て排除の最適化が多数あります:

最適化点メカニズム
ヘッダーのディープコピーの一括割り当てCloneHeader はまずすべての値の数を数え、共有の基盤配列を 1 回で割り当て——「ヘッダーごとに 1 回の割り当て(N 回)」を 1 回に削減
クエリパラメータエスケープのゼロ割り当てエスケープ不要な文字列はそのまま返却(ゼロ割り当て)。必要な場合のみプール済みバッファへバイト単位で書き出し
数値クエリパラメータの直接書き込みint/float64/bool などの数値は strconv.Append* でビルダーに直接書き込まれ、中間文字列を生成しない
リクエストヘッダーの所有権移動通常リクエストとダウンロードパスでは、エンジン Response 上の header map を Result所有権ごと移動し、複製しない
リダイレクトチェーンのインライン配列最初の 8 回のリダイレクトはプール済みオブジェクトのインライン固定長配列に記録し、8 回を超えて初めてオーバーフロースライスを割り当て——大半のリクエストはリダイレクトチェーンのための追加割り当てなし
リトライスリープタイマーの再利用リトライバックオフ用の time.Timer をプーリングして再利用し、高頻度リトライで Timer の生成を繰り返さない
プール容量の防護しきい値を超えるオブジェクトはプールに返却しない(header map > 64 エントリ、query builder 容量 > 4096 など)。巨大オブジェクトがプールに長居してメモリを膨らませるのを防止

内部メトリクスとヘルス

エンジン内部は純粋なアトミック操作(ロックなし)でリクエストごとのメトリクスを収集します:総リクエスト数、成功/失敗数、および移動平均の公式 新平均 = (旧平均×9 + 今回のレイテンシ) / 10 で維持される平滑化レイテンシ。エラー率 10% 未満を健康とみなします。これらのメトリクスはエンジン自身の健康判断に使われるもので、公開 API としては露出しません——アプリケーション層のリクエストメトリクスには MetricsMiddleware を使ってください(ミドルウェアを参照)。メソッド/URL/ステータスコード/所要時間でコールバックされ、Prometheus などの監視システムに直接接続できます。

意識する必要はない部分

以上の最適化は呼び出し側に完全に透過です。普段どおり API を使うだけで、接続再利用、オブジェクトプーリング、単一割り当てはすべて内部で自動的に行われます:

go
package main

import (
    "fmt"
    "log"

    "github.com/cybergodev/httpc"
)

func main() {
    client, err := httpc.NewDefault()
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Result はリクエストごとに新規作成、GC が自動回収、手動解放は不要
    result, err := client.Get("https://api.example.com/data")
    if err != nil {
        log.Fatal(err)
    }

    // ホットパスでは Body() ではなく RawBody() を優先
    // RawBody() は生のバイトスライスを返す。Body() は保持された文字列を返す。String() はデバッグ用フォーマット(最も高コスト)
    data := result.RawBody()
    fmt.Printf("レスポンスサイズ: %d バイト\n", len(data))
    fmt.Printf("リクエスト所要時間: %v\n", result.Meta.Duration)
}

ワークロード別チューニング例

タイムアウトバジェット

4 つのトランスポート層タイムアウト(DialTLSHandshakeResponseHeader、暗黙のボディ転送)は、いずれも Timeouts.Request という総バジェットの制約を受けます。プリセットを調整する際は「各項の合計 ≤ 総バジェット」の階層関係を保ち、「ダイヤルタイムアウトが総タイムアウトより長い」ような無効な設定を避けてください:

Timeouts.Request(総バジェット、デフォルト 180s)
 ├── Timeouts.Dial          ダイヤル(デフォルト 10s)
 ├── Timeouts.TLSHandshake  TLS ハンドシェイク(デフォルト 10s)
 ├── Timeouts.ResponseHeader レスポンスヘッダー待ち(Default/Performance は 0=トランスポート層制限なし)
 └── レスポンスボディ転送    残り時間をすべて使用可能
ワークロードRequestDial/TLS説明
内部ネットワークのマイクロサービス5–10s1–2s高速フェイル。エラーは上流のリトライ/サーキットブレーカーに委ねる
インターネット上の API30s5sクロスネットワーク遅延と偶発的な遅いレスポンスの両立
AI/長時間タスク300s+10s長いレスポンスボディが残りバジェットを使い切る
大容量ファイルダウンロード0(context で制御)15s総時間は Download の ctx で、単一リクエストは WithTimeout で管理

ResponseHeader と WithTimeout の相互作用

Default/Performance プリセットは ResponseHeader を 0 に設定し(トランスポート層で強制しない)、WithTimeout() が長いレスポンスを完全に制御できるようにしています。Secure プリセットは 10s に設定し、slowloris 系攻撃に対抗します。手動で ResponseHeader を狭めた場合、WithTimeout より先に遅いレスポンスを切断する可能性があることに注意してください。

AI API の長時間リクエスト

AI 推論 API はレスポンスに数分かかることがあり、タイムアウト制限を緩める必要があります:

go
// AI API は 5〜15 分の応答時間になり得る。デフォルトの 180s タイムアウトで打ち切らない
result, err := httpc.Post("https://api.ai.example.com/v1/completions",
    httpc.WithJSON(payload),
    httpc.WithTimeout(900*time.Second), // 15 分
)

Default の ResponseHeader が 0 の理由

TimeoutConfig.ResponseHeader = 0 はトランスポート層でレスポンスヘッダータイムアウトを強制しないことを意味し、context レベルのタイムアウト(TimeoutConfig.Request または WithTimeout)で一元制御されます。これにより WithTimeout() が長いレスポンスのリクエストを完全に制御できます。slowloris 攻撃に対抗するトランスポート層の防御が必要な場合は、SecureConfig()(10s に設定)を使ってください。

マイクロサービスの高 QPS

内部マイクロサービス間の高頻度呼び出しには大きなコネクションプールが必要です:

go
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/cybergodev/httpc"
)

func main() {
    cfg := httpc.PerformanceConfig()
    // コネクションプールはバックエンドインスタンス数に合わせてチューニング
    cfg.Connection.MaxIdleConns = 300   // 総アイドル接続
    cfg.Connection.MaxConnsPerHost = 30 // 各バックエンドインスタンスあたり
    // マイクロサービスのレスポンスは通常速いため、タイムアウトを短縮して高速フェイル
    cfg.Timeouts.Request = 10 * time.Second
    cfg.Retry.Delay = 200 * time.Millisecond
    cfg.Retry.BackoffFactor = 2.0
    cfg.Retry.MaxRetries = 2

    client, err := httpc.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    start := time.Now()
    // 高頻度リクエストはコネクションプールを再利用し、TCP/TLS の再構築は不要
    for i := 0; i < 50; i++ {
        result, err := client.Get("http://user-service:8080/api/users")
        if err != nil {
            log.Printf("リクエスト %d 失敗: %v", i, err)
            continue
        }
        _ = result
    }
    fmt.Printf("50 リクエストの所要時間: %v\n", time.Since(start))
}

大容量ファイルダウンロード(ストリーミング)

大容量ファイルのダウンロードには Download() を使ってください:内部で自動的にストリーミングモードが有効になり、レスポンスボディはネットワークからディスクへ直送されます。メモリ使用量はファイルサイズに依存せず、レジュームとチェックサムにも対応します:

go
package main

import (
    "context"
    "log"

    "github.com/cybergodev/httpc"
)

func main() {
    cfg := httpc.PerformanceConfig()
    cfg.Security.MaxResponseBodySize = 500 * 1024 * 1024 // 上限 500MB

    client, err := httpc.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    dcfg := httpc.DefaultDownloadConfig()
    dcfg.FilePath = "/tmp/large-file.zip"
    dcfg.ResumeDownload = true // レジューム

    result, err := client.Download(
        context.Background(),
        "https://example.com/large-file.zip",
        dcfg,
    )
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("ダウンロード完了: %d バイト", result.BytesWritten)
}

通常のリクエストメソッドに WithStreamBody を使わない

WithStreamBody(true)Download のようにエンジンレスポンスを直接消費するパスでのみ有効です。Get/Post/Request などの通常メソッドに設定すると、レスポンスボディは結局 Result へ完全に読み込まれた後で基盤のストリームがクローズされます——返された Result のリクエストボディは空で、呼び出し側はストリームを取得できません。大きなレスポンスボディを消費する正しい入口は Download です(詳細はファイルアップロードとダウンロード)。

クローラーとプロキシプール

クローラーのシナリオではプロキシプールで IP をローテーションします。HTTPC はリトライ回数を自動的に引き上げ、各プロキシが少なくとも 1 回試行されるようにします(詳細は リトライとフォールトトレランス):

go
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyPool = []string{
    "http://proxy1:8080",
    "http://proxy2:8080",
    "http://proxy3:8080",
    "http://proxy4:8080",
    "http://proxy5:8080",
}
cfg.Connection.ProxyRotateOnStatus = []int{403} // 403 でプロキシ切り替えをトリガー
cfg.Connection.ProxyPoolStrategy = httpc.ProxyStrategyRoundRobin
// MaxRetries は自動的に 4(プロキシ数-1)へ引き上げられ、5 つのプロキシがすべて 1 回ずつ試行される

パフォーマンスアンチパターン

アンチパターン原因正しいやり方
リクエストごとに Client を新規作成接続を再利用できず、毎回 TCP/TLS ハンドシェイクをやり直す単一の Client インスタンスをグローバルで再利用
過大な MaxResponseBodySize不要にメモリ上限を緩める実際のレスポンスサイズに合わせて設定
ホットパスで result.String() を使用余分な文字列構築のオーバーヘッドresult.Body() または result.RawBody() を使用
コネクションプールが小さすぎる高並列で接続が足りず、キューで待機MaxConnsPerHost を並列数に合わせる
通常リクエストで WithStreamBody を使用返される Result のリクエストボディが空で、ストリームも取得できない大きなレスポンスボディは Download
HTTP/2 を無効化HTTP/1.1 の直列リクエストに劣化デフォルトの有効のままに
Close() を無視接続リークdefer client.Close()
グローバル共有なのに再利用を忘れるClient の生成/破棄を繰り返す一度作成し、長期保持
goroutine の数で押し切る呼び出し先を圧迫し、429/サーキットブレークを誘発セマフォかワーカープールでインフライト数を制御

Client は必ず再利用

HTTP パフォーマンスの土台は接続再利用です。リクエストごとに Client を新規作成すると、毎回 TCP 3 ウェイハンドシェイク + TLS ハンドシェイクをやり直し、レイテンシがサブミリ秒から数十ミリ秒へ急増します。マイクロサービスのシナリオでは、Client をシングルトンとしてサービス構造体に注入し、サービスのライフサイクルとともに存続させてください。

go
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/cybergodev/httpc"
)

// アンチパターンのデモ:リクエストごとに Client を新規作成
func main() {
    start := time.Now()

    for i := 0; i < 5; i++ {
        // ❌ 毎ループで Client を新規作成——接続を再利用できない
        client, err := httpc.NewDefault()
        if err != nil {
            log.Fatal(err)
        }
        result, err := client.Get("https://httpbin.org/get")
        client.Close() // 毎回クローズし、コネクションプールを空に
        if err != nil {
            log.Printf("リクエスト %d 失敗: %v", i, err)
            continue
        }
        _ = result
    }
    // 5 リクエストの所要時間は Client を再利用するケースより大幅に長くなる
    fmt.Printf("アンチパターンの所要時間: %v\n", time.Since(start))

    // ✅ 正しいやり方:Client を再利用
    client, err := httpc.NewDefault()
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    start = time.Now()
    for i := 0; i < 5; i++ {
        result, err := client.Get("https://httpbin.org/get")
        if err != nil {
            log.Printf("リクエスト %d 失敗: %v", i, err)
            continue
        }
        _ = result
    }
    fmt.Printf("再利用パターンの所要時間: %v\n", time.Since(start))
}

次のステップ