व्ह्यू काउंट (view count) हा असा पहिला आकडा आहे ज्यावर भेट देणारा विश्वास ठेवतो. एखादा व्हिडिओ तीस सेकंद किंवा तीस मिनिटे पाहण्यासारखा आहे की नाही, हे त्यावरून समजते. TopVideoHub मध्ये, हा आकडा वेगाने बदलतो. एखादा ट्रेंडिंग क्लिप दहा मिनिटांत ४०,००० व्ह्यूज मिळवू शकतो. जर पेजवरील काउंटर फ्रीझ झाला, तर वातावरण रिकामे वाटते. युजर्स लगेच निघून जातात.
तो आकडा ब्राउझरपर्यंत पोहोचवणे साधे वाटते, पण तसे नाहीये. बहुतेक टीम्सचा पहिला उपाय 'पोलिंग' (polling) हा असतो. ते सेटअप करणे सोपे आहे आणि स्टेजिंगमध्ये (staging) व्यवस्थित काम करते. पण स्टेजिंग नेहमी खरे नसते.
जेव्हा पोलिंग स्वतःविरुद्ध DDoS बनू लागते
TopVideoHub टीमने एक साधा JavaScript poller तयार केला. तो दर पाच सेकंदांनी लेटेस्ट व्ह्यू काउंट मिळवत असे. तीन ब्राउझर्स उघडे असलेल्या टेस्ट एन्व्हायरमेंटमध्ये ते उत्तम दिसत होते. पण प्रोडक्शनमध्ये (production), त्याने प्लॅटफॉर्मची अवस्था बिकट केली.
दर पाच सेकंदांनी रिफ्रेश होणारे आठ हजार समवर्ती (concurrent) व्ह्यूअर्स दर सेकंदाला १,६०० रिक्वेस्ट जनरेट करत होते. प्रत्येक रिक्वेस्ट डेटाबेसमध्ये जात होती. यामुळे रेप्लिकेशन लॅग (replication lag) वाढला, रीड रेप्लिकासवर (read replicas) ताण आला आणि कॅश लेयर्स बायपास झाल्या. टीम व्हिडिओ सर्व्ह करत नव्हती, तर स्वतःहून निर्माण झालेला लोड (self-inflicted load) हाताळत होती.
जोपर्यंत समस्या येत नाही तोपर्यंत पोलिंग सुरक्षित वाटते. कमी ट्रॅफिक असलेल्या डॅशबोर्ड किंवा ॲडमिन पॅनेलसाठी ते ठीक आहे. पण व्हायरल व्हिडिओ पेजसाठी, ते एका चालत्या बॉम्बसारखे आहे. टीमला सर्व्हरपासून ब्राउझरपर्यंत एका 'परसिस्टंट पाईप'ची (persistent pipe) गरज होती, पण त्यांना फुल-ड्युप्लेक्स प्रोटोकॉलची (full-duplex protocol) गुंतागुंत नको होती.
वन-वे पाईप्ससाठी SSE का योग्य आहे
Server-Sent Events (SSE) नेमक्या याच प्रकारच्या समस्येसाठी बनवले आहे: सर्व्हरकडे डेटा आहे आणि ब्राउझरला फक्त तो ऐकण्याची (listen) गरज आहे.
WebSockets च्या उलट, SSE साध्या HTTP वर चालते. हे जितके साधे वाटते त्यापेक्षा जास्त महत्त्वाचे आहे. तुम्हाला नवीन प्रॉक्सी रूल्स, अपग्रेड हेडर्स किंवा लोड-बॅलन्सरची कसरत करण्याची गरज नाही. जर तुमचा सर्व्हर HTTP/1.1 किंवा HTTP/2 वापरत असेल, तर SSE काम करते. डीबगिंग करणे सोपे आहे कारण स्ट्रीम फक्त टेक्स्ट स्वरूपात असते. तुम्ही curl वापरून एंडपॉइंटवर रिअल-टाइममध्ये बदल पाहू शकता, जे बायनरी सॉकेट फ्रेम का चुकली हे शोधण्यापेक्षा कितीतरी सोपे आहे.
ब्राउझर कठीण कामे मोफत हाताळतो. जर कनेक्शन तुटले, तर SSE Last-Event-ID हेडरसह आपोआप पुन्हा कनेक्ट होते, जेणेकरून सर्व्हरला कुठून सुरू करायचे आहे हे समजते. JavaScript मधील API अत्यंत सोपे आहे: फक्त एक EventSource तयार करा, onmessage हँडलर जोडा आणि तुमचे काम झाले.
कॅशिंग (Caching) हे खरे आर्किटेक्चर आहे
लाईव्ह काउंटर्समधील सर्वात मोठी आर्किटेक्चरल चूक म्हणजे प्रत्येक ब्राउझर कनेक्शनला डेटाबेस क्वेरी करण्यासाठी कारण मानणे. जर ८,००० लोक एकच व्हिडिओ पाहत असतील, तर दर दोन सेकंदाला ८,००० क्वेरी चालवणे वेडेपणा आहे. व्हायरल ट्रॅफिकमध्ये तुमचा डेटाबेस टिकणार नाही.
TopVideoHub ने हे APCu (PHP चे इन-मेमरी opcode आणि युजर कॅश) वापरून सोडवले. ही प्रक्रिया सोपी आहे. एक बॅकग्राउंड प्रोसेस—किंवा टाइमरवर चालणारा एक हलका एंडपॉइंट—दर दोन सेकंदांनी सध्याचा व्ह्यू काउंट APCu मध्ये लिहितो. SSE एंडपॉइंट, जो हजारो ब्राउझर्सनी उघडा ठेवला असू शकतो, तो फक्त APCu मधून डेटा वाचतो.
परिणाम: कितीही व्ह्यूअर्स पाहत असले तरी, डेटाबेसवर दर दोन सेकंदाला फक्त एकच हिट पडते. कॅश हे 'शॉक ॲबसॉर्बर' (shock absorber) म्हणून काम करते. APCu काहीतरी वेगळे किंवा कठीण नाही. ते PHP सोबत येते, शेअर्ड मेमरीमध्ये राहते आणि कोणत्याही नेटवर्क राऊंड-ट्रिपपेक्षा वेगाने वाचते. जो आकडा वारंवार बदलतो पण लगेच (instantaneously) बदलण्याची गरज नसते, त्यासाठी हे योग्य साधन आहे.
जर तुम्ही APCu वापरत नसाल, तर Redis किंवा Memcached देखील वापरू शकता. तत्व एकच आहे: डेटाबेसपासून 'हॉट रीड पाथ' (hot read path) वेगळा करा.
PHP, LiteSpeed आणि Cloudflare ला स्ट्रीमिंगसाठी तयार करणे
PHP ला काम संपवून बाहेर पडायचे असते. वेब सर्व्हर्सना आउटपुट बफर (buffer) करून एक व्यवस्थित रिस्पॉन्स द्यायचा असतो. SSE ला याच्या अगदी उलट हवे असते: एक कनेक्शन जे उघडे राहील आणि जसे बाइट्स येतील तसे ते फ्लश (flush) करेल. काळजी न घेतल्यास, तुमचा "स्ट्रीम" ३० सेकंदांनंतर एक मोठा डेटा ब्लॉक (blob) म्हणून येईल, ज्यामुळे मूळ उद्देशच संपून जाईल.
TopVideoHub ने ही पाईप कशी मोकळी ठेवली, ते खालीलप्रमाणे आहे:
आउटपुट बफरिंग (output buffering) बंद करा. SSE स्क्रिप्टच्या सुरुवातीला, PHP ने सक्षम केलेले बफरिंगचे सर्व लेयर्स अक्षम (disable) करा. जर बफर सक्रिय असेल तर ob_end_flush() कॉल करा आणि हेडर्स पाठवल्यानंतर ob_implicit_flush(true) वापरून इम्पलिसिट फ्लशिंग बंद करा.
प्रॉक्सींना (proxies) थांबायला सांगा. X-Accel-Buffering: no हेडर पाठवा. Nginx आणि LiteSpeed याचे पालन करतात. हे सूचित करते की रिस्पॉन्स बफर किंवा कॅशे करण्यायोग्य ब्लॉकमध्ये कॉम्प्रेस केला जाऊ नये.
कमी लाइफटाइम सेट करा. प्रत्येक SSE कनेक्शन एक PHP वर्कर (worker) गुंतवून ठेवते. TopVideoHub स्ट्रीम्स ५५ सेकंदांवर मर्यादित ठेवते. जेव्हा टाइमर संपतो, तेव्हा सर्व्हर एक शेवटचा कमेंट पाठवतो, स्ट्रीम बंद करतो आणि ब्राउझर आपोआप पुन्हा कनेक्ट होतो. हे री-कनेक्शन एका नवीन वर्करवर होते, ज्यामुळे कोणताही एक प्रोसेस कायमचा अडकून पडत नाही.
कनेक्शन चालू ठेवण्यासाठी 'Ping' करा. दर वीस सेकंदांनी एक कमेंट लाईन पाठवा—जसे की : ping. SSE मधील कमेंट्स ब्राउझरच्या मेसेज हँडलरद्वारे दुर्लक्षित केल्या जातात, परंतु त्या TCP कनेक्शन सक्रिय (warm) ठेवतात. लोड बॅलन्सर आणि CDNs अनेकदा तीस किंवा साठ सेकंदांनंतर शांत (silent) कनेक्शन्स तोडतात. एक साधी नवीन ओळ (newline) तुम्हाला त्या संकटातून वाचवू शकते.
वापरकर्त्याच्या टॅबचा आदर करा. जेव्हा एखादा व्हिजिटर टॅब मिनिमाइज करतो किंवा लपवतो, तेव्हा प्रक्रिया थांबवा. ब्राउझरमध्ये visibilitychange वर लक्ष ठेवा आणि eventSource.close() कॉल करा. सर्व्हरने देखील क्लायंट डिस्कनेक्ट झाल्याचे ओळखले पाहिजे आणि लूप थांबवला पाहिजे. PHP मध्ये लूपच्या आत connection_aborted() वापरून हे तपासता येते. दहा मिनिटांपूर्वी निघून गेलेल्या लोकांसाठी 'घोस्ट कनेक्शन्स' (ghost connections) मुळे वर्कर्स वाया घालवू नका.
कडक मर्यादा: PHP Workers
PHP मधील SSE त्याच्या मर्यादांबाबत स्पष्ट आहे. प्रत्येक उघडलेले SSE कनेक्शन एक PHP worker वापरते. जर तुमच्या पूलमध्ये शंभर वर्कर्स असतील, तर तुमच्याकडे शंभर स्ट्रीम्स आहेत. इतकेच. जोपर्यंत तुम्ही Apache किंवा PHP-FPM प्रोसेस मॉडेलमध्ये आहात, तोपर्यंत कोणताही async पर्याय उपलब्ध नाही. तुम्ही pm.max_children ट्यून करू शकता, परंतु मेमरी आणि CPU खऱ्या मर्यादा ठरवतात.
जर तुम्ही नियमित पेज लोड, API कॉल्स आणि ॲसेट जनरेशनसाठी देखील वर्कर्स वापरत असाल, तर ही मर्यादा लवकर जाणवते. तुमच्या वर्कर सॅच्युरेशनवर (worker saturation) काळजीपूर्वक लक्ष ठेवा. जर सर्व वर्कर्स वीस मिनिटांच्या स्ट्रीम्समध्ये अडकल्यामुळे तुमचा SSE endpoint रांगेत (queuing) उभा राहू लागला, तर तुमची संपूर्ण साइट मंदावते.
जेव्हा संख्या मर्यादेबाहेर जाते, तेव्हा बदल करा. Go हा सहसा पुढचा टप्पा असतो, जरी Rust, Node.js किंवा Erlang देखील तीच भूमिका बजावू शकतात. Go चे goroutines ही मुख्य गुरुकिल्ली आहे. एका goroutine साठी फक्त काही किलोबाइट्स खर्च होतात. तुम्ही सामान्य हार्डवेअरवरही कोणत्याही कष्टाशिवाय हजारो स्ट्रीम्स हाताळू शकता. मूळ लॉजिक तेच राहते—कॅशमधून वाचा, सॉकेटमध्ये लिहा—परंतु रनटाइम जड प्रोसेसेसकडून हलक्या थ्रेड्सकडे (lightweight threads) वळतो.
तरीही, तिथून सुरुवात करू नका. PHP तुम्हाला आश्चर्यकारकपणे लांबपर्यंत घेऊन जाऊ शकते. प्रथम उत्पादन (product) तपासा. जेव्हा मेट्रिक्स पेजवर डेटाबेस ओव्हरलोडऐवजी वर्कर संपल्याचे (worker exhaustion) दिसते, तेव्हा समजून जा की तुम्ही आता या स्टॅकच्या पलीकडे गेला आहात. ही एक चांगली समस्या आहे.
सारांश
लाइव्ह काउंटर्स हे केवळ तंत्रज्ञानाबद्दल नाहीत. ते तुमच्या डेटाबेसचे तुमच्या स्वतःच्या वापरकर्त्यांपासून संरक्षण करण्याबद्दल आहेत. SSE ने सुरुवात करा कारण ते दिसते तितके गुंतागुंतीचे नाही. स्ट्रीम आणि डेटाबेस दरम्यान आक्रमकपणे (aggressively) कॅशिंग करा, जेणेकरून कनेक्शनची संख्या क्वेरीच्या संख्येत रूपांतरित होणार नाही. तुमच्या वर्कर मर्यादांवर बारीक लक्ष ठेवा. आणि साध्या पद्धतीने सुरुवात करा. जोपर्यंत गरज पडत नाही तोपर्यंत PHP पुरेसे आहे, आणि तोपर्यंत तुम्हाला नक्की समजेल की तुम्ही पुन्हा का लिहित आहात.
तुमच्या पुढील प्रोजेक्टसाठी:
- जेव्हा डेटा एकाच दिशेने, सर्व्हरकडून ब्राउझरकडे वाहत असेल, तेव्हा SSE वापरा.
- डेटाबेसच्या समोर एक कॅश लेयर ठेवा. दर काही सेकंदांनी केलेली एक क्वेरी हजारो क्वेरींपेक्षा चांगली आहे.
- SSE कनेक्शन्सना एक मिनिटाच्या खाली मर्यादित ठेवा आणि ब्राउझरला पुन्हा कनेक्ट होऊ द्या.
- टॅब लपवल्यावर स्ट्रीम्स बंद करा. सक्रिय वर्कर्सचा वापर न वापरलेल्या (idle) कनेक्शन्ससाठी करू नका.
- PHP वर्कर वापराचे निरीक्षण करा. जेव्हा मर्यादा संपेल, तेव्हा स्ट्रीमिंग लेयर Go मध्ये पोर्ट करा.
