Skip to content

Graceful Shutdown คืออะไร [TH Version]

Posted on:January 3, 2024

Graceful Shutdown คืออะไร [TH Version]

Graceful Shutdown คืออะไร

แปลตรง ๆ ก็คือ “การปิดระบบอย่างสง่างาม” หมายถึงการที่ application ที่เริ่มทำงานไปแล้ว ต้องหยุดทำงานด้วยเหตุผลต่าง ๆ เช่น การ upgrade version จึงต้องการหยุดการทำงานของ version เก่า ซึ่งการทำ graceful shutdown จะเป็นการทำให้ application รู้ว่าต้องหยุดทำงานแล้ว ไม่รับงานเพิ่ม แต่จะหยุดรอกระบวนการทำงานต่าง ๆ ให้เสร็จเรียบร้อยก่อน และทำการคืน resource ต่าง ๆ ที่ใช้งานอยู่

ตัวอย่างการทำ Graceful Shutdown ของ Http Server

เริ่มต้นด้วยการทำ http server รอรับ request ซึ่งจะทำการรอ 10 วินาที และ return string Hello World กลับไป

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(10 * time.Second)
		w.Write([]byte("Hello World"))
	})

	http := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	fmt.Println("Server running at :8080")
	err := http.ListenAndServe()
	if err != nil {
		panic(err)
	}
	fmt.Println("Server stopped")
}

ซึ่งหากเราทำการ request แล้วรอ 10 วินาที เราก็จะได้ response ปกติ แต่หาก application ของเราถูกหยุดระหว่างประมวลผล application ของเราก็หยุดการทำงาน และออกจากโปรแกรมทันที ไม่รอให้ทำงานเสร็จก่อน ซึ่งการทำ graceful shutdown จะเข้ามาแก้ปัญหานี้

stop-without-graceful-shutdown

อีกทั้ง Response ที่ฝั่ง Client ได้รับก็ไม่สมบูรณ์ T T

client-without-graceful-shutdown

เรามาเริ่มทำ graceful shutdown ให้ http server กันดีกว่า เริ่มจากการ handle signal ของการหยุดการทำงานของ application หรือก็คือ SIGINT, SIGTERM โดยจะเป็นการทำให้ application ของเรารู้ว่าจะต้องหยุดแล้ว จากทำการเรียก function Shutdown ที่มากับ http.Server ที่เป็น standard lib ของ Golang อยู่แล้วสามารถใช้ได้เลย

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(10 * time.Second)
		w.Write([]byte("Hello World"))
	})

	http := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	fmt.Println("Server running at :8080")
	go func() {
		http.ListenAndServe()
	}()

	// Wait for interrupt signal
	exit := make(chan os.Signal, 1)
	signal.Notify(exit, os.Interrupt)
	signal.Notify(exit, os.Kill)

	<-exit
	// Create a deadline to wait for.
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	if err := http.Shutdown(ctx); err != nil {
		panic(err) // failure/timeout shutting down the server gracefully
	}

	fmt.Println("Server stopped")
}

ทดสอบหยุด application ระหว่างที่กำลังประมวลผล สังเกตุได้ว่า application ไม่ได้ทำการหยุดลงทันที แต่จะรอให้ประมวลผล request เสร็จก่อน แล้วจึงทำงานต่อไปจนจบการทำงานทั้งหมด และปิดตัวลง

stop-with-graceful-shutdown

หากยังมี request เข้ามาระหว่างที่ application กำลังจะปิดตัวลง http server ก็จะไม่รับ request แล้ว (Load balance ไม่ควร route request เข้ามายัง application ที่กำลังหยุดทำงาน)

request-after-shutdown

ส่วน request ที่กำลังประมวลผลอยู่ ก็จะทำงานต่อไปและ response กลับไปที่ client อย่างสมบูรณ์ เย่~~ Happy ending

request-during-shutdown

Conclusion

การทำ graceful shutdown ช่วยให้การทำงานของ application ของเราสมบูรณ์มากขึ้น แม้ว่ากำลังจะปิดตัวลง นำไปใช้ได้กับอีกหลาย ๆ สถานะการณ์ เช่น การรอให้ worker ประมวลผลงานที่กำลังทำอยู่ให้เสร็จก่อน, การปิด database connection และอื่น ๆ อีกมากมาย