Go भाषा में Graceful Shutdown लागू करने के व्यावहारिक पैटर्न
(victoriametrics.com)- Go applications में Graceful Shutdown वह shutdown प्रक्रिया है जिसमें नए requests रोके जाते हैं, चल रहे काम के खत्म होने का इंतज़ार किया जाता है, और database connections, file locks, network listeners जैसे resources साफ किए जाते हैं
- Shutdown handling की शुरुआत
SIGTERM·SIGINTजैसे shutdown signals कोos/signalया Go 1.16+ केsignal.NotifyContextसे पकड़कर default immediate shutdown behavior को बदलने से होती है - Kubernetes में default 30-second grace period के भीतर shutdown पूरा करना होता है, और
preStopdelay या readiness probe failure के जरिए external load balancer तक traffic रोकने की स्थिति propagate होने का समय देना चाहिए http.Server.Shutdownनए connections रोकता है और active requests के पूरा होने का इंतज़ार करता है, लेकिन अगर handler context cancellation का पालन नहीं करता, तो partial writes, data loss, open transactions जैसी समस्याएँ हो सकती हैं- अहम resources को shutdown signal मिलते ही नहीं, बल्कि requests खत्म होने के बाद या timeout expiry के बाद साफ करना चाहिए; initialization के reverse order में shutdown करने से component dependencies बनाए रखना आसान होता है
Graceful Shutdown की न्यूनतम शर्तें
- Graceful Shutdown को आम तौर पर तीन शर्तें पूरी करनी चाहिए
- HTTP, pub/sub जैसे entry points पर नए requests या messages स्वीकार न करना
- पहले से चल रहे requests के खत्म होने तक इंतज़ार करना, और बहुत ज्यादा समय लगने पर graceful error से जवाब देना
- database connections, file locks, network listeners जैसे महत्वपूर्ण resources release करना और final cleanup करना
- बाहरी services की ओर जाने वाले database या cache connections को नए requests रोकने वाले step में तुरंत disconnect नहीं किया जाता
- focus HTTP servers और container applications पर है, लेकिन core principles दूसरे applications पर भी लागू हो सकते हैं
Shutdown signals handling
- Unix-like systems में signals software interrupts होते हैं जो process को बताते हैं कि कोई खास स्थिति हुई है
- process किसी specific signal के लिए handler register कर सकता है, और handler न होने पर default behavior follow होता है
- default behavior terminate, stop, continue running, ignore आदि हो सकता है
SIGKILLजैसे कुछ signals पकड़े या ignore नहीं किए जा सकते और process को terminate करते हैं
- Go runtime
mainfunction चलने से पहले हीSIGTERM,SIGQUIT,SIGILL,SIGTRAPआदि कई signal handlers automatically register करता है - Graceful Shutdown में मुख्य रूप से तीन shutdown signals महत्वपूर्ण होते हैं
SIGTERM: process को terminate करने का standard और polite तरीका, और Kubernetes द्वारा force termination से पहले application को भेजा जाने वाला signalSIGINT: user जब terminal मेंCtrl+Cसे process रोकने की कोशिश करता है, तब भेजा जाता हैSIGHUP: मूल रूप से terminal disconnect के लिए इस्तेमाल होता था, और अब अक्सर configuration reload signal के रूप में भी उपयोग होता है
- अलग से handling न होने पर,
SIGTERM,SIGINT,SIGHUPमिलने पर Go runtime application को terminate कर देता है
os/signal और NotifyContext
signal.NotifyGo runtime को निर्देश देता है कि specified signals को default behavior की जगह channel पर deliver करे- signal channel को buffer size 1 के साथ बनाना अधिक stable माना जाता है
- Go internals channel send के लिए
selectऔरdefaultका उपयोग करते हैं - buffer में जगह हो तो signal deliver होता है, और buffer full हो तो signal drop हो जाता है
- unbuffered channel में अगर receive कर रही goroutine न हो तो signal miss हो सकता है
- Go internals channel send के लिए
signal.Notifyको एक ही signal के लिए कई बार call किया जा सकता है, और Go उस signal को सभी registered channels पर भेजता हैCtrl+Cकई बार दबाने पर भी आम तौर पर दूसरी input अपने आपSIGKILLमें promote नहीं होती- अधिकांश bash या Linux shells automatic promotion नहीं करते
- force termination के लिए
kill -9से सीधेSIGKILLभेजना पड़ता है
- local development में दूसरे
Ctrl+Cसे force termination करवाना हो तो पहला signal मिलते हीsignal.Stopसे आगे signal receiving रोक सकते हैं - Go 1.16 से
signal.NotifyContextके जरिए signal handling को context cancellation से जोड़ा जा सकता हैctx.Done()के बाद भीstop()call करना चाहिए, ताकि दूसराCtrl+Capplication को forcefully terminate कर सके
Shutdown timeout और Kubernetes behavior
- shutdown signal मिलने के बाद application वास्तव में कितना shutdown time इस्तेमाल कर सकता है, यह पहले जानना चाहिए
- Kubernetes का default grace period, अगर
terminationGracePeriodSecondsअलग से specify न किया गया हो, 30 seconds है - यह समय बीतने पर Kubernetes
SIGKILLभेजकर application को forcefully stop कर देता हैSIGKILLको पकड़ा या handle नहीं किया जा सकता
- remaining request processing और resource release सहित पूरा shutdown logic इसी समय के भीतर खत्म होना चाहिए
- default 30 seconds के आधार पर करीब 20% safety margin छोड़ें, तो पूरा shutdown 25 seconds के अंदर खत्म करना बेहतर है
नए requests रोकना और readiness handling
- Go के
net/httpमेंhttp.Server.Shutdownसे Graceful Shutdown किया जा सकता है- नए connections accept करना बंद करता है
- active requests के complete होने तक इंतज़ार करता है
- इसके बाद idle connections बंद करता है
- पहले से चल रहे requests complete हो सकते हैं, और complete होने के बाद वह connection idle state में जाकर बंद हो जाता है
- shutdown के दौरान नया connection try करने वाले clients को listener पहले ही बंद होने के कारण आम तौर पर
connection refusederror मिलता है - container environment या external load balancer वाली orchestration environment में नए requests accept करना तुरंत बंद न करना महत्वपूर्ण है
- pod को termination target के रूप में mark किए जाने के बाद भी थोड़ी देर traffic मिल सकता है
- Kubernetes internal component
kube-proxypod state केTerminatingमें बदलने को जल्दी पहचान लेता है - external load balancer Kubernetes से independent होकर अपना health check use करता है, इसलिए state propagation में समय लगता है
- traffic blocking propagation का इंतज़ार करने के दो तरीके हैं
preStophook में थोड़ी देरsleepकरके external load balancer को pod termination state recognize करने का समय देनाpreStopमें लगा समयterminationGracePeriodSecondsमें शामिल होता है
- code level पर readiness probe fail करना और थोड़ी देर इंतज़ार करना
- यह Kubernetes के अलावा उन environments में भी लागू हो सकता है जहाँ load balancer को ready state पता होनी चाहिए
- readiness probe periodically check करता है कि container traffic receive करने के लिए ready है या नहीं
- HTTP request, TCP connection, command execution जैसे तरीकों से health check किया जा सकता है
- probe fail होने पर Kubernetes pod को service endpoint से हटा देता है ताकि वह traffic न ले
- shutdown की तैयारी में
isShuttingDownजैसेatomic.Boolका इस्तेमाल करके/healthzसे HTTP 503 return करवाया जा सकता है - readiness state को fail में बदलने के बाद changes propagate होने के लिए कुछ seconds इंतज़ार करना चाहिए
- example configuration
periodSeconds: 5है, और article example 5-second wait use करता है - exact wait time readiness probe configuration पर निर्भर करता है
- example configuration
चल रहे requests का handling
- shutdown budget के अनुसार
context.WithTimeoutसे timeout बनाकरserver.Shutdown(ctx)को pass करें server.Shutdownreturn होने की दो स्थितियाँ होती हैं- सभी active connections बंद हो गए और सभी handler processing खत्म हो गई
- passed context handler completion से पहले expire हो गया और server ने waiting छोड़ दी
- दोनों ही cases में
Shutdownतब return करता है जब server request processing पूरी तरह रोक चुका होता है - handlers को तेज और context-aware तरीके से काम करना चाहिए
- ऐसा न होने पर timeout expiry पर काम बीच में कट सकता है
- partial writes, data loss, inconsistent state, open transactions, corrupted data जैसी समस्याएँ हो सकती हैं
- handler तक shutdown signal पहुँचाने के दो common तरीके हैं
- middleware के जरिए हर request context में cancellation logic inject करना
http.ServerकेBaseContextसे सभी connections के लिए shared global context provide करना
- HTTP server में customize किए जा सकने वाले contexts
BaseContextऔरConnContextहैं- Graceful Shutdown के लिए server-wide cancelable global context बनाने वाला
BaseContextअधिक उपयुक्त है
- Graceful Shutdown के लिए server-wide cancelable global context बनाने वाला
- Graceful Shutdown तभी प्रभावी होता है जब functions context cancellation का सम्मान करें
context.Background(),time.Sleep()जैसे cancellation ignore करने वाले uses से बचना चाहिएtime.Sleep(duration)कोselectसेtime.After(duration)औरctx.Done()का साथ में इंतज़ार करने वाले तरीके से replace किया जा सकता है
- पुराने Go versions में
time.Aftertimer run होने तक memory leak कर सकता है- यह issue Go 1.23+ में fix हो गया है
- version निश्चित न हो तो
time.NewTimerऔरStop, और जरूरत पड़ने पर<-t.Ccheck का इस्तेमाल किया जा सकता है - संबंधित issue: time: stop requiring Timer/Ticker.Stop for prompt GC
Shutdown और Close का अंतर
- यही principle HTTP servers के अलावा third-party services पर भी लागू होता है
database/sqlकाDB.Closedatabase connections बंद करता है, नए queries शुरू होने से रोकता है, और चल रही queries के खत्म होने तक इंतज़ार करता है- मुख्य बात यह है कि नए requests या messages अब न लें, और existing work को defined grace period के भीतर खत्म होने का समय दें
server.Close()चल रहे connections का इंतज़ार किए बिना तुरंत terminate करता है- network use कर रहे handlers को read/write के समय error मिलता है
- client को तुरंत
ECONNRESETयाsocket hang upजैसे connection errors मिल सकते हैं - network से interact न करने वाले long-running handlers background में चलते रह सकते हैं
server.Shutdown()error return करने के बादserver.Close()इस्तेमाल किया जा सकता है, लेकिन यह shutdown strategy पर निर्भर करता है- shutdown signal को context के जरिए propagate करना अधिक reliable और graceful approach है
महत्वपूर्ण resources release करने का क्रम
- एक common mistake यह है कि shutdown signal मिलते ही critical resources release कर दिए जाते हैं
- इस समय handlers और in-flight requests अभी भी उन resources का उपयोग कर सकते हैं, इसलिए resource cleanup को shutdown timeout बीतने या सभी requests खत्म होने के बाद तक टालना चाहिए
- कई cases में केवल process termination से भी operating system resources reclaim कर लेता है
- Go द्वारा allocated memory process termination पर release हो जाती है
- file descriptors operating system बंद कर देता है
- process handles जैसे OS-level resources भी reclaim हो जाते हैं
- explicit cleanup की जरूरत वाले cases भी होते हैं
- database connections को properly close करना चाहिए, और open transactions को commit या rollback की जरूरत होती है
- message queues और brokers को message flush, offset commit, client shutdown notification की जरूरत हो सकती है
- external services connection drop तुरंत detect नहीं कर सकतीं, इसलिए manually connection close करने से TCP timeout का इंतज़ार करने की तुलना में cleanup जल्दी हो सकता है
- components को initialization के reverse order में shutdown करना अच्छा rule है
- Go का
deferlast registered function को पहले execute करता है, इसलिए यह pattern उससे अच्छी तरह match करता है
- Go का
- memory cache data को disk पर लिखना हो जैसे कुछ components के लिए अलग shutdown routine design करनी पड़ सकती है
पूरे example का flow
- complete example
signal.NotifyContextसेSIGINTऔरSIGTERMreceive करने वाला root context configure करता है /healthzendpoint,isShuttingDowntrue होने पर HTTP 503 औरShutting downreturn करता है, अन्यथाOKreturn करता है- sample request handler 2 seconds बाद
Hello, world!return करता है, या request context cancel होने पर HTTP request timeout से respond करता है BaseContextमेंongoingCtxconnect किया जाता है ताकि in-flight requestsSIGTERMके तुरंत बाद cancel न हों- shutdown signal मिलने पर flow इस क्रम में चलता है
- additional default handling allow करने के लिए
stop()call isShuttingDown.Store(true)से readiness failure state बनाना_readinessDrainDelayयानी 5 seconds तक readiness check propagation का इंतज़ार_shutdownPeriodयानी 15-second timeout के साथserver.Shutdowncall करनाstopOngoingGracefully()से in-progress context cancel करनाShutdownfail होने पर_shutdownHardPeriodयानी 3 seconds तक forced cancellation wait time रखना
- additional default handling allow करने के लिए
1 टिप्पणियां
Hacker News राय
Kubernetes में कुछ configurations में load balancer target IP अपडेट होने में उम्मीद से ज़्यादा समय लगने की वजह से मुझे नुकसान झेलना पड़ा था। मेरे मामले में graceful shutdown का 90% काम यह सुनिश्चित करना था कि pod समाप्त होने से पहले traffic सच में drain हो जाए
global
preStophook में 15-second sleep डालने से HTTP 503 rate काफी घट गया, और load balancer deregistration शुरू होने के बाद application तकSIGTERMपहुंचने तक समय मिल गया, जिससे application-side handling बहुत सरल हो गईpreStopsleep high-quality rolling deployments में SLO बनाए रखने का जादुई उपाय हैमुझे लगता है Kubernetes दो चीज़ें बेहतर कर सकता है। pod को termination sequence शुरू करने से पहले Endpoints से हटाया जाना चाहिए, और termination grace की तरह termination delay option होना चाहिए। साथ ही PDB में eviction से पहले recreation allow करने का option होना चाहिए
अगर सामान्य Prometheus
/metricsendpoint को हर N seconds में scrape किया जाता है, तो आखिरी scrape और actual process termination के बीच रिकॉर्ड हुए metrics propagate नहीं होते। इसलिए shutdown sequence के दौरान errors के बारे में गलत impression मिल सकता हैसावधान न रहें तो service बंद होने से ठीक पहले के कुछ seconds के logs भी खो सकते हैं। उदाहरण के लिए, अगर Promtail या Vector जैसा sidecar log file को watch कर रहा हो, और service start होते समय उसी path को truncate करके फिर से लिखे, तो shutdown के दौरान logs गायब होने की race condition बनती है
इतना effort लगाने के बाद भी ज़्यादातर data पूरी तरह ignore हो जाता है, और business insight भी अक्सर server में
sshकरके log file परgrepचलाने वाले गरीब संस्करण से बहुत बेहतर नहीं होती। इस ecosystem में लगाए गए effort से uptime, performance और usability वाकई meaningful तरीके से बेहतर हुए हैं या नहीं, मुझे पक्का नहीं पता“log synchronization”, “ingress के liveness handler के साथ catch up करने तक wait” जैसी चीज़ों को cover करने वाला हूं
https://github.com/utrack/caisson-go/blob/main/caiapp/caiapp...
https://github.com/utrack/caisson-go/tree/main/closer
docs अभी कम हैं और कुछ चीज़ें missing भी हैं, लेकिन vacation से लौटने पर पहला release करने की योजना है। आखिरकार यह सामान्य k8s/otel/grpc+http infrastructure संभालने वाली meta platform और reference platform library बनेगी
इसी behavior की वजह से हमारी service अभी भी statsd इस्तेमाल करती है, क्योंकि push-based model में यह समस्या नहीं होती
एक छोटी-सी आम trap अक्सर दिखती है: लोग सोचते हैं कि
log.Fatalcall करने पर भीdeferexecute होगा। असल में ऐसा नहीं होताlog.Fatal("fatal")internallyos.Exitcall करता है, इसलिए तुरंत exit हो जाता है औरdeferनहीं चलता। वहींpanic("fatal")मेंfatalऔरin deferदोनों दिखते हैंअगर distributed system के सही तरीके से काम करने के लिए यह assumption जरूरी है कि clients gracefully shutdown करेंगे, तो कभी न कभी वह बुरी तरह टूटेगा ही
system components के hard crash को झेल सकता है या नहीं, यह verify करने का इकलौता तरीका है कि hard crash को हमेशा होने वाली normal चीज़ बना दिया जाए। Chaos Monkey की जय
application का
sig intसे नीचे आना औरkillसे मारा जाना—इनमें बड़ा अंतर है। उदाहरण के लिए blue-green migration में graceful shutdown behavior चाहिए होता हैफिर सोचें तो शायद जरूरी हो। उस assumption के सच होने की guarantee करने का यही इकलौता तरीका हो सकता है। कुछ साल पहले Netflix के chaos monkey जैसा तरीका
मुझे लगा था इसमें यह तरीका बताया जाएगा कि नई service instance पुरानी instance से listening socket ले ले, ताकि incoming connections में से एक भी काटे बिना application restart हो सके
systemd में इसे implement करना relatively simple है, और nginx भी 20 साल से ज्यादा समय से इसे support करता आया है। अफसोस, Kubernetes और Docker इसे support नहीं करते क्योंकि वे assume करते हैं कि इसे load balancer या reverse proxy handle करेगा
मेरे colleague हमेशा कहते थे कि अगर program
ctrl cऔर कुछ shutdown commands को साफ-सुथरे तरीके से handle नहीं कर सकता, तो वह गलत तरीके से लिखा गया program हैमुझे लगता है Elixir ऐसे हिस्सों को वाकई समझदारी से संभालता है। मेरा अनुभव बहुत ज़्यादा नहीं है, लेकिन छोटे VM processes को इस तरह डिज़ाइन किया गया है कि वे panic करें, terminate हों और फिर से बन जाएँ, इसलिए जानबूझकर graceful shutdown routine बनाने की ज़रूरत कम हो जाती है
क्योंकि यह गुण पहले से ही application architecture में built-in है
अपने project में graceful shutdown संभालने के लिए मैंने एक छोटी library बनाई: https://github.com/eberkund/graceful
आम तौर पर कुछ services होती हैं जिन्हें start करना होता है, और हर एक का start और shutdown करने का तरीका अलग हो सकता है। कभी object को पहले instantiate करना पड़ता है, कभी कोई context होता है जिसे cancel करना चाहते हैं, और कभी कोई
Stopmethod होता है जिसे call करना पड़ता है। इसे इन सबको एक unified API के ज़रिए एक जगह लाने के लिए डिज़ाइन किया गया हैhttps://pkg.go.dev/git.sr.ht/~mariusor/wrapper#example-Regis...
जो pod shutdown हो रहा है, वह परिभाषा के अनुसार ready नहीं होता। service भी endpoint को terminating और not ready के रूप में mark करती है। यह Terminating state में जाने पर होता है, इसलिए खास तौर पर readiness check को fail कराने की ज़रूरत नहीं है
SIGTERMऔरPod.statusया endpoint slice जैसे objects के update का exact order मुझे नहीं पता।SIGTERMके बाद भी connections आने की एक छोटी window हो सकती है, लेकिन जैसा लेख संकेत देता है, वैसा “readiness check fail होने तक” चलने वाला बड़ा interval नहीं होता। cluster manage करने के नजरिए से वह बेहद छोटी window खास मायने नहीं रखती। नए connections स्वीकार न करें, मौजूदा connections को gracefully बंद करें, और reasonable तेजी से exit कर जाएँ। हालांकि जिन apps से मेरा पाला पड़ता है, उनमें से आधे या तोSIGTERMhandle करते हैं लेकिन shutdown में लंबा समय लेते हैं, या फिरSIGTERMhandle ही नहीं कर पाते और फिर भी shutdown में लंबा समय लेते हैंJustWatch के कुछ projects में हमने Google Wire अपनाया, और उसने game बदल दिया। यह हैरानी की बात है कि यह कम जाना जाता है, लेकिन Kubernetes में messy shutdown logic हटाने में मदद करता है
Wire clean dependency injection enforce करता है, इसलिए अब सब कुछ किसी unknown order में नहीं, बल्कि तय order में shutdown होता है
https://go.dev/blog/wire
https://github.com/google/wire