ایک بھاری چیٹ ہمارا API بند کر سکتی تھی
28 ستمبر کو، ایک release کے تقریباً 17 منٹ بعد، ہمارے دونوں production API سرورز ایک ہی لمحے میں memory ختم ہونے سے گر گئے، دوبارہ شروع ہوئے، اور تقریباً پانچ منٹ تک load balancer کے پاس ٹریفک بھیجنے کے لیے کوئی صحت مند سرور نہیں تھا۔ ہمارے پہلے fix نے API کو دوگنی memory دی۔ اصل وجہ اسی دن بعد میں سامنے آئی: ایک خاص چیٹ کھولنے پر API نے ایک ہی request میں 53 MB پیغامات لوڈ کرنے کو کہا۔ اب چیٹ کی تاریخ سائز اور تعداد دونوں سے محدود صفحات میں لوڈ ہوتی ہے، اور وہ چیٹ اب crash کے بجائے 5 MB کے پہلے صفحے سے کھلتی ہے۔
کیا بند ہوا
ہمارا API دو سرورز میں سے ہر ایک پر ایک container کے طور پر چلتا ہے، جو ایک load balancer کے پیچھے ہیں۔ ہر container کی memory کی حد 384 MiB تھی۔ آرام کی حالت میں وہ 130 تا 140 MiB استعمال کرتا ہے، اس لیے حد کافی گنجائش والی لگتی تھی۔
دونوں containers ایک ساتھ اس حد پر پہنچے اور “JavaScript heap out of memory” کے ساتھ crash ہو گئے۔ heap وہ memory ہے جو ایک Node.js process اپنے کام کے objects کے لیے استعمال کرتا ہے۔ چونکہ دونوں سرورز ایک ساتھ ناکام ہوئے، درخواستیں بھیجنے کی کوئی جگہ باقی نہیں رہی۔ دو سرورز والے سیٹ اپ کے لیے یہ بدترین قسم کا outage ہے: redundancy صرف اس وقت مدد کرتی ہے جب ناکامیاں ایک ساتھ نہ ہوں۔
پہلا fix: زیادہ جگہ
وقت deploy کی طرف اشارہ کرتا تھا۔ release کے بعد وہ agent turns جو deploy نے روکے تھے دوبارہ شروع ہوتے ہیں اور اپنی transcripts دوبارہ محفوظ کرتے ہیں، اور کام کا وہ اچانک ریلا memory کے اضافے کی واضح وجہ لگتا تھا۔
اس لیے پہلی تبدیلی میں API کی حد 384 MiB سے بڑھا کر 768 MiB کی گئی۔ ہم نے اسے اسی دن دونوں سرورز پر براہِ راست لاگو کیا، اور deploy configuration میں بھی commit کیا تاکہ اگلا deploy خاموشی سے 384 واپس نہ لے آئے۔ ہاتھ سے کسی سرور پر کیا گیا fix، جسے اگلا deploy واپس کر دے، وہ عام طریقہ ہے جس سے وہی outage واپس آ جاتا ہے۔
زیادہ memory ایک معقول حفاظتی گنجائش تھی۔ وہ وضاحت نہیں تھی۔
اصل وجہ: ایک request، 71 MB
بعد میں اسی دن ہم نے ایک چیٹ کی نقل پر crash دوبارہ پیدا کیا، production کی پرانی memory حد کے ساتھ۔ وہ نقل بغیر کسی deploy کے اکیلے API کو crash کر گئی۔
جب آپ پروجیکٹ کھولتے ہیں تو ویب ایپ API سے چیٹ کی تاریخ مانگتی ہے۔ endpoint آخری 30 پیغامات پورے واپس کرتا تھا۔ زیادہ تر چیٹس کے لیے 30 پیغامات چھوٹے ہوتے ہیں۔ اس چیٹ کے لیے آخری 30 پیغامات database میں 53 MB اور response 71 MB کا تھا۔ ایجنٹ کے کئی جواب 4 تا 10 MB فی جواب کے تھے، زیادہ تر sub-agents کی transcripts: وہ مددگار ایجنٹس جو مرکزی ایجنٹ ایک کام کا حصہ متوازی طور پر کرنے کے لیے شروع کرتا ہے، اور جن کا مکمل کام جواب کے ساتھ محفوظ ہوتا ہے۔
اس ایک response کو بنانے میں API کے پاس موجود heap سے زیادہ جگہ لگی۔ اور پروجیکٹ کھولنے یا refresh کرنے پر ہر بار request دوبارہ جاتی تھی۔ ایک چیٹ اس سرور کو گرا سکتی تھی جس تک وہ پہنچتی، اور چونکہ صفحہ دوبارہ لوڈ ہو سکتا تھا، وہ دونوں سرورز تک پہنچ سکتی تھی۔
“30 پیغامات” کبھی سائز کی حقیقی حد نہیں تھی۔ یہ تعداد کی حد تھی جو عام چیٹس کے لیے اتفاقاً چھوٹی تھی۔
حل: صفحات جو سائز اور تعداد دونوں سے محدود ہوں
تاریخ کا ایک صفحہ اب 30 پیغامات یا تقریباً 4 MB کے پیغامات پر مشتمل ہوتا ہے، جو پہلے آئے۔
یہ دو مراحل میں کام کرتا ہے:
- پہلے سائز مانگیں۔ API database سے سب سے نئے پیغامات کے IDs اور محفوظ شدہ سائز مانگتا ہے، ان کا مواد نہیں۔ یہ query پیغامات کتنے بھی بڑے ہوں، سستی رہتی ہے۔
- صرف وہ لوڈ کریں جو فٹ ہو۔ نئے سے پرانے کی طرف چلتے ہوئے، یہ پیغامات اس وقت تک شامل کرتا ہے جب تک اگلا پیغام صفحے کو 4 MB سے آگے نہ لے جائے۔ پھر صرف وہی rows پورے لوڈ ہوتی ہیں۔
دو اصول اسے محفوظ رکھتے ہیں:
- سب سے نیا پیغام ہمیشہ صفحے پر جاتا ہے، چاہے اس کا سائز کچھ بھی ہو۔ ایک صفحہ جو کچھ نہ لوٹائے چیٹ کو اٹکا دے گا، پیچھے اسکرول کرنے کا کوئی راستہ نہیں ہوگا۔
- کچھ کاٹا نہیں جاتا۔ کوئی پیغام کاٹا یا گرایا نہیں جاتا۔ چیٹ کا باقی حصہ “Scroll up for earlier messages” کنٹرول کے ذریعے لوڈ ہوتا ہے، جو پرانے پیغامات پہلے سے استعمال کرتا تھا، ایک وقت میں ایک صفحہ۔
ہمیں ایک اور جگہ بھی ملی جس کی شکل یہی تھی۔ جب آپ کوئی turn روکتے ہیں تو API آخری چند جوابات چیک کرتا ہے کہ کوئی خالی تو نہیں جسے صاف کرنا ہو۔ وہ یہ آٹھ پورے پیغامات لوڈ کر کے کرتا تھا۔ کئی میگا بائٹ کے جوابات والی چیٹ پر یہ اسی مسئلے کی چھوٹی شکل ہے۔ اب وہ database سے ایک ہاں یا نا کا سوال پوچھتا ہے: کیا اس پیغام میں کوئی مواد ہے؟ مواد خود لوڈ کیے بغیر۔
ہم نے کیسے جانچا
چیٹ کی نقل پر، پرانی 384 MiB حد کے ساتھ چلتے ہوئے:
- پہلا صفحہ 5 MB کا ہے۔ پہلے یہی درخواست API کو crash کرتی تھی۔
- ایک ہی وقت میں پانچ براؤزر tabs کا چیٹ کھولنا 154 MB کی memory چوٹی تک پہنچتا ہے۔
- پوری چیٹ میں پیچھے اسکرول کرنے پر تمام 190 پیغامات واپس آتے ہیں، ہر ایک صرف ایک بار، ترتیب سے۔
صفحہ چننے والی منطق کے unit tests بھی ہیں، جن میں ایک اس چیٹ کے سب سے نئے پیغامات کے حقیقی سائز سے بنایا گیا ہے۔ پہلا صفحہ ایک پیغام واپس دیتا ہے، بتاتا ہے کہ مزید ہیں، اور اگلا صفحہ اس کے بعد والے پیغام سے شروع ہوتا ہے۔
ہم نے کیا سیکھا
- تعداد سائز کی حد نہیں ہوتی۔ ہر endpoint جو کسی ایسی چیز کے “سب سے نئے N” واپس کرتا ہے جو بے حد بڑھ سکتی ہو، اسے بائٹ کا بجٹ بھی چاہیے۔ ڈیٹا بالآخر اس خلا کو ڈھونڈ لیتا ہے۔
- واضح مشتبہ شخص ہمیشہ اصل وجہ نہیں ہوتا۔ deploy کام کے اضافے کا حقیقی محرک تھا، اور زیادہ memory کے لائق تھی۔ لیکن crash بغیر deploy کے دوبارہ پیدا ہوا، اور اسے دوبارہ پیدا کیے بغیر ہم memory کے اضافے پر رک جاتے۔
- دو سرور ایک ساتھ ناکام ہوتے ہیں جب انہیں ایک ہی request ملے۔ redundancy ایک سرور کے گرنے سے بچاتی ہے۔ وہ اس request سے نہیں بچاتی جو وہ سرور بھی گرا دے جس تک وہ پہنچے، خاص کر وہ جسے صارف refresh کر کے دوبارہ بھیجے گا۔
- config میں ٹھیک کریں، صرف مشین پر نہیں۔ memory کا اضافہ اسی دن deploy configuration میں گیا، تاکہ اگلا deploy اسے برقرار رکھے۔
یہ ایجنٹ کے output سے متعلق ہماری دوسری حالیہ memory کہانی ہے۔ پہلی وہ تھی جس میں ایک status label 690 MB کے strings قطار میں ڈال رہا تھا۔ اگر آپ Mythex پر sub-agents استعمال کرتے ہیں تو وہ کیسے کام کرتے ہیں، یہ docs میں پڑھ سکتے ہیں۔