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 における Gra ceful Shutdown とハマりどころ
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
ogidow
November 09, 2026
240
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Go における Gra ceful 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
120
Elasticsearch勉強会
ogidow
0
3.5k
ドリコム若手発表会
ogidow
0
630
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1033
470k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.7k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
3.1k
Into the Great Unknown - MozCon
thekraken
41
2.8k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
18k
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
480
The untapped power of vector embeddings
frankvandijk
2
2k
The Curse of the Amulet
leimatthew05
3
17k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
570
Done Done
chrislema
187
17k
Heart Work Chapter 1 - Part 1
lfama
PRO
10
37k
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
ありがとうございました