はじめに
Goの障害対応でよくあるのが
- 外部APIが詰まる
- goroutineが溜まる
- タイムアウトしない
- 復旧してもプロセスが重いまま
というパターンです。
原因はだいたい
- context を受け取っていない
- context を渡していない
- タイムアウトの責務が曖昧
のどれかです。
この記事は context の仕様説明ではなく
実務で事故らないための設計チェックリストとしてまとめます。
まず決める前提
- タイムアウトは誰が決めるか(入口か呼び出し先か)
- キャンセルはどこまで伝播させるか
- 何をログに残すか(期限超過か、キャンセルか)
よくある失敗パターン
背景で勝手に動き続ける
HTTPリクエストが切れても処理が残り
- DB
- 外部API
- キュー
が詰まります。
context.Background を深いところで使う
一見動きますが
- タイムアウトが効かない
- トレースが途切れる
ので、障害時に最悪です。
WithTimeout の多重化
あちこちで勝手にタイムアウトを付けると
- どれが効いたのか分からない
- 期限が短すぎて落ちる
になります。
設計の型
境界で受け取る
- HTTPハンドラ
- gRPC
- ジョブキュー
の入口で ctx を受け取ります。
境界で締める
タイムアウトは
- 入口でまとめて付ける
のが基本です。
内部で付けるのは
- 個別I/Oだけ短くしたい
など理由があるときに限定します。
下層へ渡す
- DB
- 外部API
- 依存サービス
に必ず ctx を渡します。
チェックリスト(レビュー用)
- 境界で ctx を受け取っている
- 関数引数に ctx があるのに捨てていない
- context.Background を深い層で使っていない
- WithTimeout の責務が曖昧になっていない
- cancel を必ず呼んでいる(defer cancel)
- 期限超過とキャンセルをログで区別できる
まとめ
context はGoの並行処理の安全装置です。
- 境界で受け取る
- 境界で締める
- 下層へ渡す
この型を守るだけで、タイムアウト事故とgoroutineリークが激減します。