One self-hosted console to run your entire business — commerce, ERP, HRM, CRM & manufacturing

ट्रैफ़िक स्पाइक में autoscaling: असल में क्या scale होता है और क्या नहीं होना चाहिए

पहले से तय स्पाइक सेकंडों में चरम पर पहुँचता है, जबकि नया सर्वर तैयार होने में मिनट लगते हैं। जानिए अचानक दबाव में क्या scale होता है, reactive नियमों से pre-scaling क्यों बेहतर है, SSR और PHP-FPM परतें कैसे सजाएँ और load test कैसे करें।

Author

Anichur Rahaman

3 सप्ताह पहले11 min read2 views
ट्रैफ़िक स्पाइक में autoscaling: असल में क्या scale होता है और क्या नहीं होना चाहिए

20 request प्रति सेकंड, बिल्कुल सपाट। फ़्लैश सेल खुलने से पहले की रात 11:59 पर एक ऑनलाइन स्टोर का traffic बस इतना ही है, और प्लैटफ़ॉर्म लीड dashboard देख रहा है। autoscaling का नियम तैयार है: CPU पाँच मिनट तक 70% से ऊपर रहे तो एक server जुड़ेगा। (यह एक काल्पनिक दृश्य है, कोई असली दुकान नहीं।)

आधी रात को सेल खुलती है और क़रीब एक हज़ार ख़रीदार एक ही मिनट में टूट पड़ते हैं। लोड छलाँग लगाकर 440 request प्रति सेकंड पर पहुँच जाता है। 12:01 पर dashboard अब भी हरा है, क्योंकि पाँच मिनट का औसत मुश्किल से हिला है। 12:03 पर checkout timeout दे रहा है। नया server 12:06 पर जुड़ता है, तब तक ख़रीदार किसी प्रतिद्वंद्वी की दुकान पर जा चुके हैं।

autoscaling लोड के बाद जवाब देता है, और पहले से तय स्पाइक उस जवाब से पहले आ जाता है। यह लेख बताता है कि अचानक दबाव में असल में क्या scale होता है, किससे scale करने की उम्मीद नहीं रखनी चाहिए, और इसे event के बीच में नहीं, उससे पहले कैसे परखें।

नीचे के आँकड़े मेरे अपने test से हैं, एक ही Laravel और Next.js सेटअप पर। ये तरीक़ा दिखाने के लिए हैं, हर जगह लागू होने वाला बेंचमार्क नहीं। अपने system पर ख़ुद चलाकर देखें।

यह पाँच भागों की सीरीज़ "हाई-वॉल्यूम system की इंजीनियरिंग" का पहला भाग है। इसमें एज से लेकर एप्लिकेशन तक request के रास्ते की बात है। queue, data टियर, observability और सुरक्षित deploy भाग 2 से 5 में आएँगे।

स्पाइक का मतलब व्यस्त दिन नहीं है

दो तरह के traffic के लिए दो अलग जवाब चाहिए। धीरे-धीरे बढ़ता लोड किसी भी कंट्रोल लूप को भाँपने और जवाब देने का वक़्त देता है। अचानक की सीढ़ी-छलाँग यह वक़्त नहीं देती। अगर एक मिनट के भीतर लोड 20 से 440 request प्रति सेकंड पर पहुँच जाए, तो पाँच मिनट का औसत CPU देखने वाला नियम तब तक कुछ देख ही नहीं पाया होता।

अच्छी बात यह है कि पहले से तय स्पाइक का अंदाज़ा लगाया जा सकता है। शुरू होने का समय आपको पता होता है, कितने लोग आएँगे यह मोटे तौर पर पता होता है, और पिछले event का request मिक्स अक्सर दोबारा चलाकर देखा जा सकता है। यही पहले से मिली जानकारी आपका सबसे बड़ा औज़ार है, और इस लेख का ज़्यादातर हिस्सा इसे काम में लाने के बारे में है।

request का रास्ता: एज से app तक

क्या scale करना है, यह तय करने से पहले एक request का पूरा रास्ता खींच लें। मेरे चलाए सेटअप में यह कुछ ऐसा दिखता है: सबसे आगे CDN और WAF, फिर एक managed load balancer, दो बैकएंड वेब नोड, एक या ज़्यादा server-side rendering (SSR) फ़्रंटएंड नोड, बैकग्राउंड जॉब के लिए एक worker नोड, और सबके पीछे data टियर।

रेफ़रेंस आर्किटेक्चर: CDN और WAF, load balancer, बैकएंड वेब नोड और SSR फ़्रंटएंड नोड, worker नोड, फिर database, Redis और ऑब्जेक्ट स्टोरेज
एज से data तक request का रास्ता। हर परत अलग तरीक़े से और अलग रफ़्तार से scale होती है।

हर डिब्बे की अपनी ख़राबी होती है और अपना scale करने का तरीक़ा भी। सबको एक "server" मान लेने का नतीजा यह होता है कि जिस परत में कभी दिक़्क़त थी ही नहीं, उसी में RAM बढ़ा दी जाती है।

एज और load balancer

सबसे पहले आगे एक CDN लगाएँ। स्टैटिक फ़ाइलें, इमेज और हर गेस्ट के लिए एक जैसे दिखने वाले पेज स्पाइक के दौरान आपके server तक पहुँचने ही नहीं चाहिए। उसी परत पर WAF और बॉट-नियम स्क्रैपर्स को भी रोकते हैं, ताकि ख़रीदारों के लिए रखी गई क्षमता वे न खा जाएँ।

CDN के पीछे health check और connection draining वाला managed load balancer रखें। Health check बीमार नोड को अपने-आप बाहर कर देता है। Draining नोड को जाने से पहले चल रही request पूरी करने देता है, और इसी वजह से deploy और scale-in user को दिखते ही नहीं।

दो बारीक बातें जाँचने लायक़ हैं। पहली, बैलेंसिंग एल्गोरिदम। कई cloud load balancer डिफ़ॉल्ट में round robin चलाते हैं, जो मानकर चलता है कि हर request का ख़र्च बराबर है। checkout और सर्च बराबर नहीं होते। "Least outstanding requests" या "least connections" मोड अगली request उस नोड को भेजता है जिसके पास सबसे कम काम चल रहा हो। मिसाल के तौर पर AWS में Application Load Balancer का डिफ़ॉल्ट round robin है, और least outstanding requests एक target group की सेटिंग है। दूसरी, load balancer ख़ुद भी एक ऐसी सर्विस है जिसे scale होना पड़ता है। AWS के दस्तावेज़ों के मुताबिक़ एक Application Load Balancer क़रीब पाँच मिनट में अपनी क्षमता दोगुनी कर सकता है, और जिन event में पाँच मिनट से कम में traffic दोगुने से ज़्यादा होने वाला हो, उनके लिए पहले से क्षमता रिज़र्व करने की सुविधा है (capacity reservation गाइड देखें)। दूसरे प्रोवाइडर्स की भी ऐसी सीमाएँ होती हैं, इसलिए अपने वाले से पूछ लें।

मेरे load test में load balancer कभी रुकावट नहीं बना: सबसे ऊँची रेट पर भी CPU 8% या उससे कम रहा और एरर शून्य थे। यही आम तस्वीर है। पहले रास्ते में और आगे देखें।

Stateless वेब नोड: यही एंट्री टिकट है

दो वेब नोड load balancer के पीछे तभी रखे जा सकते हैं जब कोई भी नोड कोई भी request सँभाल सके। इस गुण को stateless कहते हैं। इसे शुरू से ठीक रखना सस्ता है, बाद में जोड़-तोड़ से ठीक करना तकलीफ़देह। दूसरा नोड जोड़ने से पहले यह चेकलिस्ट मिला लें।

  1. सेशन Redis या Valkey में, नोड की डिस्क की फ़ाइलों में नहीं।
  2. एप्लिकेशन cache Redis या Valkey में, ताकि सारे नोड एक ही cache इस्तेमाल करें।
  3. अपलोड और बनी हुई फ़ाइलें ऑब्जेक्ट स्टोरेज में (S3-संगत), लोकल डिस्क पर कभी नहीं।
  4. सिर्फ़ एक scheduler। Cron जैसे जॉब ठीक एक नोड पर चलें, वरना हर टास्क दो बार चलेगा।
  5. एक जैसे बिल्ड। हर नोड वही इमेज चलाए, कॉन्फ़िगरेशन एनवायरनमेंट से आए।
  6. हर परत पर अपलोड लिमिट एक जैसी रखें। हर प्रॉक्सी की अपनी बॉडी लिमिट होती है (nginx की client_max_body_size, PHP की post_max_size और upload_max_filesize)। एक सेटअप में होस्ट प्रॉक्सी की 1 MB की डिफ़ॉल्ट लिमिट चुपचाप अपलोड लौटाती रही, जब तक सारी परतें बराबर नहीं की गईं।

StoreConsole जैसा self-hosted प्लैटफ़ॉर्म इसी वजह से सेशन और cache Redis में रखता है, ताकि नोड एक-दूसरे की जगह ले सकें।

SSR परत: एक Node प्रोसेस यानी एक कोर

यहीं मुझे सबसे ज़्यादा बार हैरानी हुई है। Next.js server पेज एक Node.js प्रोसेस में रेंडर करता है, और Node आपका JavaScript एक ही थ्रेड पर चलाता है। मशीन कितनी भी बड़ी हो, एक प्रोसेस रेंडरिंग में लगभग एक ही कोर इस्तेमाल कर पाता है।

मैंने पिछले एक पीक का असली request मिक्स k6 से सिर्फ़ एक फ़्रंटएंड प्रोसेस पर चलाया। वह लगभग 220 से 250 request प्रति सेकंड पर जवाब दे गया। उसके बाद p95 latency क़रीब 260 ms से उछलकर क़रीब 3 सेकंड हो गई और p99 6.4 सेकंड तक पहुँची। इस पूरे दौरान होस्ट पर दो से ज़्यादा कोर ख़ाली पड़े थे। असली event के पीक मिनट में क़रीब 440 request प्रति सेकंड चाहिए थे, यानी एक प्रोसेस ठीक उसी पल फ़ेल होता जब उसकी सबसे ज़्यादा ज़रूरत थी।

हल सीधा था: एक ही इमेज से कई एक जैसे फ़्रंटएंड container चलाना (मेरे मामले में तीन), आगे nginx में least_conn, जो हर request उस server को देता है जिस पर सबसे कम सक्रिय connection हों। फिर टार्गेट रेट पर दोबारा test। Next.js के लिए एक और बात: डिफ़ॉल्ट में हर इंस्टेंस अपना लोकल cache रखता है, इसलिए कई इंस्टेंस चलाएँ तो फ़्रेमवर्क के दस्तावेज़ में बताए तरीक़े से एक साझा cache स्टोर तय कर दें।

हिसाब

यहाँ क्षमता की योजना सीधा गुणा है। प्रति प्रोसेस नापी गई सीमा को प्रोसेस की संख्या से गुणा करें, फिर अपेक्षित पीक से हेडरूम समेत मिलाकर देखें।

मदमान (एक सेटअप)टिप्पणी
पीक मिनट में ज़रूरत~440 req/sपिछले असली event को दोबारा चलाकर मिला
एक SSR प्रोसेस, नापा हुआ220-250 req/sइसके बाद p95 ~260 ms से ~3 s हो गया
तीन SSR प्रोसेस~660-750 req/sपीक का लगभग डेढ़ गुना, हेडरूम के तौर पर
Load balancer CPU8% या कमरुकावट नहीं था

PHP-FPM: पूल का साइज़ सोच-समझकर तय करें

बैकएंड वेब परत की समस्या उल्टी है। PHP-FPM worker प्रोसेस का एक पूल चलाता है और हर worker एक बार में एक ही request सँभालता है। पूल छोटा हुआ तो request क़तार में खड़ी रहती हैं। बहुत बड़ा हुआ तो नोड की मेमोरी ख़त्म हो जाती है और स्वैपिंग या प्रोसेस किल शुरू हो जाते हैं।

काम चलाऊ अंगूठे का नियम: ज़रूरी busy worker ≈ request प्रति सेकंड × प्रति request का औसत समय। एक काल्पनिक मिसाल: 200 request प्रति सेकंड, हर एक 150 ms की, तो क़रीब 30 busy worker चाहिए, इसलिए 60 का पूल धीमी request के लिए गुंजाइश छोड़ता है। फिर मेमोरी जाँचें: एक worker क़रीब 100 MB लेता हो तो 60 worker को क़रीब 6 GB चाहिए, यानी नोड में इससे ज़्यादा होनी चाहिए।

एक सेटअप में मैंने ये मान रखे थे: pm.max_children=60, start_servers=30, min_spare_servers=20, max_spare_servers=30 और max_requests=1000, साथ में nginx से FPM तक keepalive (keepalive 16 और fastcgi_keep_conn on)। 30 से शुरू करना अहम है: जब लहर आती है तो पूल पहले से गरम होता है, दबाव में fork नहीं करना पड़ता।

दो आदतें और फ़ायदा देती हैं। प्रोडक्शन में validate_timestamps=0 के साथ OPcache चालू रखें, और deploy पर worker रीस्टार्ट करें। साथ ही fastcgi_next_upstream error timeout http_500 http_503 और fastcgi_next_upstream_tries 2 पर विचार करें; इससे FPM की क्षणिक गड़बड़ियाँ चुपचाप retry में निपट गईं। यह सिर्फ़ idempotent request के लिए सुरक्षित है। payment की POST request को retry करना हल नहीं, बग है।

Reactive autoscaling देर से क्यों पहुँचता है

अब असली बात। Reactive autoscaling एक metric देखता है, थ्रेशोल्ड का इंतज़ार करता है, मशीन चालू करता है, फिर उसके बूट होने, load balancer से जुड़ने और cache गरम होने का इंतज़ार करता है। व्यवहार में इसमें सबसे अच्छी हालत में भी एक से तीन मिनट लगते हैं। डिफ़ॉल्ट सेटिंग देरी और बढ़ा सकती हैं: जैसे AWS EC2 Auto Scaling में डिफ़ॉल्ट cooldown 300 सेकंड है, और basic monitoring इंस्टेंस की metric पाँच मिनट में एक बार ही देती है, जब तक आप पैसे देकर एक मिनट वाली detailed monitoring न लें।

पहले से तय स्पाइक सेकंडों में चरम पर पहुँचता है। इसलिए नई मशीन तब पहुँचती है जब लोग जा चुके होते हैं, या उससे भी बुरा, जब वे एरर देख चुके होते हैं। वर्चुअल मशीन की RAM भी रीबूट के बिना नहीं बढ़ाई जा सकती, इसलिए "जहाँ है वहीं बड़ा कर दो" भी काम नहीं करता।

टाइमलाइन: reactive autoscaling स्पाइक के चरम के कई मिनट बाद क्षमता जोड़ता है, जबकि pre-scaling event से पहले ही क्षमता तैयार रखता है
Reactive scaling लोड के पीछे-पीछे चलता है। Pre-scaling लोड आने से पहले तैयार खड़ा रहता है। उदाहरण के तौर पर टाइमलाइन।

जिन event का पता है, उनके लिए pre-scale करें

पहले से तय event के लिए शुरू होने से पहले नोड की न्यूनतम संख्या बढ़ा दें और बाद में घटा दें। यह सुनने में जितना महँगा लगता है, उतना है नहीं: कुछ घंटों के लिए एक अतिरिक्त नोड का ख़र्च एक नाकाम सेल के नुक़सान के सामने मामूली है। यह किसी भी नियम से ज़्यादा भरोसेमंद भी है, क्योंकि यह इस पर निर्भर नहीं करता कि कोई metric वक़्त पर सीमा पार करेगी या नहीं।

Reactive नियम अनहोनी के लिए सुरक्षा-जाल के तौर पर रखें, मुख्य योजना के तौर पर नहीं। और वही scale करें जो जल्दी scale हो सके। container के भीतर के प्रोसेस सेकंडों में शुरू हो जाते हैं, मशीनें मिनटों में, इसीलिए मैं queue worker को मशीन से नहीं, प्रोसेस से scale करता हूँ (इस सीरीज़ के भाग 2 में)।

तरीक़ाप्रतिक्रिया का समयकिसके लिए बेहतर
Pre-scale (न्यूनतम संख्या बढ़ाना)event से पहले तैयारपहले से तय स्पाइक
Reactive VM autoscaling1-3 मिनट या ज़्यादाधीरे बढ़ता लोड, अनियोजित traffic
प्रोसेस-स्तर का scalingकुछ सेकंडcontainer के भीतर queue worker
फ़्लोचार्ट: क्या स्पाइक पहले से पता है, क्या लोड धीरे बढ़ता है, क्या काम queue के जॉब हैं, और हर जवाब किस scaling तरीक़े की ओर ले जाता है
कौन-सा तरीक़ा कब: तीन सवाल तय करते हैं कि pre-scaling, reactive scaling, प्रोसेस-स्तर का scaling या एज की सुरक्षा चुननी है।

असली event की तरह load test करें

ऊपर का कोई भी आँकड़ा अंदाज़े से नहीं आया। ये उन test से आए हैं जो event जैसे दिखते थे। सिर्फ़ होमपेज पर हथौड़ा चलाने से लगभग कुछ साबित नहीं होता। यह क्रम अपनाएँ।

  1. असली request मिक्स उठाएँ पिछले event के access log से: कौन-से URL, किस अनुपात में, user कितना रुक-रुककर करता है।
  2. k6 (या ऐसे ही टूल) से उसे दोबारा चलाएँ अपेक्षित रेट पर, फिर उसके डेढ़ गुना पर।
  3. स्क्रिप्ट में abort threshold रखें, जैसे p95 3 सेकंड से ऊपर, या कोई भी 5xx या timeout, ताकि नाकाम test अपने-आप रुके और साझा system को नुक़सान न पहुँचाए।
  4. एक guard script जोड़ें जो नोड्स का CPU देखती रहे और सीमा छूते ही रन रोक दे।
  5. अपनी metric को cloud प्रोवाइडर की metric से मिलाएँ। मेरे test में दोनों कुछ प्रतिशत के भीतर मिलीं, यानी dashboard पर भरोसा किया जा सकता था।
  6. एक बार में एक ही चीज़ बदलें, और हर फ़िक्स के बाद टार्गेट रेट पर दोबारा चलाएँ।

जब तक test न बता दे कि सीमा कहाँ है, हार्डवेयर अपग्रेड न करें। मेरे मामले में मशीनें ठीक थीं। सीमा सिंगल-थ्रेड वाला प्रोसेस था, और हल में कॉन्फ़िगरेशन के सिवा कुछ ख़र्च नहीं हुआ।

वही आधी रात, इस बार तैयारी के साथ

पहला दृश्य फिर से चलाइए, इस बार तैयारी पूरी करके। रात 11 बजे वेब नोड की न्यूनतम संख्या दो से तीन हो जाती है, और SSR परत में तीन प्रोसेस least_conn के पीछे चल रहे हैं। cache गरम है और FPM पूल में पहले से 30 worker हैं।

आधी रात को 440 request प्रति सेकंड आती हैं। नापी गई सीमा क़रीब 660 है, इसलिए p95 अपने सामान्य स्तर के आसपास रहता है और autoscaling का नियम एक बार भी नहीं चलता, क्योंकि उसकी ज़रूरत ही नहीं पड़ी। 12:30 पर न्यूनतम संख्या वापस घट जाती है। पूरे event की क़ीमत थी कुछ घंटे का एक अतिरिक्त नोड।

इस रास्ते के पीछे की परतें अलग तरह से टूटती हैं। अचानक दबाव में आम तौर पर सबसे पहले बैकग्राउंड जॉब टूटते हैं, और भाग 2 में बड़े पैमाने पर queue और worker की बात है। भाग 3 में दबाव में data टियर, भाग 4 में observability, और भाग 5 में सुरक्षा और zero-downtime deploy। कारोबार बढ़ने के साथ कितना बड़ा server चाहिए, यह तय करने के लिए server बढ़ाने के रोडमैप पर मेरा पुराना लेख देखें।

मुख्य बातें

  • पहले से तय स्पाइक सीढ़ी की छलाँग है, ढलान नहीं। Reactive autoscaling को मिनट लगते हैं, इसलिए वह स्पाइक गुज़र जाने के बाद पहुँचता है।
  • जाने-पहचाने event के लिए pre-scale करें: शुरू से पहले नोड की न्यूनतम संख्या बढ़ाएँ, बाद में घटाएँ।
  • वेब नोड stateless रखें: सेशन और cache Redis में, फ़ाइलें ऑब्जेक्ट स्टोरेज में, scheduler एक ही।
  • एक Node.js SSR प्रोसेस लगभग एक कोर इस्तेमाल करता है। कई एक जैसे प्रोसेस least_conn के पीछे चलाएँ और टार्गेट रेट पर test करें।
  • PHP-FPM पूल का साइज़ request प्रति सेकंड, request के समय और प्रति worker मेमोरी से तय करें, और पूल को पहले से गरम रखें।
  • असली request मिक्स, abort threshold और guard script के साथ load test करें; हार्डवेयर ख़रीदने से पहले नापी गई रुकावट ठीक करें।

अनिचुर रहमान सॉफ़्टवेयर आर्किटेक्ट और StoreConsole के संस्थापक हैं। वे बढ़ते कारोबारों के लिए कॉमर्स और ERP सिस्टम डिज़ाइन करते हैं — ख़ास ध्यान event-driven आर्किटेक्चर, डेटा की शुद्धता और अपने सर्वर पर चलने वाले सिस्टम पर।

About the Author

Anichur Rahaman

Continue Reading