Cara Implementasi Graceful Shutdown di Golang
Tutorial praktis graceful shutdown di Go. Simak cara menangani sinyal, implementasi dengan berbagai framework, dan konfigurasi PM2 untuk zero-downtime deployment
Di environment production, restart aplikasi pasti terjadi—baik karena deployment, scaling, maupun maintenance. Tanpa penanganan yang tepat, restart bisa menyebabkan user kehilangan request yang sedang diproses, koneksi database terputus, atau bahkan data corrupt.
Artikel ini akan membahas cara implementasi graceful shutdown di Go secara lengkap, mulai dari signal handling hingga integrasi dengan PM2 untuk zero-downtime deployment.
Daftar Isi
- Apa Itu Graceful Shutdown?
- Signal Handling di Go
- Implementasi HTTP Server dengan Graceful Shutdown
- Graceful Shutdown dengan Framework Populer
- Integrasi dengan PM2 untuk Zero-Downtime Reload
- Kesimpulan
Apa Itu Graceful Shutdown?
Secara singkat, graceful shutdown adalah proses untuk menghentikan aplikasi secara perlahan dan terstruktur. Berbeda dengan forced shutdown yang langsung matikan aplikasi, graceful shutdown akan memberikan waktu bagi koneksi yang sedang aktif untuk menyelesaikan tugasnya terlabih dulu.
Ada 4 hal penting yang dijamin dengan graceful shutdown:
- Request yang sedang diproses diselesaikan – Server tunggu hingga semua request selesai sebelum menutup.
- Koneksi database ditutup dengan benar – Tidak ada orphan connection atau transaksi yang terputus.
- Resource dibebaskan secara proper – File, memory, dan koneksi jaringan ditutup dengan rapi.
- Tidak ada kehilangan data – Data yang sedang diproses disimpan dulu.
Analoginya seperti restoran yang akan tutup. Staff tidak langsung usir pelanggan, tetapi akan memberitahu restoran mau tutup dan memberikan waktu untuk semua pelanggan untuk menyelesaikan makanannya. Setelah semua selesai, baru deh tutup. Nah, graceful shutdown prinsipnya sama.
Mengapa Graceful Shutdown Penting?
Di production, aplikasi pasti sering restart karena:
- Deployment – Update versi aplikasi baru
- Scaling – Tambah atau kurang instance
- Maintenance – Perbaikan sistem atau server
- Auto-restart – PM2 atau systemd restart aplikasi yang crash
Tanpa graceful shutdown, setiap restart bisa menyebabkan:
- User kehilangan request yang sedang diproses
- Data belum tersimpan menjadi corrupt
- Koneksi database jadi orphan
- Logging tidak lengkap untuk debugging
- User experience jadi buruk
Signal Handling di Go
Go punya package os/signal untuk menangkap sinyal dari sistem operasi. Yang paling umum ditangani di aplikasi server adalah sebagai berikut:
| Sinyal | Deskripsi | Kapan Terjadi |
|---|---|---|
SIGTERM | Termination request | PM2, systemd, atau proses lain |
SIGINT | Interrupt dari keyboard | Ctrl+C di terminal |
SIGQUIT | Quit dengan core dump | Untuk debugging |
SIGHUP | Hangup signal | Reload konfigurasi |
Contoh Dasar Signal Handling
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
// Buat channel untuk terima sinyal
sigChan := make(chan os.Signal, 1)
// Listen kepada sinyal yang diharapkan
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
fmt.Println("Aplikasi jalan. Tekan Ctrl+C untuk stop.")
// Tunggu sinyal
sig := <-sigChan
fmt.Printf("Terima sinyal: %v\n", sig)
fmt.Println("Menutup aplikasi...")
}Timeout untuk Graceful Shutdown
Di aplikasi nyata, untuk memastikan aplikasi nanti akan tetap dimatikan, maka kita haru menyertakan timeout, agar aplikasi tidak ditunggu selamanya:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
fmt.Println("Aplikasi jalan...")
sig := <-sigChan
fmt.Printf("Terima sinyal: %v\n", sig)
// Channel untuk tandain shutdown selesai
done := make(chan struct{})
go func() {
// Tambah cleanup logic di sini
fmt.Println("Lagi cleanup...")
time.Sleep(2 * time.Second)
fmt.Println("Cleanup beres")
close(done)
}()
// Beri timeout 10 detik
select {
case <-done:
fmt.Println("Aplikasi berhenti aman")
case <-time.After(10 * time.Second):
fmt.Println("Timeout! Aplikasi dipaksa stop")
}
os.Exit(0)
}Implementasi HTTP Server dengan Graceful Shutdown
Package net/http di Go punya method Shutdown() yang sangat berguna untuk ini. Berikut ini adalah salah satu contoh implementasi graceful shutdown:
Contoh Lengkap HTTP Server
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// Inisialisasi router
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"healthy"}`))
})
mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
w.Write([]byte("Selesai setelah 5 detik"))
})
// Konfigurasi server
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Jalanin server dalam goroutine
go func() {
log.Println("Server started on :8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
// Tunggu sinyal shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Menerima sinyal shutdown...")
// Context dengan timeout 30 detik
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Shutdown server
if err := server.Shutdown(ctx); err != nil {
log.Printf("Server shutdown error: %v", err)
}
log.Println("Server berhenti aman")
}Tambahkan Cleanup untuk Database
Pastikan untuk menutup semua koneksi ke database, agar koneksi tidak dangling, yang bisa mengurangi jatah koneksi:
package main
import (
"context"
"database/sql"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
_ "github.com/lib/pq"
)
type App struct {
server *http.Server
db *sql.DB
}
func main() {
db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
if err != nil {
log.Fatal(err)
}
// hilangkan defer, karena sudah ada close di akhir aplikasi
//defer db.Close()
app := &App{db: db}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
app.server = server
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Printf("Server shutdown error: %v", err)
}
if err := db.Close(); err != nil {
log.Printf("Database close error: %v", err)
} else {
log.Println("Database connection closed")
}
log.Println("Aplikasi berhenti aman")
}Graceful Shutdown dengan Framework Populer
Banyak developer Go yang menggunakan framework seperti Gin, Fiber, atau Echo. Berikut ini adalah cara implementasi graceful shutdown di masing-masing framework:
Gin
Gin adalah framework Go yang paling populer. Implementasinya:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(gin.Recovery())
router.Use(gin.Logger())
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello from Gin!")
})
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
log.Println("Gin server starting on :8080")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down Gin server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("Server shutdown error: %v", err)
}
log.Println("Gin server stopped gracefully")
}Catatan:
- Gin sebenarnya menggunakan
http.Serversecara internal, jadi bisa lengsung menggunakansrv.Shutdown() - Wajib menggunakan
gin.SetMode(gin.ReleaseMode)di production - Middleware
gin.Recovery()wajib untuk meng-handle panic
Fiber
Fiber adalah framework Express-like yang sangat cepat karena menggunakan fasthttp. Berikut ini adalah contoh implementasinya:
package main
import (
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
)
func main() {
app := fiber.New(fiber.Config{
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
GracefulShutdown: true,
})
app.Use(recover.New())
app.Use(logger.New())
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello from Fiber!")
})
app.Get("/health", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"status": "healthy"})
})
errChan := make(chan error, 1)
go func() {
log.Println("Fiber server starting on :8080")
if err := app.Listen(":8080"); err != nil {
errChan <- err
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
select {
case sig := <-quit:
log.Printf("Received signal: %v", sig)
case err := <-errChan:
log.Printf("Server error: %v", err)
}
log.Println("Shutting down Fiber server gracefully...")
if err := app.ShutdownWithTimeout(30 * time.Second); err != nil {
log.Printf("Server shutdown error: %v", err)
}
log.Println("Fiber server stopped gracefully")
}Catatan:
- Fiber memiliki konfigurasi
GracefulShutdown: trueyang mempermudah proses shutdown - Gunakan
app.ShutdownWithTimeout()untuk membatasi waktu shutdown - Fiber sangat cepat tapi ada beberapa perbedaan dengan
net/http
Echo
Echo adalah framework yang ringan dan fleksibel. Berikut ini implementasinya:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello from Echo!")
})
e.GET("/health", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"status": "healthy",
})
})
srv := &http.Server{
Addr: ":8080",
Handler: e,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
log.Println("Echo server starting on :8080")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down Echo server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("Server shutdown error: %v", err)
}
log.Println("Echo server stopped gracefully")
}Catatan:
- Echo juga menggunakan
http.Serverjadi miripnet/httpstandar - Echo punya middleware yang berguna seperti CORS, Logger, dan Recover
- Echo sangat fleksibel dan cocok untuk API yang kompleks
Perbandingan Framework
| Aspek | Gin | Fiber | Echo |
|---|---|---|---|
| Engine | net/http | fasthttp | net/http |
| Graceful Shutdown | srv.Shutdown() | app.ShutdownWithTimeout() | srv.Shutdown() |
| Performa | Sangat cepat | Tercepat | Cepat |
Integrasi dengan PM2 untuk Zero-Downtime Reload
PM2 adalah process manager yang populer buat manage aplikasi Go di production. Dengan konfigurasi yang tepat, PM2 bisa zero-downtime reload yang bekerja sama sama graceful shutdown di Go.
Konfigurasi PM2 Ecosystem File
module.exports = {
apps: [
{
name: "graceful-go-app",
script: "./myapp",
exec_mode: "fork",
instances: 1,
env: {
PORT: 8080,
LOG_LEVEL: "info",
},
// Pengaturan untuk graceful shutdown
kill_timeout: 30000, // 30 detik timeout
wait_ready: true, // Tunggu aplikasi siap
listen_timeout: 5000, // Timeout untuk siap
// Pengaturan restart
max_restarts: 10,
min_uptime: "10s",
// Log
out_file: "~/.pm2/logs/graceful-go-app-out.log",
error_file: "~/.pm2/logs/graceful-go-app-error.log",
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
merge_logs: true,
},
],
};Penjelasan Konfigurasi
| Opsi | Nilai | Fungsi |
|---|---|---|
kill_timeout | 30000 | Waktu buat graceful shutdown sebelum PM2 kill proses |
wait_ready | true | PM2 tunggu aplikasi kirim sinyal siap |
listen_timeout | 5000 | Timeout buat terima sinyal ready |
Perintah PM2 untuk Zero-Downtime Reload
# Build aplikasi Go
go build -o myapp
# Jalankan dengan PM2
pm2 start ecosystem.config.js
# Zero-downtime reload
pm2 reload graceful-go-app
# Atau untuk restart dengan delay
pm2 restart graceful-go-app
# Lihat status
pm2 status
pm2 logs graceful-go-app
Kesimpulan
Graceful shutdown wajib ada di aplikasi zonba production. Dengan implementasi yang tepat, maka bisa dipastikan:
- Tidak ada kehilangan data – Request yang sedang diproses akan ditunggu sampai selesai dulu
- User experience tetap baik – User tidak dapat error tiba-tiba
- Resource bersih – Koneksi database dan resource lain ditutup dengan benar
- Zero-downtime deployment – Aplikasi bisa di-update tanpa mengganggu user
Kunci utamanya:
- Tangkap sinyal
SIGINTdanSIGTERM - Pakai
contextdengan timeout - Tutup semua resource dalam urutan yang benar
- Konfigurasi PM2 dengan
kill_timeoutyang sesuai
Langkah selanjutnya:
Silakan coba implementasikan graceful shutdown di aplikasi Go Anda. Jika Anda menggunakan PM2 untuk deployment, silahkan baca juga panduan Deploy Aplikasi Go dengan PM2 untuk konfigurasi lengkapnya.


