हा प्रश्न वारंवार का विचारला जातो हे मला समजतं. WebSocket उघडं राहतं, तुम्ही कोण आहात हे लक्षात ठेवतं, आणि सर्व्हरला तुम्ही पुन्हा पुन्हा विचारल्याशिवाय डेटा पाठवू देतं. मग एकच सतत चालू राहणारी पाईप उघडून काम भागत असताना आपण अजूनही एका पान लोडसाठी शेकडो वेगवेगळे HTTP रिक्वेस्ट का पाठवतो? खरं सांगायचं तर हा प्रश्न लोक समजतात त्यापेक्षा जास्त हुशार आहे — आणि उत्तर “कारण HTTP जास्त चांगलं आहे” असं नाही. ते यापेक्षा बरंच गुंतागुंतीचं आहे.
थांबा, स्टेटफुल कनेक्शन हे साहजिकच जास्त कार्यक्षम नाही का?
कागदावर तरी, हो. एकदा का सुरुवातीच्या HTTP “अपग्रेड” हँडशेकद्वारे WebSocket कनेक्शन स्थापित झालं, की क्लायंट आणि सर्व्हर दोघेही कधीही एकमेकांना डेटा पाठवू शकतात, आणि प्रत्येक मेसेजवर फारच कमी फ्रेमिंग ओव्हरहेड येतो [1]. पुन्हा पुन्हा हेडर्स नाहीत, प्रत्येक रिक्वेस्टसोबत कुकीज वाहून नेण्याची गरज नाही, प्रत्येक देवाणघेवाणीत तुम्ही कोण आहात हे पुन्हा सिद्ध करण्याची गरज नाही. हँडशेकनंतर, WebSocket डेटा फ्रेम्समध्ये HTTP च्या तुलनेत खूपच कमी प्रोटोकॉल ओव्हरहेड असतो — कारण HTTP मधल्या प्रत्येक रिक्वेस्ट आणि रिस्पॉन्ससोबत कुकीज, यूजर-एजंट स्ट्रिंग्ज, आणि कॅश-कंट्रोल डायरेक्टिव्हजसारखे हेडर्स ओढले जातात [2].
त्यामुळे ही उपजत भावना तर्कसंगत वाटते: जर कनेक्शनला आधीच मी कोण आहे हे माहीत आहे आणि ते उघडं राहतं, तर मी प्रत्येक पानावर शंभर वेळा स्वतःची ओळख का करून द्यावी?
पण इथेच खरी गोम आहे — जी गोष्ट WebSockets ला रिअल-टाइम कम्युनिकेशनसाठी उत्तम बनवते (स्टेटफुलनेस), तीच गोष्ट त्यांना मोठ्या प्रमाणावर चालवणं महाग बनवते. हा काही मोफत मिळणारा फायदा नाही. हा एक व्यवहार आहे जो तुम्ही करत आहात, आणि सर्वसाधारण वेब पानासाठी बहुतेक वेळा हा व्यवहार फायदेशीर ठरत नाही.
“सर्व्हरला तुम्ही आठवत असतो” याची लपलेली किंमत
जेव्हा तुम्ही एक सर्वसाधारण 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 तर खूप कनेक्शन्स वाया घालवतं!” — आता तसं फारसं नाही
मला वाटतं या युक्तिवादातला हाच भाग लोकांना सर्वात जास्त गोंधळात टाकतो, कारण तो HTTP बद्दलच्या 2009 सालच्या समजुतीत अडकलेला आहे. हो, ब्राउझर्सनी ऐतिहासिकदृष्ट्या HTTP/1.1 अंतर्गत प्रति डोमेन फक्त 6 समकालीन कनेक्शन्सची मर्यादा घातली होती [9], आणि हो, यामुळे एखादं “बोलकं” पान असं वाटायचं की रिक्वेस्ट्स एकामागोमाग एक रांगेत उभ्या आहेत.
पण दोन गोष्टींनी हे चित्र खूप बदललं:
- HTTP/1.1 keep-alive. “Connection: keep-alive” यंत्रणा ब्राउझरला प्रत्येक वेळी नवीन कनेक्शन उघडण्याऐवजी अनेक रिक्वेस्ट्ससाठी तेच TCP कनेक्शन पुन्हा वापरू देते [8], ज्यामुळे लोकांना वाटणाऱ्या HTTP च्या “ओव्हरहेड"चा मोठा भाग आधीच नाहीसा होतो.
- HTTP/2 मल्टिप्लेक्सिंग. HTTP/2 सोबत, ब्राउझर प्रत्येक डोमेनसाठी फक्त एकच TCP कनेक्शन उघडतो आणि त्यावर अनेक रिक्वेस्ट/रिस्पॉन्स “स्ट्रीम्स” समकालीन चालवतो [9]. यामुळे जुनी प्रति-डोमेन अडचण जवळजवळ नाहीशी होते — WebSockets बद्दल लोकांना जी कार्यक्षमता आवडते (एक कनेक्शन, अनेक देवाणघेवाणी) ती तुम्हाला स्टेटलेसनेस न सोडता मिळते.
खाली दिलेली तुलना खरे ट्रेड-ऑफ्स बरेच स्पष्ट करते:
| बाब | HTTP/1.1 (keep-alive) | HTTP/2 | WebSocket |
|---|---|---|---|
| प्रति पान कनेक्शन्स | प्रति डोमेन कमाल 6 [9] | प्रति डोमेन 1 (मल्टिप्लेक्स्ड) [9] | 1 (सतत चालू) |
| सर्व्हरला तुम्हाला “लक्षात” ठेवावं लागतं का | नाही | नाही | हो — कनेक्शन जिवंत असेपर्यंत [3] |
| CDNs/प्रॉक्सीजद्वारे कॅश करता येतं का | हो [12] | हो [12] | नाही — कॅश करण्यासारखं काही नसतं, ती एक थेट स्ट्रीम असते |
| न विचारता सर्व्हर डेटा पाठवू शकतो का | नाही (आधी रिक्वेस्ट यायला हवी) | नाही | हो, कधीही [2] |
| कडक कॉर्पोरेट फायरवॉल्समागे काम करतं का | हो (पोर्ट 443 नेहमी उघडा असतो) | हो | अनेकदा ब्लॉक होतं किंवा फॉलबॅक लागतो [15] |
| स्केलिंग मॉडेल | स्टेटलेस — कोणताही सर्व्हर, कुठेही | स्टेटलेस — कोणताही सर्व्हर, कुठेही | स्टिकी सेशन्स किंवा सामायिक स्थिती लागते [4][6] |
ही तक्ता पाहिली की प्रश्नाचं उत्तर जवळजवळ आपोआपच मिळतं: HTTP/2 ने स्टेटलेसनेस सोडण्यास न सांगता आधीच “खूप जास्त कनेक्शन्स"ची समस्या सोडवली आहे. तुम्हाला कॅशिंग, सोपी क्षैतिज स्केलिंग, आणि फायरवॉल-अनुकूलता मिळतेच — आणि तुम्ही WebSocket कडे फक्त तेव्हाच वळता जेव्हा तुम्हाला त्याची एकमेव खरोखर वैशिष्ट्यपूर्ण ताकद हवी असते: सर्व्हरने स्वतः काहीतरी घडलं असं ठरवल्यावर, तुम्ही न विचारता, तुम्हाला डेटा पाठवणं.
तुम्ही कॅशिंग गमवाल — आणि हे ऐकण्यापेक्षा खूप मोठी गोष्ट आहे
मला वाटतं ही गोष्ट सर्वात जास्त कमी लेखली जाते. सगळ्यासाठी WebSockets वापरणं म्हणजे तुम्ही काहीच कॅश करू शकत नाही, आणि हे हळूहळू तुमचा सर्व्हर खर्च खूप वाढवतं [3]. एका सर्वसाधारण पान लोडमध्ये काय काय असतं याचा विचार करा — तुमचं CSS, तुमच्या इमेजेस, क्वचितच बदलणाऱ्या गोष्टींसाठीचे तुमचे API रिस्पॉन्सेस, तुमचा यूजर अवतार, तुमच्या उत्पादनांच्या याद्या. यातला मोठा भाग हजारो यूजर्ससाठी सारखाच असतो आणि मिनिटा-मिनिटाला फारसा बदलत नाही.
साध्या HTTP सोबत, CDNs आणि रिव्हर्स प्रॉक्सीज तुमच्या सर्व्हर्सच्या आधी उभे राहून हे सगळं कॅश करतात, आणि पुन्हा-पुन्हा येणाऱ्या रिक्वेस्ट्सना थेट एजवरून उत्तर देतात, ज्यामुळे तुमच्या मूळ सर्व्हरला घाम फुटत नाही [12]. हा एक संरचनात्मक फायदा आहे जो स्टेटलेस रिक्वेस्ट/रिस्पॉन्स मॉडेलमध्येच अंतर्भूत आहे — कारण कोणीही काहीही लक्षात ठेवत नसल्याने, कुठलाही कॅश, कुठेही, उत्तर देऊ शकतो.
WebSocket ही एक थेट, द्विदिशी स्ट्रीम आहे. इथे कॅश करण्यासाठी कोणताच “रिस्पॉन्स” नसतो — फक्त त्या एका कनेक्शनसाठी खास असलेलं चालू संभाषण असतं. ज्या क्षणी तुम्ही सगळं सॉकेट्समधून ढकलायला सुरुवात करता, त्याच क्षणी तुम्ही वेबवरचं सर्वात स्वस्त, सर्वात अनुभवसिद्ध परफॉर्मन्स साधन — साधा HTTP कॅश — फेकून देता.
आणि मग येतो तो अचानक “नाही” म्हणणारा कॉर्पोरेट फायरवॉल
तुम्ही असं काही बनवलं आहे का जे घरच्या वाय-फाय वर पूर्ण ठीक चाललं, पण कोणीतरी ऑफिसच्या नेटवर्कवरून वापरून पाहताच पूर्णपणे कोलमडलं? WebSockets ला हे सतत होतं. बहुतेक वेब प्रॉक्सीज आणि कडक कॉर्पोरेट फायरवॉल्स WebSocket कनेक्शन्स थेट ब्लॉक करतात, अनेकदा कारण ते फक्त 80 आणि 443 पोर्ट्सवरून, पारदर्शक प्रॉक्सीद्वारे, साधा HTTP ट्रॅफिकच जाऊ देण्यासाठी सेट केलेले असतात [15][16].
पोर्ट बरोबर असला तरी, प्रॉडक्शनमध्ये WebSocket अयशस्वी होण्याचं सर्वात सामान्य कारण म्हणजे, WebSocket कनेक्शन सुरू करणाऱ्या HTTP “Upgrade” हँडशेकला पुढे पाठवण्यासाठी रिव्हर्स प्रॉक्सीजना स्पष्टपणे कॉन्फिगर करावं लागतं [15]. ही सेटिंग नसेल — आणि बऱ्याचदा ती नसतेच, कारण प्रत्येक ऑप्स टीम ती जोडण्याचा विचार करत नाही — तर तुमचं सॉकेट कनेक्टच होणार नाही, आणि मग तुम्हाला फॉलबॅक लॉजिक (लाँग-पोलिंग, रिट्राय लूप्स, सगळं काही) लिहावं लागतं, फक्त तुमचं अॅप सुरळीतपणे खालावत जावं म्हणून.
साध्या HTTP ला ही समस्या नाही. पृथ्वीवरचा प्रत्येक प्रॉक्सी, फायरवॉल आणि कॉर्पोरेट नेटवर्क हे अपेक्षित आणि स्वीकारण्यासाठीच बनवलेलं आहे. हा काही छोटासा फायदा नाही — हा “हॉटेलच्या वाय-फाय वर असलेल्या अकाउंटंटसाठीही तुमचं अॅप खरोखर काम करतं” इतक्या मोठ्या स्तराचा फायदा आहे.
मग मोठ्या रिअल-टाइम अॅप्स हे प्रत्यक्षात कसं हाताळतात?
मला हा भाग खरोखरच शिकवणारा वाटतो, कारण Slack आणि Discord सारख्या कंपन्या खरोखरच सतत चालू राहणाऱ्या कनेक्शन्सवर खूप अवलंबून असतात — पण लक्षात घ्या, त्या HTTP ला सॉकेट्सने बदलत नाहीत. त्या दोन्ही एकत्र चालवतात, प्रत्येक जे चांगलं करतं ते करत राहतो.
- Discord चं Gateway हे एक सतत चालू राहणारं WebSocket कनेक्शन आहे जे रिअल-टाइम घटना पाठवतं — एखाद्या चॅनलचं नाव बदललं, एखादा रोल तयार झाला, कोणीतरी ऑनलाइन आलं. पण Discord स्पष्टपणे सांगतं की बहुतेक प्रकरणांत, त्याच्या संसाधनांवरील सर्वसाधारण कार्ये Gateway ऐवजी सर्वसाधारण HTTP API द्वारेच व्हायला हवीत, कारण gateway कनेक्शन्स उघडणं, टिकवणं आणि डिस्कनेक्ट झाल्यावर सावरणं हे सरळसोट जास्त गुंतागुंतीचं असतं [13].
- Slack चा Socket Mode हाही असाच आहे — अॅप्स लाइव्ह घटना मिळवण्यासाठी WebSocket वापरतात, पण Slack स्पष्टपणे शिफारस करतं की प्रतिसाद परत पाठवण्यासाठी मात्र मानक Web API (साधं HTTPS) वापरावं [14]. एक मार्ग “मला सांग नुकतं काय घडलं” यासाठी, आणि दुसरा “मला त्याबद्दल काय करायचं आहे ते सांगतो” यासाठी.
हा नमुना लक्षात आला का? सतत चालू राहणारं कनेक्शन फक्त त्या एका गोष्टीसाठी राखीव ठेवलं जातं जी HTTP खरोखरच चांगल्या प्रकारे करू शकत नाही: सर्व्हरने स्वतःच्या वेळापत्रकानुसार तुम्हाला डेटा पाठवणं. बाकी सगळं — लॉग इन करणं, मेसेज इतिहास आणणं, प्रोफाइल अपडेट करणं, शोधणं — हे सगळं अजूनही साध्या जुन्या स्टेटलेस HTTP रिक्वेस्ट्सवरच चालतं, कारण तेच मॉडेल चांगलं कॅश होतं, नाटक न करता क्षैतिज दिशेने स्केल होतं, आणि कॉर्पोरेट फायरवॉलमधूनही तगून राहतं.
मग “प्रत्येक पानासाठी एक सॉकेट” खरोखर कधी अर्थपूर्ण ठरतं?
मला असं वाटू द्यायचं नाही की WebSockets ही काहीतरी चूक आहे — ती नाहीत. खालील परिस्थितींमध्ये त्या योग्य निवड ठरतात:
- सर्व्हरला न विचारता डेटा पाठवायचा असतो — लाइव्ह चॅट, मल्टीप्लेअर गेमची स्थिती, स्टॉक टिकर्स, सहयोगी संपादन जिथे एका व्यक्तीचं प्रत्येक कीस्ट्रोक मिलिसेकंदात बाकी सर्वांपर्यंत पोहोचायला हवं [3].
- अपडेट्सची वारंवारता इतकी जास्त आहे की पोलिंग करणं वाया घालवण्यासारखं ठरेल — जर तुम्ही “कदाचित काही बदललं असेल” म्हणून दर सेकंदाला एखाद्या एंडपॉइंटला धडका मारत राहणार असाल, तर सॉकेट हीच स्पष्टपणे जास्त प्रामाणिक रचना ठरते.
- विलंब (latency) हा अनुभवासाठी खरोखरच महत्त्वाचा असतो — टायपिंग इंडिकेटरमध्ये अर्ध्या सेकंदाचा विलंब चालतो; स्पर्धात्मक मल्टीप्लेअर गेममध्ये तो चालत नाही.
पण एखाद्या सर्वसाधारण पान लोडसाठी — उत्पादनाचं पान, डॅशबोर्ड, ऑर्डर्सची यादी, यूजरचं प्रोफाइल आणणं — यापैकी कोणतीच अट खरंच लागू होत नाही. तुम्ही एकदा काहीतरी मागता, उत्तर मिळतं, आणि पुढे जाता. HTTP नेमकं याच आकाराच्या कामासाठी बनवलं गेलं होतं, आणि नेमकं यालाच कॅशिंग, स्टेटलेसनेस आणि — गोष्टी सुरळीत ठेवण्यासाठी तुमच्या ऑप्स टीमला स्टिकी सेशन्स आणि सामायिक Redis स्थिती कॉन्फिगर करावी लागत नाही याचा — फायदा होतो.
माझं प्रामाणिक मत
जर मला हे सगळं एका वाक्यात सांगायचं असेल, तर: WebSockets चा “स्टेटफुल” भाग हा तुम्हाला फुकटात मिळणारं अतिरिक्त वैशिष्ट्य नाही — ती किंमत आहे जी तुम्ही पुश मिळवण्याच्या क्षमतेसाठी चुकवता. जेव्हा तुम्हाला खरोखर पुशेसची गरज असते तेव्हा हा एक उत्तम व्यवहार ठरतो. जेव्हा गरज नसते तेव्हा तो वाईट व्यवहार ठरतो, कारण मग तुम्ही न वापरत असलेल्या एका वैशिष्ट्यासाठी सगळ्या किमती (प्रति-कनेक्शन मेमरी, स्टिकी सेशन्स, फायरवॉलची नाजूकता, शून्य कॅशिंग) वाहत राहता.
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