मुझे समझ आता है कि यह सवाल बार-बार क्यों उठता है। एक WebSocket खुला रहता है, याद रखता है कि आप कौन हैं, और सर्वर को बार-बार पूछे बिना ही आपको डेटा भेजने देता है। तो फिर हम एक ही पेज लोड के लिए सौ अलग-अलग HTTP रिक्वेस्ट क्यों भेजते रहते हैं, जब हम सिर्फ एक परसिस्टेंट पाइप खोलकर काम चला सकते हैं? सच कहूं तो यह सवाल जितना समझदार लगता है, उतनी क्रेडिट लोग इसे नहीं देते — और इसका जवाब “क्योंकि HTTP बेहतर है” नहीं है। मामला इससे कहीं ज़्यादा बारीक है।
रुकिए, क्या एक स्टेटफुल कनेक्शन साफ़ तौर पर ज़्यादा कुशल नहीं है?
कागज़ पर, हाँ। एक बार जब शुरुआती HTTP “अपग्रेड” हैंडशेक के ज़रिए WebSocket कनेक्शन स्थापित हो जाता है, तो क्लाइंट और सर्वर दोनों किसी भी समय एक-दूसरे को डेटा भेज सकते हैं, और हर मैसेज पर फ़्रेमिंग का ओवरहेड बहुत कम होता है [1]। न दोहराए जाने वाले हेडर्स, न हर रिक्वेस्ट के साथ चलने वाले कुकीज़, न हर एक्सचेंज पर यह दोबारा साबित करना कि आप कौन हैं। हैंडशेक के बाद, WebSocket डेटा फ़्रेम्स में HTTP के मुक़ाबले बहुत कम प्रोटोकॉल ओवरहेड होता है, जहाँ हर रिक्वेस्ट और रिस्पॉन्स के साथ कुकीज़, यूज़र-एजेंट स्ट्रिंग्स, और कैश-कंट्रोल डायरेक्टिव्स जैसे हेडर्स घसीटे जाते हैं [2]।
तो यह सहज समझ बनती है: अगर कनेक्शन को पहले से पता है कि मैं कौन हूँ और वह खुला रहता है, तो मुझे हर पेज पर सौ बार अपना परिचय क्यों देना पड़े?
लेकिन यहीं पेच है — जो चीज़ WebSocket को रियल-टाइम कम्युनिकेशन में बेहतरीन बनाती है (यानी स्टेटफुलनेस), वही चीज़ इसे बड़े पैमाने पर चलाना महंगा भी बनाती है। यह कोई मुफ़्त की सुविधा नहीं है। यह एक सौदा है जो आप कर रहे हैं, और एक सामान्य वेब पेज के लिए ज़्यादातर समय यह सौदा फ़ायदेमंद नहीं होता।
“सर्वर को आप याद हैं” — इसकी छुपी हुई कीमत
जब आप कोई सामान्य HTTP रिक्वेस्ट भेजते हैं, तो REST APIs स्टेटलेस होते हैं — सर्वर रिक्वेस्ट्स के बीच क्लाइंट की डिटेल्स याद नहीं रखता, जिससे ज़्यादा सर्वर जोड़ना और लोड बाँटना बेहद आसान हो जाता है [3]। आपके बेड़े का कोई भी सर्वर किसी भी रिक्वेस्ट का जवाब दे सकता है, क्योंकि कॉल्स के बीच कोई आपके बारे में कुछ “याद” नहीं रख रहा होता।
WebSocket इस पूरी तस्वीर को पलट देता है। सर्वर को हर एक कनेक्शन का हिसाब रखना पड़ता है — कौन जुड़ा है, उसकी स्थिति क्या है, उसने किन चीज़ों को सब्सक्राइब किया है — जब तक वह सॉकेट जीवित है। और यह मुफ़्त नहीं है:
- हर खुला हुआ WebSocket कनेक्शन सिर्फ़ निष्क्रिय बैठे रहने के लिए लगभग 2–10 KB मेमोरी खा जाता है, और जब बात लाखों यूज़र्स की हो तो यह जल्दी ही बहुत बड़ा आँकड़ा बन जाता है [4]।
- हर कनेक्शन सर्वर पर एक फ़ाइल डिस्क्रिप्टर भी घेरता है, और ऑपरेटिंग सिस्टम्स में इस बात की सख़्त सीमा होती है कि एक साथ कितने खुले रह सकते हैं [4]।
- सर्वर कनेक्शन्स को ज़िंदा रखने में ही CPU जलाता रहता है — हार्टबीट्स भेजना, रीकनेक्ट्स संभालना, जो कुछ भी इधर-उधर से रिसता है उसे प्रोसेस करना [5]।
अब इसे “हर पेज लोड पर एक सॉकेट” से गुणा कीजिए। अगर आपकी साइट पर कुछ हज़ार लोग एक साथ आते हैं, तो अचानक आपके पास कुछ हज़ार लंबे समय तक टिके रहने वाले, मेमोरी खाने वाले, स्टेटफुल कनेक्शन्स आ जाते हैं, जिनकी देखभाल आपके सर्वर्स को करनी पड़ती है — उन पेजेज़ के लिए, जिन्हें असल में बस कुछ JSON लाकर एक बार रेंडर करना था।
लोड बैलेंसर का वह दर्द जिसका कोई ज़िक्र नहीं करता
यहीं पर बैकएंड टीमों के लिए असली परेशानी शुरू होती है। चूँकि WebSocket स्टेटफुल होता है, इसलिए जो सर्वर वह कनेक्शन पकड़े हुए है, सिर्फ़ वही जानता है कि उस क्लाइंट के साथ क्या चल रहा है। इसका मतलब है कि लोड-बैलेंस्ड माहौल में WebSocket कनेक्शन्स को सेशन एफ़िनिटी (यानी “स्टिकी सेशन्स”) की ज़रूरत होती है [4]। एक बार जब क्लाइंट सर्वर B से जुड़ जाता है, तो उस क्लाइंट के लिए हर भविष्य की बातचीत को सर्वर B पर ही जाना पड़ता है — आपका लोड बैलेंसर बस कंधे उचका कर उसे जहाँ खाली जगह हो वहाँ नहीं भेज सकता।
यह तब तक मैनेज करने लायक लगता है जब तक आप इसे असल में प्रोडक्शन में नहीं चलाते:
- जब सर्वर B क्रैश होता है, तो उससे जुड़ा हर एक क्लाइंट एक झटके में अपनी सेशन स्थिति खो देता है [6]।
- अपने पूरे बेड़े में लोड को फिर से बाँटना काफ़ी मुश्किल हो जाता है, क्योंकि कनेक्शन्स को आज़ादी से इधर-उधर नहीं किया जा सकता [6]।
- रोलिंग डिप्लॉयमेंट्स एक उथल-पुथल भरी घटना बन जाती हैं, क्योंकि अपडेट के लिए किसी सर्वर को ख़ाली करने का मतलब है उससे जुड़े हर स्टिकी क्लाइंट को ज़बरदस्ती डिस्कनेक्ट करना [6]।
इसका “इलाज” यह है कि सेशन स्टेट को Redis जैसी किसी चीज़ में बाहर निकाल दिया जाए ताकि कोई भी सर्वर किसी भी क्लाइंट को संभाल सके [6] — जो बिल्कुल किया जा सकता है, लेकिन ज़रा गौर कीजिए कि यहाँ क्या हो गया: आप “किसी शेयर्ड स्टेट की ज़रूरत नहीं” (HTTP) से “अब मुझे एक डिस्ट्रिब्यूटेड स्टेट स्टोर चाहिए ताकि मेरा स्टेटफुल प्रोटोकॉल स्टेटलेस जैसा बर्ताव कर सके” तक पहुँच गए। एक ऐसा पेज बनाने के लिए यह बहुत सारे अतिरिक्त चलते-पुर्ज़े हैं, जो HTTP के साथ बिना किसी झंझट के काम कर जाता।
“लेकिन HTTP तो कितने सारे कनेक्शन्स बर्बाद करता है!” — अब असल में ऐसा नहीं है
मुझे लगता है कि यही वह बिंदु है जहाँ लोग सबसे ज़्यादा भटक जाते हैं, क्योंकि यह सोच सन् 2009 की HTTP की समझ में अटकी हुई है। हाँ, ब्राउज़र्स ऐतिहासिक रूप से HTTP/1.1 के तहत आपको प्रति डोमेन 6 समानांतर कनेक्शन्स तक सीमित रखते थे [9], और हाँ, इसका मतलब यह था कि एक “बातूनी” पेज ऐसा महसूस करा सकता था मानो रिक्वेस्ट्स एक-दूसरे के पीछे कतार में लगी हों।
लेकिन दो चीज़ों ने इस तस्वीर को काफ़ी हद तक बदल दिया:
- HTTP/1.1 कीप-अलाइव। “Connection: keep-alive” मेकेनिज़्म ब्राउज़र को हर बार नया कनेक्शन खोलने के बजाय एक ही TCP कनेक्शन को कई रिक्वेस्ट्स के लिए दोबारा इस्तेमाल करने देता है [8], जो लोगों के मन में बैठे HTTP के “ओवरहेड” का एक बड़ा हिस्सा यूँ ही ख़त्म कर देता है।
- HTTP/2 मल्टीप्लेक्सिंग। HTTP/2 के साथ, ब्राउज़र प्रति डोमेन सिर्फ़ एक TCP कनेक्शन खोलता है और उसी पर एक साथ कई रिक्वेस्ट/रिस्पॉन्स “स्ट्रीम्स” चलाता है [9]। यह पुरानी प्रति-डोमेन रुकावट को लगभग पूरी तरह मिटा देता है — आपको WebSocket की वह पसंदीदा कुशलता (एक कनेक्शन, कई एक्सचेंज) मिल जाती है, बिना स्टेटलेसनेस को छोड़े।
यहाँ एक तुलना है जो असली ट्रेड-ऑफ़्स को काफ़ी साफ़ कर देती है:
| पहलू | HTTP/1.1 (कीप-अलाइव) | HTTP/2 | WebSocket |
|---|---|---|---|
| प्रति पेज कनेक्शन्स | प्रति डोमेन 6 तक [9] | प्रति डोमेन 1 (मल्टीप्लेक्स्ड) [9] | 1 (परसिस्टेंट) |
| सर्वर को आपको “याद” रखना पड़ता है | नहीं | नहीं | हाँ — कनेक्शन की पूरी उम्र भर [3] |
| CDN/प्रॉक्सी द्वारा कैश हो सकता है | हाँ [12] | हाँ [12] | नहीं — कैश करने के लिए कुछ है ही नहीं, यह एक लाइव स्ट्रीम है |
| बिना पूछे सर्वर डेटा भेज सकता है | नहीं (पहले रिक्वेस्ट आना ज़रूरी) | नहीं | हाँ, कभी भी [2] |
| सख़्त कॉर्पोरेट फ़ायरवॉल्स के पीछे काम करता है | हाँ (पोर्ट 443 हमेशा खुला रहता है) | हाँ | अक्सर ब्लॉक हो जाता है या फ़ॉलबैक चाहिए [15] |
| स्केलिंग मॉडल | स्टेटलेस — कोई भी सर्वर, कहीं भी | स्टेटलेस — कोई भी सर्वर, कहीं भी | स्टिकी सेशन्स या शेयर्ड स्टेट चाहिए [4][6] |
इस टेबल को देखकर सवाल का जवाब लगभग ख़ुद ही मिल जाता है: HTTP/2 ने “बहुत ज़्यादा कनेक्शन्स” की समस्या को बिना स्टेटलेसनेस छोड़े पहले ही सुलझा दिया है। आपको कैशिंग, आसान हॉरिज़ॉन्टल स्केलिंग, और फ़ायरवॉल-फ़्रेंडलीनेस — सब बना रहता है — और WebSocket का सहारा सिर्फ़ तभी लेना पड़ता है जब आपको उसकी एक सच में अनोखी ख़ासियत चाहिए हो: सर्वर का अपनी मर्ज़ी से डेटा भेजना, न कि तब जब आप पूछें।
आप कैशिंग खो देंगे — और यह जितना लगता है उससे कहीं बड़ी बात है
यह वो बिंदु है जिसे लोग सबसे ज़्यादा कम आँकते हैं। हर जगह WebSocket इस्तेमाल करने का मतलब है कि आप कुछ भी कैश नहीं कर सकते, और यह चुपके से आपकी सर्वर लागत को काफ़ी ऊपर पहुँचा देता है [3]। ज़रा सोचिए कि एक सामान्य पेज लोड में असल में क्या-क्या शामिल होता है — आपकी CSS, इमेजेज़, उन चीज़ों के API रिस्पॉन्स जो शायद ही कभी बदलते हों, आपका यूज़र अवतार, प्रोडक्ट लिस्टिंग्स। इसका एक बड़ा हिस्सा हज़ारों यूज़र्स के लिए बिल्कुल एक जैसा होता है और मिनट-दर-मिनट मुश्किल से ही बदलता है।
सादे HTTP के साथ, CDN और रिवर्स प्रॉक्सी आपके सर्वर्स के आगे बैठकर यह सब कुछ कैश कर लेते हैं, और दोहराई गई रिक्वेस्ट्स को सीधे एज से परोस देते हैं, बिना आपके ओरिजिन सर्वर को पसीना बहाए [12]। यह स्टेटलेस रिक्वेस्ट/रिस्पॉन्स मॉडल में बुनी हुई एक मूलभूत बढ़त है — क्योंकि कोई कुछ याद नहीं रख रहा, इसलिए कहीं भी मौजूद कोई भी कैश जवाब दे सकता है।
WebSocket एक लाइव, दो-तरफ़ा स्ट्रीम है। यहाँ कैश करने के लिए कोई “रिस्पॉन्स” नहीं है — बस एक चलती हुई बातचीत है जो उस एक कनेक्शन के लिए अनोखी है। जिस पल आप सब कुछ सॉकेट्स के ज़रिए धकेलना शुरू करते हैं, आप वेब के सबसे सस्ते और सबसे आज़माए हुए परफ़ॉर्मेंस टूल — मामूली से दिखने वाले HTTP कैश — को फेंक चुके होते हैं।
और फिर वह कोई-न-कोई कॉर्पोरेट फ़ायरवॉल है जो बस मना कर देता है
क्या कभी आपने कुछ ऐसा बनाया है जो घर के वाई-फ़ाई पर एकदम सही चला, और फिर जैसे ही किसी ने उसे ऑफ़िस के नेटवर्क से चलाया, पूरी तरह बिगड़ गया? WebSocket को यह समस्या लगातार झेलनी पड़ती है। ज़्यादातर वेब प्रॉक्सी और सख़्त कॉर्पोरेट फ़ायरवॉल WebSocket कनेक्शन्स को सीधे ब्लॉक कर देते हैं, अक्सर इसलिए क्योंकि वे एक ट्रांसपेरेंट प्रॉक्सी के ज़रिए सिर्फ़ पोर्ट 80 और 443 पर सादे HTTP ट्रैफ़िक को ही जाने देने के लिए कॉन्फ़िगर किए गए होते हैं [15][16]।
पोर्ट सही होने पर भी, प्रोडक्शन में WebSocket फ़ेल होने का सबसे आम कारण यह है कि रिवर्स प्रॉक्सीज़ को उस HTTP “अपग्रेड” हैंडशेक को आगे भेजने के लिए साफ़ तौर पर कॉन्फ़िगर करना पड़ता है जो WebSocket कनेक्शन शुरू करता है [15]। अगर वह कॉन्फ़िगरेशन गायब है — और अक्सर ऐसा होता ही है, क्योंकि हर ऑप्स टीम इसे जोड़ने की नहीं सोचती — तो आपका सॉकेट कनेक्ट ही नहीं होगा, और अब आपको फ़ॉलबैक लॉजिक (लॉन्ग-पोलिंग, रीट्राई लूप्स, वग़ैरह) लिखनी पड़ेगी, बस इसलिए कि आपकी ऐप अच्छे से डिग्रेड हो सके।
सादे HTTP के साथ यह समस्या नहीं होती। यह वही चीज़ है जिसे धरती पर हर प्रॉक्सी, फ़ायरवॉल और कॉर्पोरेट नेटवर्क समझने और जाने देने के लिए बना है। यह कोई छोटी बढ़त नहीं है — यह “होटल के वाई-फ़ाई पर बैठा अकाउंटेंट भी आपकी ऐप सच में चला पा रहा है” वाली बढ़त है।
तो बड़ी रियल-टाइम ऐप्स असल में यह कैसे करती हैं?
यह वह हिस्सा है जो मुझे सच में सीखने लायक लगता है, क्योंकि Slack और Discord जैसी कंपनियाँ परसिस्टेंट कनेक्शन्स पर भारी भरोसा करती हैं — लेकिन ध्यान दीजिए, वे सॉकेट्स से HTTP की जगह नहीं लेतीं। वे दोनों को साथ-साथ चलाती हैं, हर एक वही करता है जिसमें वह अच्छा है।
- Discord का Gateway एक परसिस्टेंट WebSocket कनेक्शन है जो रियल-टाइम इवेंट्स पुश करता है — किसी चैनल का नाम बदला गया, कोई रोल बनाया गया, कोई ऑनलाइन आया। लेकिन Discord साफ़ तौर पर कहता है कि ज़्यादातर मामलों में उसके रिसोर्सेज़ पर सामान्य ऑपरेशन्स को Gateway के बजाय सामान्य HTTP API से होकर जाना चाहिए, क्योंकि गेटवे कनेक्शन्स को खोलना, बनाए रखना और डिस्कनेक्ट से उबरना कहीं ज़्यादा जटिल है [13]।
- Slack का Socket Mode भी मिलता-जुलता है — ऐप्स लाइव इवेंट्स पाने के लिए WebSocket इस्तेमाल करते हैं, लेकिन Slack साफ़ तौर पर सलाह देता है कि जवाब वापस भेजने के लिए स्टैंडर्ड Web API (सादा HTTPS) ही इस्तेमाल किया जाए [14]। एक चैनल “मुझे बताओ अभी क्या हुआ” के लिए, और दूसरा “मैं इसके बारे में क्या करना चाहता हूँ” के लिए।
पैटर्न दिखा? परसिस्टेंट कनेक्शन सिर्फ़ उस एक काम के लिए सुरक्षित रखा जाता है जो HTTP वाक़ई अच्छे से नहीं कर सकता: सर्वर का अपनी मर्ज़ी के समय पर डेटा भेजना। बाक़ी सब कुछ — लॉगिन करना, मैसेज हिस्ट्री लाना, प्रोफ़ाइल अपडेट करना, सर्च करना — अब भी सादे, स्टेटलेस HTTP रिक्वेस्ट्स पर ही चलता है, क्योंकि यही वह मॉडल है जो अच्छे से कैश होता है, बिना ड्रामे के हॉरिज़ॉन्टली स्केल करता है, और कॉर्पोरेट फ़ायरवॉल से बच निकलता है।
तो “हर पेज पर एक सॉकेट” आख़िर कब सही मायने रखता है?
मैं यह नहीं कहना चाहता कि WebSocket किसी तरह की ग़लती हैं — वे नहीं हैं। वे सही चुनाव हैं जब:
- सर्वर को बिना पूछे डेटा भेजने की ज़रूरत हो — लाइव चैट, मल्टीप्लेयर गेम स्टेट, स्टॉक टिकर्स, कोलैबोरेटिव एडिटिंग जहाँ एक व्यक्ति की हर कीस्ट्रोक को मिलीसेकंड्स के भीतर बाक़ी सबको पहुँचना ज़रूरी है [3]।
- अपडेट की फ़्रीक्वेंसी इतनी ज़्यादा हो कि पोलिंग बेकार साबित हो — अगर आप वैसे भी हर सेकंड किसी एंडपॉइंट को “बस एहतियातन” पीटते रहते, तो सॉकेट साफ़ तौर पर ज़्यादा ईमानदार डिज़ाइन है।
- लेटेंसी का अनुभव पर असल फ़र्क़ पड़े — टाइपिंग इंडिकेटर में आधे सेकंड की देरी ठीक है; एक प्रतिस्पर्धी मल्टीप्लेयर गेम में आधे सेकंड की देरी ठीक नहीं है।
लेकिन एक सामान्य पेज लोड के लिए — किसी प्रोडक्ट पेज, डैशबोर्ड, ऑर्डर्स की सूची, या किसी यूज़र की प्रोफ़ाइल को लाने के लिए — इनमें से कोई भी शर्त असल में लागू नहीं होती। आप एक बार कुछ माँगते हैं, जवाब पाते हैं, और आगे बढ़ जाते हैं। यह बिल्कुल वही आकार है जिसके लिए HTTP बनाया गया था, और बिल्कुल वही आकार है जिसे कैशिंग, स्टेटलेसनेस का फ़ायदा मिलता है — और जिसे यह ज़रूरत नहीं पड़ती कि आपकी ऑप्स टीम चीज़ों को चालू रखने के लिए स्टिकी सेशन्स और शेयर्ड Redis स्टेट कॉन्फ़िगर करे।
मेरी ईमानदार राय
अगर मुझे इसे एक लाइन में समेटना हो तो: WebSocket का “स्टेटफुल” पहलू कोई मुफ़्त का बोनस फ़ीचर नहीं है — यह वह बिल है जो आप पुश पाने की सुविधा के बदले चुकाते हैं। जब आपको सच में पुश की ज़रूरत हो, तब यह बढ़िया सौदा है। जब ज़रूरत न हो, तब यह बुरा सौदा है, क्योंकि आप एक ऐसे फ़ीचर के लिए सारी लागतें (प्रति-कनेक्शन मेमोरी, स्टिकी सेशन्स, फ़ायरवॉल की कमज़ोरी, ज़ीरो कैशिंग) उठाते रहते हैं जिसका आप इस्तेमाल ही नहीं कर रहे।
HTTP/2 की मल्टीप्लेक्सिंग ने पहले ही हमें “एक कुशल पाइप” का वह ज़्यादातर फ़ायदा दे दिया है जिसे लोग सॉकेट्स से जोड़ते हैं, और वह भी बिना ऑपरेशनल सिरदर्द के [9]। तो “हर पेज पर एक सॉकेट क्यों नहीं” का असली जवाब “क्योंकि HTTP अच्छा है और सॉकेट्स बुरे हैं” नहीं है — बल्कि यह है कि दोनों प्रोटोकॉल विपरीत समस्याओं के लिए बनाए गए हैं, और बिना सोचे-समझे स्टेटफुल वाले को चुन लेना उस समस्या को बदल देता है जो आपके पास नहीं है (बहुत ज़्यादा रिक्वेस्ट्स) कई ऐसी समस्याओं से जो आपके पास ज़रूर आएंगी (मेमोरी का दबाव, स्टिकी सेशन्स, कैश इनवैलिडेशन, और रात के दो बजे एक बहुत उलझन में पड़ा हुआ ऑप्स इंजीनियर)।
स्रोत
- How Do WebSockets Work? — Postman Blog
- WebSocket vs HTTP: When to Use Each Protocol — WebSocket.org
- WebSocket vs REST: Key differences and which to use — Ably
- WebSocket Connection Limits: The Real Bottlenecks — WebSocket.org
- WebSockets at Scale: Architecture for Millions of Connections — WebSocket.org
- How to scale WebSockets for high-concurrency systems — Ably
- How to Scale WebSocket Connections — OneUptime
- Connection management in HTTP/1.x — MDN Web Docs
- Chrome’s 6 TCP connections limit — HTTP/1.1
- WebSocket Handshake: HTTP Upgrade at Protocol Level — WebSocket.org
- RFC 6455 — The WebSocket Protocol
- Web (HTTP/S) Cache and Caching Proxy — Imperva CDN Guide
- Gateway Documentation — Discord Developers
- Comparing HTTP & Socket Mode — Slack Developer Docs
- How to Fix ‘Connection Refused’ WebSocket Errors — OneUptime
- Getting through firewalls — RTC Quickstart Guide