الرٹس جو ہر سرور پر نہیں، ایک بار چلیں
ہمارے operational الرٹس میں سے ایک، "low credits" جو کسی builder کی پبلش شدہ ایپ کے لیے ہے، ہمارے پاس زیادہ سے زیادہ دن میں ایک بار پہنچنا چاہیے۔ ایک دوپہر یہ پانچ بار آیا۔ وجہ یہ تھی کہ API کی ہر کاپی اپنی memory میں یاد رکھتی تھی کہ اس نے کون سے الرٹ بھیجے ہیں، تو دو سرورز نے اسے دو بار بھیجا، اور ہر ڈپلائے نے وہ memory مٹا دی اور انہیں دوبارہ بھیجنے دیا۔ یہی نمونہ ایک background job میں بھی تھا جو اپنا کام دو بار کر رہا تھا۔ اب دونوں ایک shared store کے ذریعے ہم آہنگ ہوتے ہیں، اور اگر وہ store دستیاب نہ ہو تو دونوں محفوظ سمت میں ناکام ہوتے ہیں۔
علامت
Mythex اپنی ٹیم کو الرٹ بھیجتا ہے جب کسی چیز پر نظر ڈالنی ہو: کوئی service بند ہو، کوئی job رک جائے، یا کسی builder کی credit balance کم ہو جبکہ اس کی ایپ پبلش ہو۔ ہر الرٹ کی ایک dedupe window ہوتی ہے، یعنی وہ مدت جس میں ایک ہی الرٹ صرف ایک بار بھیجا جاتا ہے۔ low-credits الرٹ کے لیے یہ window 24 گھنٹے ہے۔
ایک دوپہر میں اسی الرٹ کی پانچ کاپیاں بتاتی تھیں کہ window قائم نہیں رہ رہی۔ اس سے بھی بری بات یہ تھی کہ وہ جوڑوں میں آ رہی تھیں، تقریباً 11 منٹ کے وقفے سے۔
ایسا کیوں ہوا
ہمارے alerts package کی dedupe memory میں رہتی تھی: ایک جدول، "alert key → آخری بار کب بھیجا گیا"، چلتے ہوئے process کے اندر۔ یہ اس وقت کام کرتا ہے جب ایک ہی process ہو جو کبھی دوبارہ شروع نہ ہو۔ production میں نہ ایک process ہے نہ یہ شرط:
- دو API replicas۔ production میں API کی دو کاپیاں چلتی ہیں۔ ہر ایک کا اپنا جدول تھا، تو ہر ایک نے الرٹ ایک بار بھیجا۔ یہی جوڑے تھے۔
- ڈپلائے۔ ہر ڈپلائے دونوں replicas کو خالی جدولوں والے نئے processes سے بدل دیتا ہے۔ ڈپلائے کے بعد الرٹ ایسے دوبارہ گیا جیسے کبھی بھیجا ہی نہ گیا ہو۔
ہر جوڑے کے دو الرٹس کے درمیان تقریباً 11 منٹ کا وقفہ اس job سے آ رہا تھا جو یہ الرٹ اٹھاتی ہے۔ وہ job دونوں replicas پر چلتی ہے اور ایک lock لیتی ہے، تو replicas باری باری یہ lock جیتتے ہیں، اور ہر ایک اپنی باری پر الرٹ اٹھاتا تھا۔
اسی کوڈ میں ایک دوسری، خاموش خرابی بھی تھی۔ جدول کو لامحدود بڑھنے سے روکنے کے لیے، جب اس میں 500 keys ہو جاتیں تو یہ خود کو صاف کرتا تھا، ہر وہ entry حذف کر کے جو 15 منٹ سے پرانی ہو، چاہے اس کی window کچھ بھی ہو۔ بوجھ کے تحت، 24 گھنٹے کی dedupe ایک گھنٹے کے صرف چوتھائی تک چل سکتی تھی۔
اصلاح
ہر الرٹ کا ایک shared ریکارڈ
اب API الرٹ بھیجنے سے پہلے اس کی dedupe key کو Redis میں claim کرتا ہے، ایک shared in-memory store جس سے دونوں replicas بات کرتے ہیں اور جو API کے دوبارہ شروع ہونے پر ختم نہیں ہوتا۔ claim ایک ہی operation ہے: "یہ key صرف اسی صورت میں سیٹ کرو جب وہ موجود نہ ہو، اور window کے بعد اسے expire کر دو"۔ جو replica پہلے claim کرے وہ الرٹ بھیجتا ہے؛ دوسرا key کو لیا ہوا پا کر خاموش رہتا ہے۔ ڈپلائے کے بعد کا نیا process دیکھتا ہے کہ key ابھی بھی موجود ہے۔
کلید اور window اسی قاعدے سے آتے ہیں جو alerts package استعمال کرتا ہے، جو اب دونوں کے درمیان مشترک ہے، تاکہ دونوں تہیں اس پر متفق رہیں کہ "ایک ہی الرٹ" کیا ہے۔
ناکامی پر کھلا رہنا، بند نہیں
سب سے اہم فیصلہ یہ تھا کہ Redis تک رسائی نہ ہو تو کیا کیا جائے۔ ہم نے الرٹ پھر بھی بھیجنے کا فیصلہ کیا۔ اگر Redis بند ہو، یا 1.5 سیکنڈ میں جواب نہ دے، تو الرٹ چلا جاتا ہے۔ ڈپلیکیٹ الرٹ شور ہے۔ گرا ہوا الرٹ یہ ہے کہ کسی کو بتایا ہی نہ گیا۔
سب سے سنگین الرٹس، P0، dedupe سے بالکل نہیں روکے جاتے۔
ہر entry اپنی window رکھتی ہے
alerts package کی اپنی in-memory dedupe میں، اب ہر entry یہ محفوظ کرتی ہے کہ اس کی window کب ختم ہوتی ہے، اور صفائی صرف ان entries کو ہٹاتی ہے جن کی window ختم ہو چکی ہو۔ 24 گھنٹے کی key 24 گھنٹے تک رہتی ہے، چاہے کتنے ہی دوسرے الرٹس آئیں۔
وہی بگ، کہیں اور
یہی نمونہ background کے ایک کام کو بھی دوگنا کر رہا تھا: ایک reconciliation job جو ہر workspace کی status کا ہمارا ریکارڈ ہمارے sandbox provider کے ساتھ ہم آہنگ رکھتی ہے۔ ہر پاس اکاؤنٹ کے ہر sandbox کو فہرست کرتا ہے، production میں ہزاروں، اور تقریباً پانچ سیکنڈ لگتے ہیں، پلس ہر چلتا یا رکا ہوا پروجیکٹ۔ دونوں replicas اسے ہر دو منٹ میں چلاتے تھے۔
اس کے writes idempotent ہیں، یعنی دو بار چلانے کا نتیجہ ایک بار چلانے جیسا ہی ہے، تو کچھ نہیں ٹوٹا۔ بس کام دوگنا تھا۔ اب ہر دو منٹ کی window کو اسی طرح Redis میں claim کیا جاتا ہے جیسے ہمارے usage meters اپنی window claim کرتے ہیں، اور صرف وہی replica پاس چلاتا ہے جس نے claim جیتا ہو۔ اگر Redis تک رسائی نہ ہو تو پاس پھر بھی چلتا ہے: کام دو بار ہونا نقصان دہ نہیں، نہ ہونا نقصان دہ ہے۔
ہم نے اسے کیسے جانچا
Tests اصل سیٹ اپ کا ماڈل بناتے ہیں۔ وہ alert module کو دو بار لوڈ کرتے ہیں، جیسے دو الگ "replicas"، دونوں کو ایک shared store کی طرف اشارہ کرتے ہیں، ہر ایک سے وہی الرٹ اٹھاتے ہیں، پھر ایک تیسری تازہ کاپی لوڈ کرتے ہیں جو "ڈپلائے کے بعد" کی نمائندگی کرتی ہے، اور وہی الرٹ دوبارہ اٹھاتے ہیں۔ ٹھیک ایک الرٹ بھیجا جاتا ہے۔ دوسرے tests جانچتے ہیں کہ ایک مختلف builder کا الرٹ پھر بھی جاتا ہے، کہ P0 دونوں replicas سے بھیجا جاتا ہے، اور کہ store بند ہو یا timeout سے زیادہ دیر لے تب بھی الرٹ بھیجا جاتا ہے۔ صفائی کی اصلاح کا اپنا test ہے: 24 گھنٹے کی key 20 منٹ بعد آنے والے 600 دوسرے الرٹس کے بعد بھی باقی رہتی ہے۔
reconciliation job کے لیے، ایک ہی وقت میں دو replicas کا ٹک کرنا، اور اسی window میں ایک تیسرا ٹک، ایک ہی listing پیدا کرتا ہے۔
ہم نے اس سے کیا سیکھا
- In-memory state ہر process کا اپنا ہوتا ہے۔ جو چیز servers کے درمیان سچ رہنی چاہیے، جیسے "ایک بار بھیجا" یا "ایک بار چلا"، اسے ایسی جگہ رہنا ہوگا جو وہ مشترک رکھیں۔
- ڈپلائے بھی ایک restart ہے۔ وہ state جو اگلے restart تک ہی بچتی ہے وہ ایک عام کام کے دن تک بھی نہیں بچتی۔
- طے کریں کہ کس طرف ناکام ہونا ہے۔ الرٹس کے لیے، shared store کا بند ہونا زیادہ الرٹس کا مطلب ہونا چاہیے، کم کا نہیں۔
- صرف مثال نہیں، نمونہ دیکھیں۔ الرٹ زیادہ واضح معاملہ تھا؛ دوگنا ہوتا reconciliation job وہی بگ تھا جو خاموشی سے کام کی قیمت بڑھا رہا تھا۔
اس میں سے کچھ بھی Mythex کے اندر سے نظر نہیں آتا۔ ہم platform کو مستحکم کیسے رکھتے ہیں، اس کے لیے ہم نے ڈپلائے کو صفر 502 errors تک کیسے لایا اور گمشدہ swap file دیکھیں۔ اگر آپ کسی سروس کی ایک سے زیادہ کاپیاں چلانے کے خیال سے نئے ہیں، تو سرور کیا ہوتا ہے کا guide بنیادی باتیں بتاتا ہے۔