ایک صارف بٹن پر کلک کرتا ہے۔ درخواست رک جاتی ہے۔ دس سیکنڈ تک خاموشی رہتی ہے۔ وہ فال بیک (fallback) بٹن پر کلک کرتا ہے۔ اب ایک ہی مقصد کے لیے دو کام (jobs) چل رہے ہیں۔ نتیجہ یہ نکلتا ہے کہ ڈپلیکیٹ اثرات (side effects)، دوہری چارجز، اور ڈیٹا کا ایسا انتشار ہوتا ہے جو آپ کی پوری دوپہر برباد کر دیتا ہے۔

یہ فرنٹ اینڈ (frontend) کا بگ نہیں ہے۔ React میں بٹن کو ڈس ایبل کرنا یا ڈیباؤنس ٹائمر (debounce timer) لگانا آپ کو نہیں بچا سکے گا۔ پہلی درخواست پہلے ہی عمل میں تھی۔ نیٹ ورک نے محض جواب کو نگل لیا۔ اگر آپ کا بیک اینڈ ہر آنے والی درخواست کو ایک بالکل نئی ہدایت کے طور پر لیتا ہے، تو ری ٹرائیز (retries) آپ کے لیے نقصان دہ بن جاتے ہیں۔ آپ کو اسے اپنی API ڈیزائن اور ڈیٹا بیس اسکیما (database schema) میں ٹھیک کرنے کی ضرورت ہے۔

اس کا حل ایک سادہ سا ساختیاتی فرق (structural split) سے شروع ہوتا ہے۔

Jobs کو Attempts سے الگ کریں

ایک job کو اس چیز کے پائیدار ریکارڈ کے طور پر سوچیں جو صارف چاہتا ہے۔ یہ مالک (owner)، پیرامیٹرز، ٹارگٹ پرووائیڈر، اور اصل مقصد کو محفوظ کرتا ہے۔ ایک attempt اس مقصد کو پورا کرنے کی ایک مخصوص کوشش ہے۔

ایک پرنٹ شاپ کا تصور کریں۔ آپ ایک فائل دیتے ہیں اور وہ آپ کو ٹکٹ نمبر 45 دیتے ہیں۔ وہ ٹکٹ job ہے۔ دکان پر پہلے انجیکٹ پرنٹر (inkjet printer) آزمایا جاتا ہے۔ وہ پھنس جاتا ہے۔ یہ پہلی کوشش (attempt one) ہے۔ پھر وہ فائل کو لیزر پرنٹر پر منتقل کرتے ہیں۔ یہ دوسری کوشش (attempt two) ہے۔ اس پورے عمل کے دوران، ٹکٹ نمبر 45 کبھی نہیں بدلتا۔ اگر دکان ہر آزمائے گئے پرنٹر کے لیے ایک نیا ٹکٹ جاری کرتی، تو آپ کو تین بار ادائیگی کرنی پڑتی اور تین غیر ضروری کاپیاں ملتی۔

آپ کے ڈیٹا بیس کو بھی اسی کی عکاسی کرنی چاہیے۔ ایک ٹیبل jobs کے لیے ہونا چاہیے۔ دوسرا ٹیبل attempts کے لیے۔ job کی رو (row) مستقل رہتی ہے جبکہ اس کے نیچے attempts جمع ہوتے جاتے ہیں۔

یہ علیحدگی آپ کو کنٹرول فراہم کرتی ہے۔ یہ آپ کو ایک ایسی جگہ بھی دیتی ہے جہاں آپ ایک idempotency key منسلک کر سکیں جو نیٹ ورک کی خرابیوں کے باوجود برقرار رہے۔

ہر Job پر Idempotency Key لازمی کریں

ہر POST درخواست جو ایک job تخلیق کرتی ہے، اس میں ایک منفرد idempotency key ہونا ضروری ہے۔ یہ کی (key) صارف کی ہے، سیشن (session) کی نہیں۔ مالک کی آئی ڈی (owner ID) اور کی (key) کو ملا دیں، پھر ان دو کالمز پر ایک منفرد ڈیٹا بیس کنسٹرینٹ (database constraint) نافذ کریں۔

ڈیٹا بیس کنسٹرینٹ کیوں؟ کیونکہ ڈیٹا داخل کرنے سے پہلے ایپلی کیشن کوڈ میں موجودگی کی جانچ کرنا ایک race condition ہے جو کسی بھی وقت پیش آ سکتی ہے۔ دو ایک جیسی درخواستیں ایک ہی مائیکرو سیکنڈ کے وقفے میں نکل سکتی ہیں۔ ڈیٹا بیس کو نافذ کرنے والا (enforcer) بننے دیں۔ اگر کوئی صارف ایک ہی owner ID اور key دو بار بھیجتا ہے، تو دوسری درخواست منفرد خلاف ورزی (unique violation) کو پکڑ لے گی اور آپ موجودہ job واپس کر دیں گے۔ دونوں درخواستوں کو ایک ہی job ID ملے گا۔ کوئی اضافی کام شروع نہیں ہوگا۔

اس کی حد (scope) کے بارے میں سخت رہیں۔ اگر کوئی کی (key) کو دوبارہ استعمال کرتا ہے لیکن ان پٹ پے لوڈ (input payload) بدل دیتا ہے، تو conflict واپس کریں۔ idempotency key کو صرف صارف کے ساتھ نہیں بلکہ اصل مقصد کے ساتھ منسلک ہونا چاہیے۔ ایک ہی کی (key) کے ساتھ مختلف ان پٹ کا مطلب ہے کہ کلائنٹ الجھن کا شکار ہے، اور آپ کے سسٹم کو اندازہ لگانے کے بجائے اسے مسترد کر دینا چاہیے۔

State Transitions کا تحفظ کریں

ایک attempt اسٹیٹ ٹرانزیشن (state transition) ہے، نیا job نہیں ہے۔ آپ کی API کو ایک نئی کوشش شروع کرنے سے انکار کر دینا چاہیے اگر پچھلی کوشش ابھی بھی شروع ہونے یا نامعلوم حالت (unknown state) میں لٹکی ہوئی ہو۔

ٹائم آؤٹ (timeouts) اس کی وجہ ہیں۔ جب کسی پرووائیڈر کی درخواست کا وقت ختم ہو جاتا ہے، تو کلائنٹ کو ناکامی نظر آتی ہے، لیکن سرور سائیڈ کا عمل ابھی بھی جاری ہو سکتا ہے۔ GPU کلسٹر اب بھی آپ کی انفرنس (inference) کی درخواست پر کام کر رہا ہو سکتا ہے۔ کنٹینر (container) اب بھی blob storage میں ڈیٹا لکھ رہا ہو سکتا ہے۔ اگر آپ ٹائم آؤٹ ہونے والی کوشش کو ناکام قرار دے کر فوراً دوسری کوشش شروع کر دیتے ہیں، تو آپ ڈپلیکیٹ اثرات کے ساتھ جوا کھیل رہے ہیں۔

ٹائم آؤٹ کو ناکام حالت کے بجائے ایک نامعلوم حالت (unknown state) کے طور پر سمجھیں۔ جب تک پچھلی کوشش کسی حتمی حالت (terminal state) تک نہ پہنچ جائے یا کسی بیرونی عمل کے ذریعے واضح طور پر منسوخ نہ کر دی جائے، نئی کوششوں کو روک دیں۔ یہ وقفہ تکلیف دہ ہو سکتا ہے۔ یہ صارف کو انتظار کرنے پر مجبور کرتا ہے۔ یہ دو ورکرز کے ایک ہی ڈاؤن اسٹریم وسائل (downstream resources) کو تبدیل کرنے کے انتشار کو بھی روکتا ہے۔

Resolve Races with Compare-and-Swap

مشکل ترین مسائل تب سامنے آتے ہیں جب متعدد کوششیں (attempts) مکمل ہوتی ہیں۔ ہو سکتا ہے کہ آپ کے سسٹم نے پہلی کوشش بنیادی پرووائیڈر کے خلاف کی ہو۔ دس سیکنڈ کی خاموشی کے بعد، اس نے دوسری کوشش فال بیک (fallback) کے خلاف کی۔ اب دونوں کوششیں مکمل ہو چکی ہیں۔ آپ دونوں کو ایک ہی job رو پر اپنے نتائج لکھنے کی اجازت نہیں دے سکتے۔

compare-and-swap لاجک استعمال کریں۔ job رو میں ایک ورژن نمبر شامل کریں۔ جب ایک attempt مکمل ہو، تو یہ درج ذیل شرائط کے ساتھ ایک اپ ڈیٹ (update) چلائے:

  • موجودہ ورژن وہی ہونا چاہیے جو attempt کے آغاز میں پڑھا گیا تھا۔
  • کسی دوسری attempt نے پہلے سے نتیجہ (result slot) حاصل نہ کیا ہو۔
  • اگر دونوں شرائط پوری ہوں، تو نتیجہ لکھیں اور ورژن میں اضافہ کریں۔

SQL کی اصطلاح میں، یہ ایک اپ ڈیٹ اسٹیٹمنٹ کی طرح نظر آتا ہے: WHERE id = $1 AND version = $2 AND completed_by IS NULL۔ اگر اپ ڈیٹ صفر روز (rows) واپس کرتا ہے، تو اس کا مطلب ہے کہ کوئی دوسری attempt پہلے ہی جیت چکی ہے۔ دیر سے آنے والی درخواست کو نظر انداز کیا جانا چاہیے۔ اس کے نتیجے کو چھوڑ دیں۔ اسے نہ ملائیں (merge) اور نہ ہی شامل (append) کریں۔ اس کام کو ضائع کر دیں۔ ایک دیر سے آنے والا نتیجہ جو پہلے سے موجود فاتح کے نتیجے کو مٹاتا ہے، ڈیٹا کی خرابی (data corruption) ہے، اور واحد محفوظ راستہ اسے مسترد کرنا ہے۔

یہ الٹ ترتیب سے ہونے والے اختتام کو صفائی سے سنبھالتا ہے۔ کوشش A پہلے نکلتی ہے لیکن تیس سیکنڈ کے بعد واپس آتی ہے۔ کوشش B دوسرے نمبر پر نکلتی ہے لیکن پانچ سیکنڈ کے بعد واپس آتی ہے۔ کوشش B 'compare-and-swap' جیت جاتی ہے۔ کوشش A کی اپ ڈیٹ صفر قطاروں (rows) کو متاثر کرتی ہے۔ آپ کا سسٹم اس ریس (race) کو لاگ کرتا ہے، پرانے (stale) پیلوڈ کو نظر انداز کرتا ہے، اور آگے بڑھ جاتا ہے۔

بریک پوائنٹس (Breakpoints) کا ٹیسٹ کریں

آپ ان بگ (bugs) کو 'happy-path' ٹیسٹنگ میں نہیں پکڑ سکیں گے۔ آپ کے ٹیسٹ سوٹ کو ان کمزوریوں (fractures) کو نشانہ بنانا ہوگا۔

  • ڈبل کلک کی نقل کریں۔ ایک ہی idempotency key کے ساتھ دو بیک وقت آنے والی POST درخواستیں ایک جیسی job IDs واپس کرنی چاہئیں۔
  • غلط ان پٹ کے ساتھ وہی کی (key) بھیجیں۔ ایک 'conflict' رسپانس کی توقع رکھیں۔ اگر پیرامیٹرز مختلف ہوں تو سسٹم کو خاموشی سے موجودہ جاب واپس نہیں کرنی چاہیے۔
  • ٹائم آؤٹ (timeout) پیدا کریں۔ تصدیق کریں کہ جاب ایک نامعلوم حالت (unknown state) میں جاتی ہے، نہ کہ فیلڈ حالت (failed state) میں، اور سسٹم اس وقت تک مزید کوششوں کو روک دیتا ہے جب تک کہ ابہام دور نہ ہو جائے۔
  • دو کوششوں کو الٹ ترتیب میں ختم ہونے پر مجبور کریں۔ اس بات کی تصدیق کریں کہ واپس آنے والی دوسری کوشش ہار جاتی ہے، چاہے پہلا نکلنے والا باضابطہ بنیادی فراہم کنندہ (primary provider) ہی کیوں نہ ہو۔

یہ ٹیسٹ محض 'edge-case' کی آسائشیں نہیں ہیں۔ یہ وہ معاہدہ ہے جو آپ کی API باقی سسٹم کے ساتھ کرتی ہے۔

فیل اوور (Fail Over) کرنے سے پہلے فراہم کنندہ کے ارادے کی تصدیق کریں

اگر آپ ملٹی پرووائیڈر سیٹ اپ چلا رہے ہیں، تو آپ کا جی چاہ سکتا ہے کہ مختلف AI ماڈلز کو ایک دوسرے کے متبادل کے طور پر استعمال کریں۔ وہ ایک ہی کوڈ پاتھ، ایک ہی HTTP کلائنٹ، اور ایک ہی JSON اسکیما استعمال کرتے ہیں۔ اس کا مطلب یہ نہیں ہے کہ ان کا طرزِ عمل بھی ایک جیسا ہوگا۔

ایک ماڈل کسی ٹاپ لیول کی (top-level key) کو غلط طریقے سے تخلیق (hallucinate) کر سکتا ہے۔ دوسرا آپ کے سسٹم پرامپٹ فارمیٹنگ کو نظر انداز کر سکتا ہے۔ اسکیما ویلیڈیشن (Schema validation) سنٹیکس کی غلطیوں کو پکڑ لیتی ہے، لیکن یہ ایسا رسپانس بھی پاس کر دے گی جسے آپ کا بزنس لاجک (business logic) سمجھ نہیں سکے گا۔ ایک فراہم کنندہ درست JSON واپس کر سکتا ہے جو آپ کے پرامپٹ ٹیمپلیٹ کے ساتھ غلط کام کر رہا ہو۔

خودکار ماڈل سوئچنگ کی اجازت دینے سے پہلے فراہم کنندہ کے مخصوص ٹیسٹ چلائیں۔ اس بات کی تصدیق کریں کہ 'fallback' ماڈل کم درجہ حرارت (low temperature) پر آپ کے آؤٹ پٹ اسٹرکچر کا احترام کرتا ہے۔ تصدیق کریں کہ آپ کا پرامپٹ اس فراہم کنندہ کے ٹوکنائزر (tokenizer) کے ذریعے درست طریقے سے رینڈر ہوتا ہے۔ حقیقی ان پٹس کے ساتھ مکمل راؤنڈ ٹرپ کا ٹیسٹ کریں۔ خودکار فیل اوور (automatic failover) صرف اس وقت محفوظ ہے جب آپ یہ ثابت کر چکے ہوں کہ فال بیک ماڈل بھی اسی آپریشنل معاہدے پر عمل کرتا ہے۔

ہر ارادے (Intent) کے لیے ایک جاب رکھیں

فال بیک راستے اچھے ہیں۔ لیکن غیر منظم فال بیک کا بڑھنا ایک بگ (bug) ہے۔ آپ کے اسٹیک کے ہر لیئر کو اس بات کا جائزہ لینے کی ضرورت ہے کہ آیا اس نے پہلے ہی بالکل وہی ٹاسک دیکھا ہے۔ لوڈ بیلنسر، API ہینڈلر، ڈیٹا بیس، اور ورکر، سب کو ایک ہی شناخت (identity) کا احترام کرنا چاہیے۔

اپنے سسٹم کو اس طرح بنائیں کہ ری ٹرائیز (retries) اور فال بیکس ایک مستحکم جاب کے تحت نئی کوششوں کے طور پر سامنے آئیں۔ ڈیٹا بیس پر مبنی idempotency key کے ذریعے جاب کو لاک کریں۔ تبدیلیوں (transitions) کی حفاظت کریں۔ کوششوں کا مقابلہ (race) ہونے دیں۔ صرف ایک کو جیتنے دیں۔ اسی طرح آپ ایک صارف کے کلک کو ڈیٹا کی صفائی کے پورے ویک اینڈ میں بدلنے سے روک سکتے ہیں۔