Golang sync.Pool はシルバーバレットではない
sync.Pool とは¶
sync.Pool はオブジェクトを使いまわすためのキャッシュ。
毎回 new() や make() でアロケーションするのではなく、プールから取り出して使い終わったら返すことでGCの負荷を下げる。
var pool = sync.Pool{
New: func() any {
return &bytes.Buffer{}
},
}
// 取り出す
buf := pool.Get().(*bytes.Buffer)
buf.Reset() // 前の使用の残りをクリア
// 使う
buf.WriteString("hello")
// 返す
pool.Put(buf)
どんな場面で使うか¶
高スループットなサーバーで、短命な一時オブジェクトを大量に生成するケース。
典型例: - JSONエンコード/デコードのバッファ - ログのフォーマット用バッファ - 一時的なスライス
// HTTPハンドラで毎リクエストバッファをプールから取得
func handler(w http.ResponseWriter, r *http.Request) {
buf := pool.Get().(*bytes.Buffer)
defer pool.Put(buf)
buf.Reset()
json.NewEncoder(buf).Encode(response)
w.Write(buf.Bytes())
}
なぜシルバーバレットでないのか¶
1. GCでプールが空になる¶
sync.Pool のオブジェクトはGCのたびにクリアされる可能性がある。
低負荷時はプールが空になりやすく、New でアロケーションが走ってしまう。
→ GCの頻度が高い環境では恩恵が減る
2. プール返却を忘れるとリーク¶
Put を呼び忘れるとオブジェクトがプールに戻らず、プールが機能しない。
defer pool.Put(obj) を徹底する必要がある。
3. Reset を忘れると前のデータが残る¶
バッファをプールから取り出したら必ず初期化する。 忘れると前のリクエストのデータが混入するバグが起きる。
4. 重いオブジェクトには向かない¶
DBコネクションのような重いリソースにはコネクションプール(database/sql の内部プール)を使うべき。
sync.Pool は軽量な一時オブジェクト専用。
5. 並行性の罠¶
sync.Pool はgoroutine-safeだが、取り出したオブジェクトを複数goroutineで共有してはいけない。
Get → 使用 → Put の間は1つのgoroutineが持つ。
使うべきかの判断基準¶
| 条件 | sync.Pool使用 |
|---|---|
| 同じ型のオブジェクトを短命に大量生成する | 向いている |
| GCプレッシャーが実際にボトルネック | 向いている |
| オブジェクトの生存時間が長い | 向かない |
| 重いリソース(DB、ファイル) | 向かない |
| まず計測していない | まず計測する |
最初からsync.Poolを入れるのではなく、プロファイリングでアロケーションがボトルネックと判明してから使う。