Go: singleflight で同じキーの同時呼び出しを1本にまとめる
golang.org/x/sync/singleflight は、同じキーへの並行呼び出しを1回の実行にまとめ、結果を全員に返す。キャッシュ失効直後の DB スタンピード対策に向く。
使う場面
ホットなキャッシュキーが切れ、数百リクエストが一斉に同じ loadFromDB(key) を叩くとき。セマフォで同時数を絞るだけでは「同じ仕事の重複」は減らない。
やり方
go
import "golang.org/x/sync/singleflight"
var g singleflight.Group
func getUser(ctx context.Context, id string) (User, error) {
v, err, _ := g.Do(id, func() (any, error) {
return loadFromDB(ctx, id)
})
if err != nil {
return User{}, err
}
return v.(User), nil
}- 第3戻り値
sharedは、他の呼び出しと結果を共有したかどうか(デバッグやメトリクス用)。 DoChanでチャネル版も使える。キャンセルしたい場合は呼び出し側で context を見て打ち切る(実行中の関数自体は止めない点に注意)。- 関連: 並列数の上限は Go: errgroup.SetLimit で並列数に上限を付ける(自前のセマフォが要らない)。重い CPU 処理の同時実行は Go: Argon2id のハッシュ計算はセマフォで同時実行数を絞る(ログイン集中でメモリが溢れるのを防ぐ)。
注意点
Doの中でパニックすると、待っていた全員に伝播する。- キーの粒度が粗すぎると無関係な呼び出しまで待たされる。細すぎると効果が薄い。
- 失敗も共有されるので、一時的なエラーをキャッシュに載せたくないなら、呼び出し側でリトライ方針を決める。
- 標準ライブラリではなく
x/syncモジュール。go get golang.org/x/syncが必要。