Graceful Shutdown

Graceful shutdown adalah mekanisme ketika server dimatikan, tapi tidak akan langsung dimatikan, tetapi akan menunggu dulu ketika ada sebuah request yang sedang diproses diselesaikan terlebih dahulu.

Pernah kepikiran kalau semisalkan kita mematikan server, request yang sedang diproses apakah akan lanjut diproses atau langsung terhenti? Kalau tidak menggunakan graceful shutdown, request tersebut akan terhenti tanpa dilanjutkan prosesnya.

Kalau menggunakan graceful shutdown, ketika server dimatikan, dia nunggu dulu nih request yang ada diproses dulu, nah setelah itu server dimatikan.

Bingung ga? bingung lah haha, ntar deh kalau udah masuk ke praticalnya bakal ngerti.

Di contoh ini saya akan menggunakan bahasa pemrograman Go, disini saya akan membuat sebuah endpoint API sederhana.

Yang pertama buat handler terlebih dahulu

func index(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(map[string]any{
		"code":    http.StatusOK,
		"message": "server up",
	})
}

Dan buat main function

func main() {
	srv := &http.Server{
		Addr:    "localhost:3000",
		Handler: nil,
	}

	http.HandleFunc("/", index)

	log.Println("server running at", srv.Addr)
	if err := srv.ListenAndServe(); err != nil {
		log.Fatal("error listening", err)
	}
}

Ya sederhana aja, bakal return JSON.

PS C:\Users\ASUS> curl http://localhost:3000

StatusCode        : 200
StatusDescription : OK
Content           : {"code":200,"message":"server up"}

RawContent        : HTTP/1.1 200 OK
                    Content-Length: 35
                    Content-Type: text/plain; charset=utf-8
                    Date: Sun, 03 Jul 2022 07:24:21 GMT

                    {"code":200,"message":"server up"}

Forms             : {}
Headers           : {[Content-Length, 35], [Content-Type, text/plain; charset=utf-8], [Date, Sun, 03 Jul 2022 07:24:21
                    GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 35

Skenario tanpa menggunakan graceful shutdown

Sekarang buat skenario tanpa menggunakan graceful shutdown. Normalnya ketika mematikan server tanpa graceful shutdown, maka server akan langsung dimatikan, ga peduli deh ada suatu request yang sedang diproses atau engga pokoknya langsung dimatiin.

Yang pertama kita akan membuat skenario delay ketika mengakses endpoint APInya, pakai time.Sleep() aja.

func index(w http.ResponseWriter, r *http.Request) {
	time.Sleep(10 * time.Second) // skenario delay 10 detik mengakses endpoint root /
	json.NewEncoder(w).Encode(map[string]any{
		"code":    http.StatusOK,
		"message": "server up",
	})
}

Image 1

Dari GIF di atas saya mengirim request ke endpoint root dengan delay 10 detik. Yes nothing happen dan proses curl berhasil diproses, but what will happen if we turning off the server ketika request curl sedang diproses? (jaksel banget dah ah)

Image 2

GIF di atas adalah skenario ketika saya mematikan server tapi ada request curl yang sedang diproses. Oke servernya mati, tapi request curl yang sedang diproses akan langsung terhenti juga.

Well that’s not good tho, sebaiknya semisalkan servernya dimatikan, kenapa ga selesaikan dulu proses yang ada? setelah itu matikan servernya.

Nah kita akan bahas masalah itu di chapter di bawah ini.

Skenario menggunakan graceful shutdown

Nah sekarang skenario menggunakan graceful shutdown, bakal banyak nih kodingan yang berubah disini, semoga lu paham aja ngebacanya ya haha.

Step:

  1. Running server menggunakan goroutine.
  2. Buat channel untuk menerima sinyal SIGTERM atau Interrupt.
  3. Shutdown server menggunakan context timeout 30 detik.

Kenapa menggunakan channel? karena channel sifatnya blocking, nantinya ketika receive sinyal interrupt bakal dimasukkan ke dalam tipe data channel, ketika channelnya terisi maka ga akan blocking lagi dan akan lanjut ke proses shutdown.

Mostly kodingan yang bakal banyak berubah banyak di main function

func main() {
	srv := &http.Server{
		Addr:    "localhost:3000",
		Handler: nil,
	}

	http.HandleFunc("/", index)

	// run server menggunakan goroutine
	go func() {
		log.Println("server running at", srv.Addr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatal("error listening", err)
		}
	}()

	// buat channel dan terima sinyal interrupt atau sigterm
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)

	// masukkin data channel ke variabel sig
	// dan close channelnya
	sig := <-c
	log.Println("got signal", sig)
	defer close(c)

	// buat context dengan timeout
	// 30 detik
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// proses shutting down server
	log.Println("shutting down server..")
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("error shutting down server", err)
	}

	log.Println("server shutted down successfully")
}

Image 3

GIF di atas adalah skenario ketika menggunakan graceful shutdown.

Perhatiin deh saya ngelakuin request curl ke endpoint APInya, tapi tiba-tiba servernya dimatikan, nah servernya ga bakal langsung mati, tapi nunggu dulu proses curlnya diselesaikan dulu, ketika udah selesai.. setelah itu servernya mati.

Coba sandingkan dengan GIF kedua yang di chapter skenario tanpa graceful shutdown, keliatan deh perbedaannya gimana.

Gimana? keren kan.

Itu point utama tentang graceful shutdown. Sebenarnya ya lebih ke implementasi di backend cara ini. Graceful shutdown ga hanya bisa diimplementasikan di bahasa pemrograman Go, bisa di bahasa pemrograman apa aja kok, contohnya kaya Node JS atau Java Spring Boot.

Referensi: