जेव्हा एखादा वापरकर्ता 'send' बटण वेगाने दाबून धरत असे, तेव्हा बॉट एकाच वेळी दोन किंवा तीन वेळा तेच उत्तर देऊ लागला. ही पुनरावृत्ती फक्त अशा लोकांसाठी दिसून येत होती जे AI विचार करण्यास सुरुवात करण्यापूर्वीच काही संदेश वेगाने टाईप करू शकत होते. ही समस्या दुर्मिळ असल्याने प्रोडक्शनमध्ये बराच काळ लपून राहिली. डेटाबेस लॉक वेळेआधीच सुटल्यामुळे (lock घेतल्याच्या काही मिलीसेकंदातच तो मुक्त झाला), संभाषण असुरक्षित राहिले आणि एकाच प्रॉम्प्टला उत्तर देण्यासाठी अनेक प्रोसेस सुरू होऊ लागल्या.
लॉक का अयशस्वी झाले
कोडने एका सिंगल डेटाबेस कॉलमध्ये लॉक मिळवला आणि लगेच नियंत्रण 'request handler' कडे सोपवले. लॉकचा कालावधी मिलीसेकंदात होता, जो AI मॉडेलला प्रतिसाद तयार करण्यासाठी लागणाऱ्या वेळेपेक्षा खूपच कमी होता. मॉडेलने काम सुरू करण्यापूर्वीच लॉक निघून गेला होता, त्यामुळे दुसऱ्या विनंतीला (request) तोच संभाषण रेकॉर्ड मिळवून पुन्हा उत्तर देण्यापासून रोखणारे काहीही नव्हते.
दोन लक्षणे दिसून आली:
- एकापाठोपाठ एक सारखीच उत्तरे पाठवली जात होती.
- एकाच प्रश्नासाठी थोड्या वेगळ्या शब्दांत उत्तरे येत होती, कारण प्रत्येक प्रोसेस वापरकर्त्याच्या इनपुटवरून स्वतःचा प्रॉम्प्ट तयार करत होती.
बहुतेक वापरकर्ते संदेशांमध्ये थोडा वेळ थांबत असल्याने, ही त्रुटी (bug) लक्षात येत नव्हती. फक्त वेगाने टाईप करणाऱ्यांमुळेच 'race condition' निर्माण होत असे आणि अशी प्रकरणे दुर्मिळ होती.
अपूर्ण उपाय जो अपयशी ठरला
पहिला उपाय म्हणून संदेश आल्यावर थोडा विलंब (delay) जोडण्यात आला, जेणेकरून वेगाने येणारे इनपुट "debounce" करता येईल अशी आशा होती. जेव्हा दोन संदेश एकापाठोपाठ एक येत होते, तेव्हा याचा फायदा झाला, पण जर AI मजकूर तयार करत असतानाच तिसरा संदेश आला, तर हा उपाय अपयशी ठरला.
जेव्हा टाइमर्स आणि संभाषणाचा डेटा एकाच 'storage bucket' मध्ये ठेवला गेला, तेव्हा दुसरी समस्या समोर आली. जेव्हा बॉटने विनंतीवर प्रक्रिया पूर्ण केली, तेव्हा त्याने टाइमर रेकॉर्ड ओव्हरराईट केले, ज्यामुळे त्याचा स्वतःचा काउंटडाउन (countdown) नष्ट झाला. सिस्टीमला कोणत्या संदेशांना आधीच उत्तरे दिली आहेत याचा मागोवा घेता येत नव्हता, ज्यामुळे पुनरावृत्तीची शक्यता वाढली.
एक विश्वसनीय सुरक्षा यंत्रणा तयार करणे: version counters, isolated timers, आणि एक lease
टीमने तीन मुख्य स्तंभांवर आधारित नवीन फ्लो डिझाइन केला:
- Version counter – प्रत्येक येणारा संदेश संभाषणासोबत साठवलेला एक काउंटर वाढवतो. शेवटच्या प्रतिसादानंतर किती संदेश आले आहेत हे काउंटर सिस्टीमला सांगतो, ज्यामुळे प्रतिसाद तयार होत असताना नवीन इनपुट शोधणे सोपे होते.
- Dedicated debounce window – आता टाइमर्स संभाषणाच्या डेटापासून वेगळ्या स्टोरेज क्षेत्रात असतात. 'Debounce' कालावधीवर मर्यादा (hard cap) असल्याने वापरकर्ता बॉटला अनिश्चित काळासाठी थांबवू शकत नाही.
- Session lease – मूळ लॉकच्या जागी आता 'lease' वापरली जाते, ज्यामध्ये स्पष्ट 'expiry timestamp' असतो. ही लीज 'compare-and-swap (CAS)' ऑपरेशनद्वारे मिळवली जाते: प्रोसेस सध्याची लीज व्हॅल्यू वाचते आणि जर जुनी व्हॅल्यू जुळली तरच नवीन व्हॅल्यू लिहिते, आणि त्याद्वारे संभाषणावर अनन्य अधिकार (exclusive rights) मिळवते. जर प्रोसेस क्रॅश झाली, तर लीज आपोआप संपते आणि संभाषण पुढील हँडलरसाठी मोकळे होते.
नवीन पाइपलाइन कशी काम करते
- Message arrival – सिस्टीम व्हर्जन काउंटर वाढवते आणि 'debounce timer' (री)सेट करते. AI सुरू करण्यापूर्वीच ती लगेच क्लायंटला प्रतिसाद देते.
- Timer expiration – टाइमर हँडलर लीज मिळवण्याचा प्रयत्न करतो. जर CAS यशस्वी झाले, तर हँडलर पुढे जातो; अन्यथा, दुसरा प्रोसेस आधीच संभाषणावर नियंत्रण मिळवून आहे हे ओळखून तो थांबतो.
- Check for new input – हँडलर सध्याचा व्हर्जन काउंटर आणि टाइमर सुरू होता तेव्हा रेकॉर्ड केलेली व्हॅल्यू यांची तुलना करतो. जर काउंटर वाढला असेल, तर तो प्रलंबित संदेश एकत्र करून एकच प्रॉम्प्ट तयार करतो.
- Generate a reply – AI मॉडेल एकदा चालते आणि सर्व अलीकडील वापरकर्ता इनपुट समाविष्ट असलेले एकच उत्तर तयार करते.
- Final sanity check – उत्तर पाठवण्यापूर्वी, हँडलर पुन्हा एकदा व्हर्जन काउंटर वाचतो. जर जनरेशन दरम्यान एखादा नवीन संदेश आला असेल, तर ते उत्तर रद्द केले जाते आणि प्रोसेस पुन्हा टाइमर सुरू करते, ज्यामुळे वापरकर्त्याला जुने किंवा चुकीचे उत्तर मिळणार नाही याची खात्री होते.
या पद्धतीमुळे पुनरावृत्ती होणारी उत्तरे थांबतात, संभाषण किती वेळ थांबवता येईल यावर मर्यादा येते आणि लीज आपोआप संपत असल्याने प्रोसेस क्रॅश झाल्यास सिस्टीम आपोआप पूर्वस्थितीत येते.
निष्कर्ष
क्रिटिकल सेक्शन सुरू होण्यापूर्वीच निघून जाणारे लॉक कोणतेही संरक्षण देऊ शकत नाही. क्षणभंगुर डेटाबेस लॉकच्या जागी स्पष्ट आणि एक्सपायर होणारी 'lease' वापरून आणि टाइमर्सना संभाषणाच्या डेटापासून वेगळे करून, आता बॉट वापरकर्ते कितीही वेगाने टाईप करत असले तरी एकच आणि अद्ययावत (up-to-date) उत्तर देण्याची खात्री देतो. ही घटना एक शाश्वत धडा देते: 'concurrency safeguards' हे ज्या कामाचे संरक्षण करतात त्यापेक्षा जास्त काळ टिकले पाहिजेत, अन्यथा ते अदृश्य अडथळे बनतात ज्यामुळे त्रुटी (bugs) सहजपणे आत शिरू शकतात.
