はじめに
バックエンドエンジニアとして、自分が書いたコードが「どの程度の同時接続に耐えられるのか」を知ることは非常に重要です。今回は Go の標準ライブラリ net/http で構築したシンプルなサーバーに対し、Go 製の負荷テストツール hey を使って限界まで負荷をかけてみました。
1. 実行環境
- OS: Linux Mint
- Language: Go
- Load Test Tool: hey (github.com/rakyll/hey)
- Hardware: ローカルマシン (CPU/メモリフル稼働)
2. 事前準備:テスト用サーバーの作成
テスト対象として、軽量なレスポンスを返す /fast と、擬似的な重い処理(time.Sleep)を含む /slow の2つのエンドポイントを持つサーバーを用意しました。
package main
import (
"fmt"
"log"
"math/rand"
"net/http"
"time"
)
func main() {
http.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Fast response!")
})
http.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
delay := rand.Intn(400) + 100
time.Sleep(time.Duration(delay) * time.Millisecond)
fmt.Fprintf(w, "Slow response after %dms\n", delay)
})
fmt.Println("Server starting at :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
3. 負荷テストの実行と結果分析
ケース1:軽量処理への低負荷テスト
まずは基本的な性能を確認します。
hey -n 200 -c 20 http://localhost:8080/fast
- Requests/sec: 約 27,592 rps
- Average: 0.0006 secs
- 分析: ロジックが空の状態では、Go は秒間 2.7 万リクエストを軽々とさばきます。
ケース2:重量処理への適正負荷テスト
次に、I/O 待ちが発生する状態で並列数を上げます。
hey -n 200000 -c 500 http://localhost:8080/slow
- Requests/sec: 約 1,643 rps
- Average: 0.3012 secs
- Latency 99%: 0.4966 secs
- 分析: 同時実行数 500 までは、Go の Goroutine スケジューラが効率的に動き、全リクエストがエラーなく 0.5 秒以内に完了しました。これがこの環境の「安定稼働ライン」と言えます。
ケース3:限界突破テスト(C500Kへの挑戦)
一気に同時実行数を 50 万まで引き上げ、システムを追い込みます。
hey -n 500000 -c 500000 http://localhost:8080/fast
- 結果: PC のファンが全開になり、大量のエラーが発生。
- エラー内訳:
- context deadline exceeded (クライアント側タイムアウト)
- cannot assign requested address (OSのポート枯渇)
- 分析: サーバー側の Go が耐える前に、OS のネットワークスタックと物理的なリソース(ポート数・CPU温度)が限界を迎えました。平均レスポンスタイムも 18 秒まで悪化し、システム崩壊を確認。
4. まとめ
今回の実験を通じて、以下のことが分かりました。
- Go の並行処理の強力さ: 適正な負荷範囲内(-c 5000 程度まで)では、スループットを維持しつつ安定したレスポンスを返せる。
- 物理・OS 限界の存在: 同時接続数が数万を超えると、OS のポート制限や CPU 管理コストがボトルネックになる。
- 「崖」を見極める重要性: 負荷を上げていくと、ある一点でレスポンスタイムが指数関数的に悪化するポイントがある。
感想
非常に熱い(物理的にも)実験でしたが、Go のポテンシャルを肌で感じることができました。
膝の上にノートPCを載せていたのですが、熱すぎて「アッツ!!!」と大きめの声で言ってしまいました