ویو کاؤنٹ (view count) وہ پہلا نمبر ہے جس پر ایک وزیٹر بھروسہ کرتا ہے۔ یہ اسے بتاتا ہے کہ آیا کوئی ویڈیو اس کے تیس سیکنڈ یا تیس منٹ کے وقت کے قابل ہے یا نہیں۔ TopVideoHub پر، وہ نمبر تیزی سے بدلتا ہے۔ ایک ٹرینڈنگ کلپ دس منٹ میں 40,000 ویوز حاصل کر سکتا ہے۔ اگر صفحے پر کاؤنٹر جم جائے، تو ایسا لگتا ہے جیسے کمرہ خالی ہو۔ صارفین ویب سائٹ چھوڑ کر چلے جاتے ہیں۔
اس نمبر کو براؤزر تک پہنچانا معمولی بات لگتی ہے۔ لیکن ایسا نہیں ہے۔ زیادہ تر ٹیمیں جو پہلا حل اپناتی ہے وہ polling ہے۔ اسے ترتیب دینا آسان ہے اور یہ staging میں ٹھیک کام کرتا ہے۔ لیکن staging دھوکہ دے سکتا ہے۔
جب Polling آپ کے اپنے خلاف DDoS بن جائے
TopVideoHub کی ٹیم نے ایک سادہ JavaScript poller بنایا۔ یہ ہر پانچ سیکنڈ بعد تازہ ترین ویو کاؤنٹ حاصل کرتا تھا۔ تین براؤزرز کے ساتھ ٹیسٹ ماحول میں یہ بہترین نظر آتا تھا۔ لیکن production میں، اس نے پلیٹ فارم کو تباہ کر دیا۔
ہر پانچ سیکنڈ میں ریفریش کرنے والے آٹھ ہزار بیک وقت دیکھنے والے صارفین (concurrent viewers) نے فی سیکنڈ 1,600 درخواستیں (requests) پیدا کیں۔ ہر درخواست ڈیٹا بیس میں داخل ہو رہی تھی۔ Replication lag میں اضافہ ہوا۔ Read replicas پر بوجھ بڑھ گیا۔ Cache layers کو بائی پاس کر دیا گیا۔ ٹیم ویڈیو فراہم نہیں کر رہی تھی، بلکہ وہ خود اپنے اوپر بوجھ ڈال رہی تھی۔
Polling تب تک بے ضرر ہے جب تک کہ وہ مسئلہ نہ بن جائے۔ کم ٹریفک والے ڈیش بورڈز یا ایڈمن پینلز کے لیے یہ ٹھیک ہے۔ لیکن ایک وائرل ویڈیو پیج کے لیے، یہ ایک ٹِکنگ بم ہے۔ ٹیم کو سرور سے براؤزر تک ایک مستقل پائپ (persistent pipe) کی ضرورت تھی، لیکن انہیں full-duplex protocol کی پیچیدگیوں کی ضرورت نہیں تھی۔
SSE ون-وے پائپس (One-Way Pipes) کے لیے کیوں موزوں ہے
Server-Sent Events (SSE) بالکل اسی طرح کے مسئلے کے لیے بنایا گیا ہے: سرور کے پاس ڈیٹا ہے، اور براؤزر کو صرف اسے سننے کی ضرورت ہے۔
WebSockets کے برعکس، SSE سادہ HTTP پر چلتا ہے۔ یہ اس سے کہیں زیادہ اہم ہے جتنا یہ لگتا ہے۔ آپ کو نئے proxy rules، upgrade headers، یا load-balancer کی پیچیدگیوں کی ضرورت نہیں ہے۔ اگر آپ کا سرور HTTP/1.1 یا HTTP/2 استعمال کرتا ہے، تو SSE کام کرے گا۔ Debugging بہت آسان ہے کیونکہ اسٹریم صرف ٹیکسٹ ہے۔ آپ curl کو endpoint پر چلا کر ریئل ٹائم میں نمبروں کو بدلتے ہوئے دیکھ سکتے ہیں، جو کہ اس اندازے سے کہیں بہتر ہے کہ کوئی بائنری ساکٹ فریم (binary socket frame) کیوں خراب ہوا۔
براؤزر مشکل کام مفت میں سنبھال لیتا ہے۔ اگر کنکشن ٹوٹ جائے، تو SSE Last-Event-ID ہیڈر کے ساتھ خود بخود دوبارہ جڑ جاتا ہے تاکہ سرور کو معلوم ہو کہ کہاں سے دوبارہ شروع کرنا ہے۔ JavaScript میں API کا استعمال بہت سادہ ہے: ایک EventSource بنائیں، ایک onmessage ہینڈلر لگائیں، اور آپ کا کام ہو گیا۔
Caching ہی اصل آرکیٹیکچر ہے
لائیو کاؤنٹرز میں سب سے بڑی آرکیٹیکٹURAL غلطی یہ ہے کہ ہر براؤزر کنکشن کو ڈیٹا بیس کو کوئری (query) کرنے کی وجہ سمجھا جاتا ہے۔ اگر 8,000 لوگ ایک ہی ویڈیو دیکھ رہے ہیں، تو ہر دو سیکنڈ میں 8,000 کوئریز چلانا دیوانگی ہے۔ آپ کا ڈیٹا بیس وائرل ٹریفک کا مقابلہ نہیں کر پائے گا۔
TopVideoHub نے اسے APCu کے ذریعے حل کیا، جو کہ PHP کا in-memory opcode اور user cache ہے۔ اس کا طریقہ کار سادہ ہے۔ ایک بیک گراؤنڈ پروسیس—یا ٹائمر پر چلنے والا ایک ہلکا پھلکا endpoint—ہر دو سیکنڈ میں ایک بار موجودہ ویو کاؤنٹ کو APCu میں لکھ دیتا ہے۔ SSE endpoint، جسے ہزاروں براؤزرز نے کھول رکھا ہو سکتا ہے، صرف APCu سے ڈیٹا پڑھتا ہے۔
نتیجہ: ڈیٹا بیس پر ہر دو سیکنڈ میں صرف ایک بار بوجھ پڑتا ہے، چاہے کتنے ہی ناظرین دیکھ رہے ہوں۔ کیش (cache) ایک شاک ابزرور (shock absorber) کا کام کرتا ہے۔ APCu کوئی غیر معمولی چیز نہیں ہے۔ یہ PHP کے ساتھ آتا ہے، shared memory میں رہتا ہے، اور کسی بھی نیٹ ورک راؤنڈ ٹرپ سے زیادہ تیزی سے ڈیٹا پڑھتا ہے۔ ایک ایسے نمبر کے لیے جو بار بار بدلتا ہے لیکن فوری طور پر نہیں، یہ ایک بہترین ٹول ہے۔
اگر آپ APCu استعمال نہیں کر رہے، تو Redis یا Memcached بھی کام کر سکتے ہیں۔ اصول وہی رہتا ہے۔ ڈیٹا بیس سے ہاٹ ریڈ پاتھ (hot read path) کو الگ کر دیں۔
PHP، LiteSpeed، اور Cloudflare کو اسٹریم کرنے پر راضی کرنا
PHP چاہتا ہے کہ کام ختم کرے اور چلا جائے۔ ویب سرورز آؤٹ پٹ کو بفّر (buffer) کرنا اور ایک مکمل جواب فراہم کرنا چاہتے ہیں۔ SSE کو اس کے برعکس ضرورت ہوتی ہے: ایک ایسا کنکشن جو کھلا رہے اور جیسے ہی بائٹس (bytes) آئیں، انہیں فوراً بھیجتا رہے۔ احتیاط کے بغیر، آپ کی "اسٹریم" تیس سیکنڈ بعد ایک ہی بڑے بلاک کے طور پر پہنچے گی، جس سے مقصد ہی ختم ہو جائے گا۔
یہاں بتایا گیا ہے کہ TopVideoHub نے پائپ کو کیسے بلاک ہونے سے بچایا۔
آؤٹ پٹ بفّرنگ (output buffering) کو ختم کریں۔ SSE اسکرپٹ کے آغاز میں، PHP کی طرف سے فعال کی گئی بفّرنگ کی ہر تہہ (layer) کو غیر فعال کر دیں۔ اگر کوئی بفّر فعال ہے تو ob_end_flush() کو کال کریں، اور ہیڈرز بھیجنے کے بعد ob_implicit_flush(true) کے ذریعے امپلیسٹ فلشنگ (implicit flushing) کو بند کر دیں۔
پروکسیز کو پیچھے ہٹنے کا کہیں۔ X-Accel-Buffering: no ہیڈر بھیجیں۔ Nginx اسے مانتا ہے۔ LiteSpeed بھی اسے مانتا ہے۔ یہ اس بات کا اشارہ ہے کہ ریسپانس کو بفّر یا کسی کیش ایبل (cacheable) ڈھیر میں کمپریس نہیں کیا جانا چاہیے۔
مختصر دورانیہ مقرر کریں۔ ہر SSE کنکشن ایک PHP ورکر (worker) کو مصروف رکھتا ہے۔ TopVideoHub اسٹریمز کو 55 سیکنڈ تک محدود رکھتا ہے۔ جب ٹائمر ختم ہوتا ہے، تو سرور ایک آخری کمنٹ بھیجتا ہے، اسٹریم بند کر دیتا ہے، اور براؤزر خود بخود دوبارہ جڑ جاتا ہے۔ وہ دوبارہ کنکشن ایک نئے ورکر پر ہوتا ہے، جس سے کسی بھی ایک پروسیس کے ہمیشہ کے لیے مصروف رہنے کا خطرہ نہیں رہتا۔
کنکشن برقرار رکھنے کے لیے پنگ (Ping) کریں۔ ہر بیس سیکنڈ بعد ایک کمنٹ لائن بھیجیں—جیسے کہ : ping۔ SSE میں کمنٹس کو براؤزر کا میسج ہینڈلر نظر انداز کر دیتا ہے، لیکن یہ TCP کنکشن کو فعال (warm) رکھتے ہیں۔ لوڈ بیلنسرز اور CDNs اکثر تیس یا ساٹھ سیکنڈ کے بعد خاموش کنکشنز کو ختم کر دیتے ہیں۔ ایک معمولی سی نئی لائن (newline) آپ کو اس مشکل سے بچا سکتی ہے۔
صارف کے ٹیب کا احترام کریں۔ جب کوئی وزیٹر ٹیب کو منیمائز (minimize) یا چھپا دے، تو کنکشن ختم کر دیں۔ براؤزر میں visibilitychange پر نظر رکھیں اور eventSource.close() کو کال کریں۔ سرور کو بھی کلائنٹ کے ڈس کنیکٹ ہونے کا پتہ لگانا چاہیے اور لوپ کو ختم کر دینا چاہیے۔ PHP لوپ کے اندر connection_aborted() کے ذریعے چیک کر سکتا ہے۔ ان لوگوں کے لیے 'ghost connections' کے ذریعے ورکرز ضائع نہ کریں جو دس منٹ پہلے ہی جا چکے ہوں۔
سخت حد: PHP ورکرز
PHP میں SSE اپنی حدود کے بارے میں بالکل واضح ہے۔ ہر کھلا ہوا SSE کنکشن ایک PHP ورکر استعمال کرتا ہے۔ اگر آپ کے پول میں سو ورکرز ہیں، تو آپ کے پاس سو اسٹریمز ہیں۔ بس بات ختم۔ جب تک آپ Apache یا PHP-FPM پروسیس ماڈل کے اندر ہیں، اس کا کوئی async متبادل نہیں ہے۔ آپ pm.max_children کو ٹیون کر سکتے ہیں، لیکن میموری اور CPU اصل حد مقرر کرتے ہیں۔
یہ حد بہت جلد اثر دکھاتی ہے اگر آپ ورکرز کو عام پیج لوڈز، API کالز، اور اثاثوں (assets) کی جنریشن کے لیے بھی استعمال کر رہے ہوں۔ اپنے ورکر سیچوریشن (saturation) کی احتیاط سے نگرانی کریں۔ اگر آپ کا SSE اینڈ پوائنٹ قطار (queuing) میں لگنا شروع ہو جائے کیونکہ تمام ورکرز بیس منٹ کی اسٹریمز میں مصروف ہیں، تو آپ کی پوری سائٹ سست ہو جائے گی۔
جب اعداد و شمار مزید کام نہ آئیں، تو آگے بڑھیں۔ Go عام طور پر اگلا قدم ہوتا ہے، اگرچہ Rust، Node.js، یا Erlang بھی یہی کردار ادا کر سکتے ہیں۔ Go کی goroutines ہی اصل کلید ہیں۔ ایک goroutine صرف چند کلو بائٹس لیتی ہے۔ آپ معمولی ہارڈ ویئر پر بھی بغیر کسی مشکل کے دسیوں ہزاروں اسٹریمز برقرار رکھ سکتے ہیں۔ بنیادی منطق وہی رہتی ہے—کیش (cache) سے پڑھنا، ساکٹ (socket) پر لکھنا—لیکن رن ٹائم بھاری پروسیسز سے ہلکے پھلکے تھریڈز (threads) میں بدل جاتا ہے۔
تاہم، وہاں سے شروع نہ کریں۔ PHP آپ کو حیرت انگیز طور پر کافی دور تک لے جا سکتا ہے۔ پہلے پروڈکٹ کی اہمیت کو جانچ لیں۔ جب میٹرکس پیج ڈیٹا بیس کے اوور لوڈ ہونے کے بجائے ورکرز کی تھکن (exhaustion) دکھائے، تو سمجھ لیں کہ آپ کا اسٹیک اب کافی نہیں رہا۔ یہ ایک اچھی علامت ہے۔
خلاصہ
لائیو کاؤنٹرز محض ٹیکنالوجی کے بارے میں نہیں ہیں۔ یہ آپ کے ڈیٹا بیس کو آپ کے اپنے صارفین سے بچانے کے بارے میں ہیں۔ SSE سے شروع کریں کیونکہ یہ دیکھنے میں جتنی پیچیدہ لگتی ہے اس سے کہیں زیادہ سادہ ہے۔ اسٹریم اور ڈیٹا بیس کے درمیان بھرپور طریقے سے کیشنگ (caching) کریں تاکہ کنکشنز کی تعداد، کوئریز (queries) کی تعداد میں نہ بدل جائے۔ اپنے ورکرز کی حدود پر کڑی نظر رکھیں۔ اور سادہ طریقے سے آغاز کریں۔ PHP تب تک کافی ہے جب تک کہ وہ کافی نہ رہے، اور تب تک آپ کو بالکل معلوم ہوگا کہ آپ اسے دوبارہ کیوں لکھ رہے ہیں۔
آپ کے اگلے پروجیکٹ کے لیے:
- SSE کا استعمال تب کریں جب ڈیٹا ایک ہی سمت میں بہتا ہو، یعنی سرور سے براؤزر کی طرف۔
- ڈیٹا بیس کے سامنے ایک کیش لیئر لگائیں۔ ہر چند سیکنڈ میں ایک کوئری، ہزاروں کوئریز سے بہتر ہے۔
- SSE کنکشنز کو ایک منٹ سے کم پر محدود رکھیں اور براؤزر کو دوبارہ کنیکٹ ہونے دیں۔
- ٹیب چھپانے پر اسٹریمز بند کر دیں۔ فعال ورکرز کو غیر فعال (idle) کنکشنز کے لیے ضائع نہ کریں۔
- PHP ورکر کے استعمال کی نگرانی کریں۔ جب آپ اپنی حد تک پہنچ جائیں، تو اسٹریمنگ لیئر کو Go پر منتقل کر دیں۔
