ایک صارف ٹکٹ کھولتا ہے: موبائل ایپ سست چل رہی ہے۔ آپ اپنے ڈیش بورڈز چیک کرتے ہیں۔ CPU کا استعمال مستحکم ہے۔ غلطیوں کی شرح صفر ہے۔ آپ کے APM کے اشارے اطمینان بخش سبز ہیں۔ ہر نظر آنے والے پیمانے کے مطابق بیک اینڈ صحت مند ہے۔ لیکن صارف غلط نہیں ہے۔ سستی حقیقی ہے، اور یہ ایک Android ڈیوائس اور آپ کے سرور کے درمیان ایک طویل، تاریک راہداری میں کہیں ہو رہی ہے۔
مسئلہ یہ ہے کہ زیادہ تر observability ٹولز ایپلیکیشن کی حد پر ہی رک جاتے ہیں۔ وہ اس چیز کو ناپتے ہیں جو آپ کے فریم ورک کے ریکویسٹ کو پارس کرنے کے بعد ہوتی ہے۔ وہ ڈیٹا بیس کوئریز، کیش ہٹس (cache hits)، اور ڈاؤن اسٹریم سروس کالز کو ٹریک کرتے ہیں۔ وہ جو چیز مس کر دیتے ہیں وہ خود ریکویسٹ کا میکانزم ہے: وہ وقت جو ایک OkHttp کال Android HTTP اسٹیک کے اندر گزارتی ہے، ایک غیر مستحکم موبائل نیٹ ورک کے ذریعے گزرنا، TLS ہینڈ شیک (handshake) مذاکرات، اور وہ خاموش انتظار جو آپ کے کوڈ کے چلنے سے پہلے kernel کیوز (queues) کے اندر ہوتا ہے۔ یہ خلاز (gaps) ملی سیکنڈز—یا پورے سیکنڈز—نگل لیتے ہیں جبکہ آپ کا APM خاموش رہتا ہے۔
انکرپشن اس اندھے پن کو مکمل کر دیتی ہے۔ جدید Android ایپس ہر چیز کو BoringSSL کے ذریعے روٹ کرتی ہیں۔ جب تک کوئی پیکٹ kernel نیٹ ورک اسٹیک تک پہنچتا ہے، HTTP ہیڈرز انکرپٹ ہو چکے ہوتے ہیں۔ ایک معیاری tcpdump یا نیٹ ورک ہک صرف غیر واضح (opaque) TLS ریکارڈز دیکھ سکتا ہے۔ آپ دیکھ سکتے ہیں کہ ٹریفک بہہ رہی ہے، لیکن آپ اسے پڑھ نہیں سکتے۔ آپ یقیناً کسی مخصوص kernel-level TCP سیگمنٹ کو کسی مخصوص صارف کی API کال سے نہیں جوڑ سکتے۔ وہ trace context جس کی آپ کو ضرورت ہے، وہ ciphertext کے اندر قید ہے۔
eBPF اس صورتحال کو بدل دیتا ہے کیونکہ یہ آپ کو اپنے ایپلیکیشن کوڈ کو تبدیل کیے بغیر سسٹم کو اندر سے باہر کی طرف انسٹرومنٹ (instrument) کرنے کی اجازت دیتا ہے۔ اپنی ایپ سے اس کی اپنی لیٹنسی (latency) رپورٹ کرنے کے کہنے کے بجائے، آپ براہ راست kernel اور اہم userspace لائبریریوں کے ساتھ چھوٹے پروگرام منسلک کرتے ہیں۔ یہ پروگرام واقعات کے ہوتے ہی انہیں مشاہدہ کرتے ہیں، جو آپ کو چاہیے اسے نکالتے ہیں، اور اسے ایک ring buffer میں بھیج دیتے ہیں۔ آپ کے Android APK کے اندر ایک ہلکے پھلکے ہیڈر کے علاوہ کوئی SDK bloat نہیں ہوتا، اور کوئی instrumentation agent آپ کے بیک اینڈ کلاسز کو دوبارہ نہیں لکھ رہا ہوتا ہے۔
چار حصوں والا سیٹ اپ
اس پائپ لائن کو بنانے میں مشاہدے کے چار مختلف لیئرز شامل ہیں۔
1. traceparent اینکر۔ Android ڈیوائس پر، آپ ایک OkHttp انٹرسیپٹر (interceptor) شامل کرتے ہیں جو ہر آؤٹ باؤنڈ ریکویسٹ میں W3C traceparent ہیڈر شامل کرتا ہے۔ موبائل طرف سے صرف یہی ایک تبدیلی درکار ہے، اور یہ بہت معمولی ہے۔ یہ ہیڈر انکرپٹڈ پے لوڈ کے اندر آپ کے بیک اینڈ تک سفر کرتا ہے۔ چونکہ یہ HTTP لیئر کے اندر ہوتا ہے، اس لیے یہ اس plaintext کے اندر محفوظ رہتا ہے جسے TLS انجن آخر کار ظاہر کرتا ہے۔
2. TCP آمد کا وقت۔ بیک اینڈ ہوسٹ پر، آپ نیٹ ورک انٹرفیس کے ساتھ منسلک eBPF Traffic Control ہکس (hooks) کا استعمال کرتے ہیں۔ جیسے ہی انفرادی TCP سیگمنٹس پہنچتے ہیں، یہ پروگرام چلتے ہیں۔ وہ کنارے (edge) پر ہی سیکوئنس نمبرز اور ٹائم اسٹیمپ (timestamps) کو کیپچر کرتے ہیں۔ اب آپ کو بالکل درست معلوم ہوتا ہے کہ بٹس (bits) وائر سے کب نکلے اور آپ کی مشین میں داخل ہوئے، اس سے بہت پہلے کہ آپ کی ایپلیکیشن ایک بھی بائٹ پڑھے۔
3. ڈیکرپشن پروبز۔ آپ کا بیک اینڈ OpenSSL یا BoringSSL کا استعمال کرتے ہوئے TLS کو ختم (terminate) کرتا ہے۔ یہیں سے آرکیٹیکچر دلچسپ ہو جاتا ہے۔ uprobes—ڈائنامک userspace پروبز—کا استعمال کرتے ہوئے، آپ TLS لائبریری کے اندر SSL_write اور SSL_read کے ساتھ منسلک ہو جاتے ہیں۔ جیسے ہی plaintext ڈیٹا انکرپشن انجن سے گزرتا ہے، یہ فنکشنز فوری طور پر چلتے ہیں۔ آپ کا eBPF پروگرام اس ڈیکرپٹڈ بفر کو پڑھتا ہے، traceparent ہیڈر کو تلاش کرتا ہے، اور اسے نکال لیتا ہے۔ اب kernel کے پاس ایک خام TCP فلو اور ایک مخصوص موبائل ریکویسٹ کے درمیان براہ راست میپنگ موجود ہے، بغیر اس کے کہ آپ کسی کسٹم ٹول میں سرٹیفکیٹس یا کیز (keys) کو ہینڈل کریں۔
4. Kernel کیوئیگ۔ ڈیٹا ڈیکرپٹ ہونے اور تیار ہونے کے بعد بھی، ہو سکتا ہے کہ آپ کی ایپلیکیشن اسے فوری طور پر استعمال نہ کرے۔ آپ socket buffers اور شیڈولنگ ایونٹس کو سنبھالنے والے متعلقہ kernel فنکشنز کے ساتھ kprobes منسلک کرتے ہیں۔ یہ کیوئیگ لیٹنسی (queueing latency) کو ناپتا ہے: وہ وقت جو ریکویسٹس kernel land میں انتظار میں گزارتی ہیں کیونکہ آپ کا پروسیس CPU کے لیے مقابلہ کر رہا ہے یا محض ابھی تک read() کو کال نہیں کیا ہے۔
یہ چاروں سگنل ذرائع ایک eBPF ring buffer میں واقعات لکھتے ہیں۔ userspace میں چلنے والا ایک sidecar پروسیس اس بفر کو خالی کرتا ہے، traceparent ID کے ذریعے واقعات کو آپس میں جوڑتا ہے، اور ہر ریکویسٹ کے لیے ایک واحد، مربوط ٹائم لائن دوبارہ تعمیر کرتا ہے۔ جو پہلے غیر منسلک kernel شور کا بکھراؤ تھا، اب ایک منظم ٹریس (trace) بن جاتا ہے۔
مکمل راستہ پڑھنا
جمع شدہ آؤٹ پٹ کو عام طور پر فلیم گراف (flame graph) یا ایک منظم span tree کے طور پر دکھایا جاتا ہے جو لیٹنسی کو چار ٹھوس حصوں میں تقسیم کرتا ہے:
- Network transit time: اینڈرائیڈ ریڈیو کی جانب سے درخواست کا آخری بائٹ بھیجنے سے لے کر بیک اینڈ NIC کے اسے وصول کرنے تک کا دورانیہ۔ سیلولر اتار چڑھاؤ (volatility) کا اصل مرکز یہی ہے۔
- TLS handshake duration: انکرپٹڈ ٹنل کے لیے مذاکرات (negotiating) میں صرف ہونے والا وقت۔ غیر مستحکم نیٹ ورکس پر، یہ وقت اصل ڈیٹا ٹرانسفر کے مقابلے میں بہت زیادہ ہو سکتا ہے۔
- Kernel queueing latency: سیگمنٹ کے پہنچنے کے بعد لیکن یوزر اسپیس (userspace) کے استعمال سے پہلے کرنل بفرز اور شیڈیولر کیوز میں گزارا جانے والا وقت۔
- Application processing time: وہ حصہ جو آپ کا بیک اینڈ فریم ورک اور بزنس لاجک اصل میں استعمال کرتا ہے۔
آپ کو معلوم ہو سکتا ہے کہ 800ms کی موبائل درخواست میں سے صرف 40ms آپ کے JSON پارسر کے اندر صرف ہوتے ہیں۔ مزید 200ms ایک کمزور (lossy) کنکشن پر رکے ہوئے TLS ہینڈ شیک میں ضائع ہو جاتے ہیں۔ مزید 300ms ایک اوور سبسکرائبڈ ہوسٹ پر کرنل بیک لاگ میں غائب ہو جاتے ہیں۔ آپ کا APM صرف 40ms رپورٹ کر رہا تھا۔ کرنل لیول کی ویزیبلٹی کے بغیر، آپ مکمل طور پر غلط چیز کو بہتر (optimize) کرنے کی کوشش کر رہے ہوتے۔
وہ اوور ہیڈ (Overhead) جو حقیقت میں اسکیل ہوتا ہے
روایتی APM ایجنٹس آپ کے لینگویج رن ٹائم کے اندر کالز کو انٹرسیپٹ (intercept) کر کے اپنی ویزیبلٹی حاصل کرتے ہیں۔ وہ میتھڈز کو ریپ (wrap) کرتے ہیں، اسپین آبجیکٹس (span objects) مختص کرتے ہیں، اور آپ کے پروسیس ہیپ (process heap) کے اندر ٹیلی میٹری ڈیٹا کو سیریلائز (serialize) کرتے ہیں۔ لوڈ کے دوران، یہ اوور ہیڈ تیزی سے بڑھتا جاتا ہے۔ سیریلائزیشن کی لاگت بڑھ جاتی ہے۔ گربیج کلیکشن (Garbage collection) کا دباؤ بڑھ جاتا ہے۔ آپ اپنی ایپلی کیشن کے اپنے وسائل کی قیمت پر ویزیبلٹی حاصل کر رہے ہوتے ہیں۔
eBPF پروگرامز ایک کرنل ورچوئل مشین کے اندر چلتے ہیں اور انہیں نیٹیو مشین انسٹرکشنز میں JIT-کمپائل کیا جاتا ہے۔ لوڈ ہونے سے پہلے ایک ویری فائر ان کی حفاظت کی جانچ کرتا ہے۔ ہر پروب (probe) راستے میں مائیکرو سیکنڈز کا اضافہ کرتا ہے، ملی سیکنڈز کا نہیں۔ ایونٹس کو میچ کرنے اور گراف بنانے کا بھاری کام سائیڈ کار (sidecar) میں ہوتا ہے، جو آپ کے سروس کے ہاٹ پاتھ (hot path) سے باہر ہوتا ہے۔ آپ ہیپ الیکیشنز (heap allocations) کو نہیں بڑھا رہے ہوتے۔ آپ ریکویسٹ ہینڈلنگ کے دوران سیریلائزیشن ٹیکس (serialization taxes) نہیں لگا رہے ہوتے۔ ان سروسز کے لیے جو فی سیکنڈ ہزاروں درخواستیں بھیجتی ہیں، یہ فرق اہمیت رکھتا ہے۔
چلانے کے لیے کیا ضروری ہے
یہ کوئی جادوئی حل (magic bullet) نہیں ہے، اور نہ ہی یہ کوئی مینیجڈ SaaS ہے جسے آپ صرف آن کر سکیں۔ آپ کو جدید eBPF سپورٹ کے ساتھ ایک بیک اینڈ کرنل کی ضرورت ہے، بشمول BTF ٹائپ انفارمیشن تاکہ آپ کے پروبز محفوظ طریقے سے کرنل اسٹرکچرز میں کام کر سکیں۔ آپ کی TLS لائبریری کو ایسے سمبلز (symbols) فراہم کرنے چاہئیں جنہیں uprobes ٹارگٹ کر سکیں؛ اگر آپ ایک اسٹیٹک لنکڈ بائنری بھیج رہے ہیں جس میں OpenSSL کا اسٹرپڈ (stripped) یا بہت زیادہ کسٹمائزڈ ورژن استعمال ہوا ہے، تو آپ کو اس کا خیال رکھنا ہوگا۔ آپ کو یہ بھی تصدیق کرنے کی ضرورت ہے کہ آپ کا traceparent ہیڈر موبائل کلائنٹ اور TLS ٹرمینیشن پوائنٹ کے درمیان موجود کسی بھی پراکسی یا ایج گیٹ وے (edge gateway) سے گزرنے کے بعد بھی برقرار رہتا ہے۔
لیکن ان ٹیموں کے لیے جو "موبائل سلو ہے" والے ٹکٹس سے تھک چکی ہیں جن کا روٹ کاز اینالیسس (root-cause analysis) کرنا مشکل ہوتا ہے، یہ آرکیٹیکچر محض اندازوں کی جگہ ٹھوس سگنلز (hard signals) فراہم کرتا ہے۔ آپ نیٹ ورک کے حالات کے بارے میں ق
