کئی ہفتوں تک، Elevare Digital میں ایک cron job مقررہ وقت پر جاگتی، اپنی قطار (queue) چیک کرتی، اور کامیابی کا پیغام (log) درج کرتی۔ اس نے بالکل صفر ڈرافٹس منظور کیے۔ انیس مواد کے ٹکڑے انتظار میں پڑے رہے۔ ٹیم کو اس کا پتہ بعد میں چلا، جب یہ خاموش وقفہ ایک عجیب و غریب بات سے بڑھ کر ایک چھوٹے سے بیک لاگ (backlog) میں تبدیل ہو گیا۔ کچھ بھی کریش نہیں ہوا تھا۔ کوئی پیجنگ الرٹ (paging alert) نہیں بجا۔ سسٹم تکنیکی طور پر تو صحت مند تھا لیکن عملی طور پر مردہ ہو چکا تھا۔
یہ خودکار پائپ لائنز (autonomous pipelines) کا خاموش خوف ہے۔ جب آپ انسانی مداخلت کو ختم کر دیتے ہیں، تو آپ اس شخص کو بھی ہٹا دیتے ہیں جو یہ محسوس کر سکے کہ کچھ بھی نہیں ہو رہا۔
وہ پائپ لائن جو خود ہی چلتی رہی
Elevare Digital ایک مکمل طور پر خودکار مواد کے ورک فلو (content workflow) پر کام کرتی ہے۔ سافٹ ویئر ایجنٹس ڈرافٹس تیار کرتے ہیں۔ ایک شیڈول شدہ approver cron گیٹ کیپر کے طور پر کام کرتا ہے، جو ان ڈرافٹس کا جائزہ لیتا ہے اور منظور شدہ اشیاء کو براہ راست پبلشنگ کے لیے بھیج دیتا ہے۔ کوئی بھی انسان ہر بیچ (batch) کو منظور کرنے کے لیے ڈیش بورڈ نہیں کھولتا۔ اس کا پورا مقصد یہ ہے کہ مشین تھکا دینے والے کام سنبھال لے جبکہ ٹیم دوسرے مسائل پر توجہ دے سکے۔
اس ماڈل کے تحت، بھروسہ آپ کا بنیادی انٹرفیس بن جاتا ہے۔ آپ شیڈولر (scheduler) پر بھروسہ کرتے ہیں کہ وہ وقت پر چلے گا۔ آپ جاب کے چلنے پر بھروسہ کرتے ہیں۔ آپ ایگزٹ کوڈ (exit code) پر بھروسہ کرتے ہیں۔ جب لاگز (logs) میں 200 OK رسپانسز کی مستقل دھڑکن نظر آتی ہے، تو آپ فرض کر لیتے ہیں کہ کام ہو رہا ہے۔ کئی ہفتوں تک، وہ دھڑکن بالکل درست تھی۔ cron ہر بار وقت پر چلا۔ لیکن اس نے کبھی اصل کام ہی نہیں کیا۔
انیس ڈرافٹس اور کوئی الارم نہیں
یہ انکشاف اتفاقی تھا۔ آخر کار کسی نے محسوس کیا کہ پبلشنگ کی قطار (queue) خاموش ہو گئی ہے، یا شاید انہوں نے کسی ڈاؤن اسٹریم میٹرک (downstream metric) کو چیک کیا اور وہاں کوئی تبدیلی نہیں دیکھی۔ انہیں جو ملا وہ انیس ڈرافٹس کا ذخیرہ تھا جو بالکل غیر چھوئے پڑے ہوئے تھے۔ approver فرض شناسی سے چل رہا تھا، ہر روز کامیابی کا لاگ درج کر رہا تھا، لیکن ان میں سے ایک کو بھی پراسیس نہیں کیا تھا۔
ایک دستی ورک فلو (manual workflow) میں، ایک انسانی ریویو کرنے والا پہلے ہی دن خالی ان باکس یا زیر التواء اشیاء کے ڈھیر کو دیکھ لیتا۔ خودکار ورژن میں، سرگرمی کی عدم موجودگی بالکل کام کی عدم موجودگی جیسی لگ رہی تھی۔ cron کا کوئی مینیجر نہیں تھا جسے وہ مایوس کر سکے۔ وہ بس وقت پر کام شروع کرتا اور جلدی گھر چلا جاتا۔
دو بگ، ایک خالی نتیجہ
اس ناکامی کے دو اسباب تھے۔ ان میں سے کوئی بھی سنٹیکس ایرر (syntax error)، ٹائم آؤٹ (timeout)، یا ڈیپینڈینسی آؤٹج (dependency outage) نہیں تھا۔ دونوں سیمنٹک غلطیاں (semantic mistakes) تھیں جنہوں نے کوئری انجن (query engine) کی نظر میں انیس درست روز (rows) کو صفر کر دیا۔
پہلا، ٹائپ کا فرق (type mismatch)۔ ڈرافٹس تیار کرنے والے ایجنٹ نے ریکارڈز کو article کے طور پر ٹیگ کیا۔ approver cron نے خاص طور پر thread ٹائپس کے لیے کوئری کی۔ یہ اس قسم کا فرق ہے جو اس وقت ہوتا ہے جب پروڈیوسرز (producers) اور کنزیومرز (consumers) الگ الگ راستوں پر ترقی کر رہے ہوں۔ ایک ٹیم—یا ایک ایجنٹ—نے فیصلہ کیا کہ آؤٹ پٹ ایک آرٹیکل ہے۔ دوسرے نے کنزیومر کو اس مفروضے پر لکھا کہ وہ تھریڈز (threads) کو قبول کرے گا۔ کسی بھی ٹائپ سسٹم نے کمپائل ٹائم ایرر (compile-time error) نہیں دیا کیونکہ یہ غالباً غیر رسمی اسٹرنگ ٹیگز (string tags) تھے، شاید JSON فیلڈز یا غیر نافذ شدہ varchar ویلیوز۔ ڈیٹا بیس کو کوئی مماثل ریکارڈ نہیں ملا اور اس نے ایک خالی سیٹ واپس کر دیا۔ انجن کے لیے یہ کوئی ایرر کی صورتحال نہیں ہے۔ یہ ایک غلط سوال کا درست جواب ہے۔
دوسرا، approver کی کوئری میں ایک inner join نے خاموشی سے تمام روز (rows) کو نگل لیا۔ اگر کوئری نے ڈرافٹس ٹیبل کو کسی دوسرے ٹیبل کے ساتھ جوڑا—شاید میٹا ڈیٹا، اسٹیٹس فلیگز، یا روٹنگ رولز کے لیے—اور جوائن کی شرط (join condition) ناکام ہو گئی، تو inner join بالکل اسی طرح کام کیا جیسا کہ اسے ڈیزائن کیا گیا تھا۔ اس نے غیر مماثل روز کو نکال دیا۔ رزلٹ سیٹ میں کوئی یتیم (orphan) روز ظاہر نہیں ہوئے۔ کسی null ویلیو نے مسئلہ ظاہر نہیں کیا۔ انیس ڈرافٹس چھلنی سے گزرتے پانی کی طرح کوئری سے گزر گئے، اور ایپلی کیشن لیئر کو ایک بالکل صاف ستھری، خالی فہرست موصول ہوئی۔
چونکہ کوئری نے کوئی روز واپس نہیں کیے، اس لیے فنکشن کامیابی سے ختم ہو گیا۔ کوئی استثنیٰ (exception) سامنے نہیں آیا۔ HTTP رسپانس 200 OK تھا۔ cron نے کامیابی کا لاگ درج کیا اور دوبارہ سو گیا۔
'پروسیسڈ زیرو' کا جال
مسئلہ یہاں ہے۔ ایک کیو پر مبنی سسٹم (queue-based system) میں، کنزیومر کو اکثر پراسیس کرنے کے لیے صفر روز ملتے ہیں۔ کیو خالی ہو جاتی ہے۔ ورکر جلدی کام ختم کر لیتا ہے۔ لاگ میں processed: 0 لکھا آتا ہے اور ٹیم اسے اچھی خبر سمجھتی ہے: ہم ڈیمانڈ کے مطابق کام کر رہے ہیں۔ یہ ایک صحت مند حالت ہے۔
لیکن processed: 0 دو بالکل مختلف حقیقتوں کو ظاہر کرتا ہے:
- صحت مند حالت (Healthy state): صفر پراسیس ہوئے کیونکہ زیر التواء کچھ نہیں ہے۔ کیو خالی ہے۔ سسٹم ڈیزائن کے مطابق فارغ ہے۔
- خراب حالت (Broken state): صفر پراسیس ہوئے کیونکہ کنزیومر کام کو دیکھ نہیں پا رہا۔ کیو میں انیس روز موجود ہیں۔ سسٹم اندھا ہے، فارغ نہیں۔
کیو کی گہرائی (queue depth) پر آزادانہ چیک کے بغیر، یہ دونوں حالتیں ایک جیسی ٹیلی میٹری (telemetry) فراہم کرتی ہیں۔ وہ ڈیش بورڈز میں ایک جیسی نظر آتی ہیں، لاگ ایگریگیٹرز (log aggregators) میں ایک جیسی لگتی ہیں، اور PagerDuty میں ایک جیسی خاموشی پیدا کرتی ہیں۔ آپ نے ایسی مانیٹرنگ حکمت عملی بنائی ہے جو اس وقت کام کرتی ہے جب ورکر چیختا ہے، نہ کہ اس وقت جب وہ اصل کام کے ڈھیر کے پاس سے خاموشی سے گزر جاتا ہے۔
فرق کو ختم کرنا
Elevare Digital نے اپنی نگرانی (monitoring) کے طریقہ کار کو تبدیل کر کے اس مسئلے کو حل کیا۔ انہوں نے صرف error rates اور success statuses پر انحصار کرنا چھوڑ دیا۔ اس کے بجائے، انہوں نے دستیاب کام اور مکمل شدہ کام کے درمیان فرق (gap) پر الرٹ (alert) کرنا شروع کر دیا۔
اب ہر بیچ (batch) کے بعد، وہ ایک سادہ invariant check چلاتے ہیں:
- اگر processed 0 ہو اور pending rows 0 سے زیادہ ہوں، تو high severity alert جاری کریں۔
یہ اصول جان بوجھ کر وجہ سے آزاد (agnostic) رکھا گیا ہے۔ اسے اس سے فرق نہیں پڑتا کہ کام کیوں نہیں ہوا—چاہے وہ کوئی غلط فلٹر ہو، ٹوٹا ہوا join ہو، یا غلط ٹائپ شدہ enum string ہو۔ اسے صرف اس بات سے مطلب ہے کہ کام موجود ہے لیکن کوئی کام مکمل نہیں ہوا۔ یہ نگرانی کو "کیا پروسیس نے شکایت کی؟" سے بدل کر "کیا کام آگے بڑھا؟" پر لے آتا ہے۔
اس کی حمایت کے لیے، وہ queue depth کو ایک اہم میٹرک (first-class metric) کے طور پر استعمال کرتے ہیں، جسے وقت کے ساتھ ٹریک کیا جاتا ہے، نہ کہ صرف کبھی کبھار کے چیک کے طور پر۔ اگر producer مسلسل روز (rows) شامل کر رہا ہے جبکہ consumer مسلسل کامیابی (success) رپورٹ کر رہا ہے، تو depth کا رجحان ایک واضح ثبوت (smoking gun) بن جاتا ہے۔ ایک ساکن snapshot جھوٹ بول سکتا ہے، لیکن بڑھتا ہوا backlog کبھی جھوٹ نہیں بولتا۔
خود مختار نظاموں (Autonomous Systems) کے لیے اسباق
Elevare کا واقعہ ان تمام لوگوں کے لیے کچھ عملی اصول فراہم کرتا ہے جو خودکار پائپ لائنز (hands-off pipelines) چلا رہے ہیں۔
Scanned rows کو processed rows سے الگ لاگ (log) کریں۔ ہو سکتا ہے کہ consumer ایسی query چلائے جو چالیس روز (rows) کو چھوئے، لیکن غلط معیار کی وجہ سے ان سب کو فلٹر کر دے، اور پھر processed: 0 رپورٹ کرے۔ اگر آپ صرف حتمی تعداد کو لاگ کرتے ہیں، تو آپ اس پوشیدہ عمل (ghost interaction) کو نظر انداز کر دیں گے۔ Scanned-rows میٹرک یہ ظاہر کرتا ہے کہ ورکر آیا، کام کو دیکھا، اور الجھن میں واپس چلا گیا۔ Scanned اور processed کے درمیان یہ فرق اکثر آپ کا ابتدائی ترین اشارہ ہوتا ہے۔
Queue depth کو time-series کے طور پر ٹریک کریں۔ ایک قطار (queue) جو عارضی طور پر خالی ہو، وہ ٹھیک ہے۔ لیکن ایک قطار جو مسلسل بڑھتی جا رہی ہو جبکہ ورکرز "green" (صحیح) دکھا رہے ہوں، وہ ٹھیک نہیں ہے۔ Depth کو consumer throughput کے مقابلے میں ظاہر کریں۔ جب یہ دونوں الگ سمتوں میں جائیں، تو فوری طور پر تحقیقات کریں، چاہے ہر ہیلتھ چیک پاس ہی کیوں نہ ہو رہا ہو۔
Consumers کا ٹیسٹ صرف mocks کے بجائے اصل producer output کے ساتھ کریں۔ Mocked ڈیٹا کے ساتھ کیے گئے unit tests ٹیسٹر کے مفروضات پر مبنی ہوتے ہیں۔ اگر mock factory thread اقسام پیدا کرتی ہے اور consumer بھی thread اقسام کی توقع رکھتا ہے، تو آپ کے ٹیسٹ تو پاس ہو جائیں گے لیکن پروڈکشن میں ناکامی ہوگی۔ ایسے integration tests چلائیں جو producer کے آؤٹ پٹ سے اصل ریکارڈ حاصل کریں۔ اس بات کو یقینی بنائیں کہ consumer واقعی وہ دیکھ سکے جو producer لکھ رہا ہے۔
Data types اور enum values کو معاہدوں (contracts) کے طور پر سمجھیں۔ JSON blobs میں غیر واضح string tags تب تک آسان رہتے ہیں جب تک وہ ناکامی کے پوشیدہ مقامات نہ بن جائیں۔ Schemas کو واضح طور پر بیان کریں۔ Constants کو شیئر کریں۔ Producer اور consumer کے درمیان کے مقام پر payloads کو ویلیڈیٹ کریں۔ اگر معاہدہ ٹوٹتا ہے، تو سسٹم کو سرحد (boundary) پر واضح طور پر ناکام ہونا چاہیے، نہ کہ خاموشی سے کسی WHERE clause کے اندر۔
اصل سبق
خود مختار نظام انسانوں کی طرح ناکام نہیں ہوتے۔ وہ بیماری کی وجہ سے چھٹی نہیں مانگتے، ہر بار exceptions نہیں پھینکتے، یا واضح crash dumps نہیں چھوڑتے۔ وہ 200 OK واپس کرتے ہیں اور انوینٹری کو گلنے سڑنے کے لیے چھوڑ دیتے ہیں۔ اگر آپ کے الرٹس صرف چیخ و پکار (screams) کا انتظار کرتے ہیں، تو آپ سب سے مہنگے ناکامیوں کو نظر انداز کر دیں گے—وہ ناکامیاں جہاں سب کچھ ٹھیک نظر آتا ہے لیکن کوئی کام نہیں ہو رہا ہوتا۔
اپنی observability کو اس طرح ڈیزائن کریں کہ وہ فرق (gap) پر نظر رکھے۔ داخل ہونے والے کام کا موازنہ نکلنے والے کام سے کریں۔ جب یہ دونوں مزید مطابقت نہ رکھیں، تو فرض کر لیں کہ مشین آپ سے جھوٹ بول رہی ہے۔ کیونکہ کبھی کبھی، ایک مکمل کامیاب لاگ (success log) اس نظام کی واحد علامت ہوتی ہے جو مکمل طور پر اندھا ہو چکا ہو۔
