Graceful(優雅な)Shutdown = サーバーを安全に停止する仕組み
code:go
package main
import (
"context"
"os/signal"
"syscall"
// ... 他のimport
)
func main() {
// ... サーバー設定 ...
// 1. サーバーをgoroutineで起動
go func() {
core.Logger.Info("Server is starting", "addr", server.Addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
core.Logger.Error("server failed", slog.Any("err", err))
os.Exit(1)
}
}()
// 2. シグナル受信チャネルを作成
quit := make(chan os.Signal, 1)
// 3. Ctrl+CやTERMシグナルを監視
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// - SIGINT: Ctrl+Cで送信されるシグナル
// - SIGTERM: killコマンドで送信されるシグナル
// - バッファサイズ1: シグナルを確実に受信
// 4. シグナルを待機(ここでブロック)
<-quit // ここで待機
core.Logger.Info("Shutting down server...")
// - main関数がここで停止
// - シグナルが来るまで待機
// - サーバーは動作継続
// 5. Graceful Shutdown開始(30秒のタイムアウト)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// - 新規接続: 拒否
// - 処理中の接続: 最大30秒待つ
// - 30秒後: 強制終了
// 6. 処理中のリクエストが完了するまで待つ
if err := server.Shutdown(ctx); err != nil {
core.Logger.Error("Server forced to shutdown", slog.Any("err", err))
os.Exit(1)
}
core.Logger.Info("Server exited")
}
Graceful Shutdown実装のメリット:
1. 処理中のリクエストを完了させる
2. データの整合性を保つ
3. リソースを適切に解放する
4. 本番環境での安全な再起動が可能
実際の動作フロー
code:go
1. サーバー起動
$ go run main.go
INFO: Server is starting addr=:8080
2. 通常動作中
- リクエスト処理
- 新規接続受付
3. Ctrl+C押下
^C
INFO: Shutting down server...
4. Graceful Shutdown
- 新規接続を拒否
- 処理中: /api/heavy (残り10秒)
- 処理中: /api/upload (残り5秒)
- 待機中...
5. 全リクエスト完了
INFO: Server exited