Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Go における Graceful Shutdown とハマりどころ
Search
ogidow
November 09, 2026
2.3k
5
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Go における Graceful Shutdown とハマりどころ
ogidow
November 09, 2026
More Decks by ogidow
See All by ogidow
minneにおけるGraphQLの導入事例
ogidow
0
1.4k
Gopher-dojo-lt.pdf
ogidow
1
130
Elasticsearch勉強会
ogidow
0
3.5k
ドリコム若手発表会
ogidow
0
630
Featured
See All Featured
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
350
Product Roadmaps are Hard
iamctodd
55
13k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
Building Better People: How to give real-time feedback that sticks.
wjessup
371
20k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
470
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.2k
Utilizing Notion as your number one productivity tool
mfonobong
4
640
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
1
300
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
520
First, design no harm
axbom
PRO
2
1.4k
Believing is Seeing
oripsolob
1
270
How to Ace a Technical Interview
jacobian
280
24k
Transcript
Go における Graceful Shutdown とハマりどころ 2026-11-09 layerx.go #6 荻堂 修太@ogidow
荻堂 修太 LayerX バクラク事業部 債務管理開発チーム 2026年4月入社 沖縄在住 X: @ogidow ©
LayerX Inc.
今日話すこと Graceful Shutdown とは Go での実装方法とハマりどころ © LayerX Inc. 3
みなさん Graceful Shutdown していますか?
Graceful Shutdown とは 新しいリクエストの受付を止めて処理中のリクエストを終わらせてから止めること 終わるまで待つ 短いリクエストならこれで十分 © LayerX Inc. 途中で切り上げる(今日の話)
SIGTERM が来たら切れ目で終わる あとで続きから再開する 長い処理向き 5
今日の題材:お知らせメールを一斉送信する API リクエストを1回受けると100人に1通ずつメールを送る 1通1秒かかるので全部で100秒くらいかかる func notify(w http.ResponseWriter, r *http.Request) {
for _, u := range users { sendMail(u) // 1通1秒かかる } } © LayerX Inc. 6
デプロイすると 実行中の長い処理はどうなる?
SIGTERM を受けた時点でサーバは終了する お知らせメールを100人に送る メールを送っている 残りの人には送られない デプロイで SIGTERM が届く → その場で終了
やり直すと最初から送るので全員にまた届く © LayerX Inc. 8
送信したらマークしてマーク済みはスキップする func notify(w http.ResponseWriter, r *http.Request) { ctx := r.Context()
// このリクエストの ctx id := r.URL.Query().Get("id") for _, u := range users { if isSent(id, u) { // 送信済みならスキップ continue } sendMail(u) // 1通1秒かかる markSent(ctx, id, u) // 送信済みとして DB に記録する } } これでやり直しても送信した人には届かない © LayerX Inc. 9
問題はメール送信とマークの間 for _, u := range users { if isSent(id,
u) { // 送信済みならスキップ continue } sendMail(u) // メールは届く // ← SIGTERM markSent(ctx, id, u) // 実行されない } ここで が来てサーバが終了 記録が残らないのでやり直すと同じ人にまた届く © LayerX Inc. 10
SIGTERM が来たら今の1通を終えてから止めたい 古いサーバ メールを送っている この1通まで SIGTERM 新しいサーバ あとで同じリクエストが来たら 続きの人から送る ©
LayerX Inc. 11
そうだ Graceful Shutdown だ
Shutdown を呼ぶと受付を止めて処理中のリクエストを待つ func main() { // SIGTERM を受けるとこの ctx がキャンセルされる
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM) defer stop() srv := &http.Server{Addr: ":8080", Handler: http.HandlerFunc(notify)} go srv.ListenAndServe() <-ctx.Done() // SIGTERM が来るまでここで待つ // 処理中のリクエストが終わるのを最大10秒待つ shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Shutdown(shutdownCtx) } © LayerX Inc. 13
1通送るたびにキャンセルされていないか確認する func notify(w http.ResponseWriter, r *http.Request) { ctx := r.Context()
// このリクエストの ctx id := r.URL.Query().Get("id") for _, u := range users { if ctx.Err() != nil { // キャンセルされていたら抜ける return } if isSent(id, u) { // 送信済みならスキップ continue } sendMail(u) // 1通1秒かかる markSent(ctx, id, u) // 送信済みとして DB に記録する } } © LayerX Inc. 14
SIGTERM が来ると ハンドラの ctx はキャンセルされる?
SIGTERM が来てもキャンセルされず if では抜けない for _, u := range users
{ if ctx.Err() != nil { // nil → return } if isSent(id, u) { // 送信済みならスキップ continue } sendMail(u) // メールを送り続ける markSent(ctx, id, u) // 送信済みとして DB に記録する } のまま 抜けない 10秒たつと Shutdown が時間切れになりプログラムが終了する 送信とマークの間で終了するとやり直したときに二重に届く © LayerX Inc. 16
Shutdown はハンドラに何も伝えない Shutdown gracefully shuts down the server without interrupting
any active connections. ... and then waiting indefinitely for connections to return to idle ... Shutdown は処理中の接続を中断せずにサーバを止める 接続がアイドルに戻るのを無期限に待つ net/http ドキュメント(Server.Shutdown)と日本語訳 © LayerX Inc. 17
main が終わるとハンドラも途中で消える main Shutdown で待っている ハンドラ メールを送り続ける(ctx はキャンセルされない) つまり 0秒:SIGTERM
10秒:main が終わる → プログラムも終わる SIGTERM が来てもハンドラは気づかずメールを送り続ける 10秒たつと main が終わりハンドラも途中で消える 送信とマークの間で消えるとやり直したときに二重に届く © LayerX Inc. markSent 18
キャンセルは親から子にしか伝わらない parent, cancel := context.WithCancel(context.Background()) child, _ := context.WithCancel(parent) other,
_ := context.WithCancel(context.Background()) cancel() child.Err() // context canceled ← 子には伝わる other.Err() // nil ← 親が違うと伝わらない © LayerX Inc. 19
main の ctx とハンドラの ctx はつながっている? func main() { //
SIGTERM ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM) // notify srv := &http.Server{Addr: ":8080", Handler: http.HandlerFunc(notify)} ... } でキャンセルされる をこのサーバのハンドラとして登録する func notify(w http.ResponseWriter, r *http.Request) { // ctx := r.Context() ... } ハンドラが見ている r.Context() © LayerX Inc. の親は誰? 20
r.Context() の親は net/http の中で作られる // net/http (*Server).Serve // baseCtx r.Context()
baseCtx := context.Background() if s.BaseContext != nil { baseCtx = s.BaseContext(origListener) } この BaseContext をもとに が作られる を渡していないので親は context.Background() srv := &http.Server{Addr: ":8080", Handler: http.HandlerFunc(notify)} main の ctx とは親が違うので SIGTERM は伝わらない © LayerX Inc. 21
BaseContext に main の ctx を渡す srv := &http.Server{ Addr:
":8080", Handler: http.HandlerFunc(notify), BaseContext: func(net.Listener) context.Context { return ctx }, } © LayerX Inc. 22
r.Context() の親が main の ctx になる main の ctx(signal.NotifyContext) BaseContext
で親にする r.Context()(ハンドラが見ている) SIGTERM のキャンセルがハンドラまで伝わる SIGTERM が来た時点で もキャンセルされる ハンドラは次の で気づく r.Context() ctx.Err() © LayerX Inc. 23
SIGTERM が来たら if で抜けるようになった for _, u := range users
{ if ctx.Err() != nil { // context canceled → return } ... } 直す前 Shutdown は10秒待って時間切れ ハンドラは途中で消える © LayerX Inc. ここで抜ける 直した後 Shutdown はすぐ戻る ハンドラは if で抜ける 24
送信中に SIGTERM が来ると markSent が失敗する for _, u := range
users { if ctx.Err() != nil { return } ... sendMail(u) // この送信中に SIGTERM が来ると markSent(ctx, id, u) // ctx } はキャンセル済みで失敗する 記録が残らないのでやり直すと同じ人にまた届く © LayerX Inc. 25
止めたくない処理は ctx のキャンセルを切り離す sendMail(u) markSent(context.WithoutCancel(ctx), id, u) // キャンセルされず記録できる これで
SIGTERM が来てもやり直したときに二重に届かない © LayerX Inc. 26
まとめ Shutdown だけでは処理は切り上げられない BaseContext に main の ctx を渡す ©
LayerX Inc. 27
ありがとうございました