दौड़ की स्थिति क्या है? मैंने दौड़ की स्थिति की एक अच्छी परिभाषा की खोज की और मुझे यह सबसे अच्छी परिभाषा मिली: दौड़ की स्थिति एक अप्रत्याशित व्यवहार है जो कई प्रक्रियाओं द्वारा साझा संसाधनों के साथ अपेक्षा से भिन्न क्रम में बातचीत करने के कारण होता है। यह काफी मुंह की बात है और यह अभी भी बहुत स्पष्ट नहीं है कि में दौड़ की स्थिति कैसी दिखती है। रेल्स रेल का उपयोग करते हुए, हम हमेशा कई प्रक्रियाओं के साथ काम कर रहे हैं - प्रत्येक अनुरोध या पृष्ठभूमि कार्य एक व्यक्तिगत प्रक्रिया है जो अन्य प्रक्रियाओं से अधिकतर स्वतंत्र रूप से काम कर सकती है। हम हमेशा साझा संसाधनों के साथ भी काम कर रहे हैं। क्या एप्लिकेशन रिलेशनल डेटाबेस का उपयोग करता है? वह एक साझा संसाधन है. क्या एप्लिकेशन किसी प्रकार के कैशिंग सर्वर का उपयोग करता है? हाँ, यह एक साझा संसाधन है। क्या आप किसी प्रकार की बाहरी एपीआई का उपयोग करते हैं? आपने अनुमान लगाया - यह एक साझा संसाधन है। दौड़ की स्थितियों की दो उदाहरण श्रेणियां हैं जिनके बारे में मैं बात करना चाहूंगा और फिर उनसे निपटने के तरीके पर बात करना चाहूंगा। पढ़ने के लिए संशोधित-लिखने रीड-मॉडिफाई-राइट श्रेणी एक प्रकार की दौड़ की स्थिति है जहां एक प्रक्रिया साझा संसाधन से मूल्यों को पढ़ेगी, मेमोरी के भीतर मूल्य को संशोधित करेगी, और फिर इसे साझा संसाधन पर वापस लिखने का प्रयास करेगी। जब हम इसे एक प्रक्रिया के चश्मे से देखते हैं तो यह बहुत सीधा लगता है। लेकिन जब कोई दूसरी प्रक्रिया सामने आती है, तो इसके परिणामस्वरूप कुछ अप्रत्याशित व्यवहार हो सकता है। ऐसे कोड पर विचार करें जो इस तरह दिखता है: class IdeasController < ActionController::Base def vote @idea = Idea.find(params[:id]) @idea.votes += 1 @idea.save! end end यहां हम ( , संशोधित कर रहे हैं ( ), फिर लिख रहे हैं ( )। Idea.find(params[:id]) @idea.votes += 1 @idea.save! हम देख सकते हैं कि इससे किसी विचार पर वोटों की संख्या एक से बढ़ जाएगी। यदि कोई विचार शून्य वोट वाला होता तो वह एक वोट पर ही ख़त्म हो जाता। हालाँकि, यदि कोई दूसरा अनुरोध आता है और डेटाबेस से विचार पढ़ता है जबकि उसके पास अभी भी शून्य वोट हैं और स्मृति में उस मूल्य को बढ़ाता है, तो हमारे पास ऐसी स्थिति हो सकती है जहां दो वोट एक साथ आते हैं - फिर भी अंतिम परिणाम यह है कि वोटों की संख्या डेटाबेस में केवल एक है. इसे रेस स्थिति भी कहा जाता है। लॉस्ट अपडेट जाँचें-फिर-कार्य करें चेक-तब-एक्ट श्रेणी एक प्रकार की दौड़ की स्थिति है जहां डेटा एक साझा संसाधन से लोड किया जाता है, और मौजूद मूल्य के आधार पर, हम निर्धारित करते हैं कि क्या कोई कार्रवाई करने की आवश्यकता है। यह कैसे दिखाई देता है इसका एक उत्कृष्ट उदाहरण रेल्स में सत्यापन में इस प्रकार है: validates_uniqueness_of class User < ActiveRecord::Base validates_uniqueness_of :email end ऐसे कोड पर विचार करें जो इस तरह दिखता है: User.create(email: "demo@example.com") सत्यापन के साथ, रेल्स जाँच करेगा कि क्या उस ईमेल के पास कोई मौजूदा उपयोगकर्ता है। यदि कोई अन्य नहीं है, तो यह उपयोगकर्ता को डेटाबेस में बनाए रखकर कार्य करेगा। हालाँकि, यदि दूसरा अनुरोध एक ही समय में समान कोड निष्पादित कर रहा हो तो क्या होगा? हम ऐसी स्थिति में पहुँच सकते हैं जहाँ दोनों अनुरोध यह निर्धारित करने के लिए जाँच करते हैं कि क्या डुप्लिकेट डेटा है (और कोई नहीं है) - फिर वे दोनों डेटा को सहेजकर कार्य करेंगे, जिसके परिणामस्वरूप डेटाबेस में एक डुप्लिकेट उपयोगकर्ता होगा। दौड़ की स्थितियों को संबोधित करना दौड़ की स्थितियों को ठीक करने के लिए कोई चांदी की गोली नहीं है, लेकिन कुछ रणनीतियाँ हैं जिनका उपयोग किसी विशेष समस्या के लिए किया जा सकता है। दौड़ की शर्तों को हटाने के लिए तीन मुख्य श्रेणियां हैं: 1. महत्वपूर्ण अनुभाग हटाएँ हालाँकि इसे आपत्तिजनक कोड को हटाने के रूप में देखा जा सकता है, कभी-कभी आप कोड को दोबारा तैयार कर सकते हैं ताकि यह दौड़ की स्थितियों के प्रति संवेदनशील न हो। अन्य समय में, आप परमाणु संचालन पर गौर कर सकते हैं। एक परमाणु ऑपरेशन वह है जहां कोई अन्य प्रक्रिया ऑपरेशन को बाधित नहीं कर सकती है, इसलिए आप जानते हैं कि यह हमेशा एक इकाई के रूप में निष्पादित होगा। पढ़ने-संशोधित-लिखने के उदाहरण के लिए, स्मृति में विचार वोटों को बढ़ाने के बजाय, उन्हें डेटाबेस में बढ़ाया जा सकता है: @ideas.increment!(:votes) वह एसक्यूएल निष्पादित करेगा जो इस तरह दिखता है: UPDATE "ideas" SET "votes" = COALESCE("votes", 0) + 1 WHERE "ideas"."id" = 123 इसका उपयोग समान दौड़ शर्तों के अधीन नहीं होगा। चेक-तब-एक्ट उदाहरण के लिए, रेल को मॉडल को मान्य करने की अनुमति देने के बजाय, हम रिकॉर्ड को सीधे डेटाबेस में एक अप्सर्ट के साथ सम्मिलित कर सकते हैं: User.where(email: "demo@example.com").upsert({}, unique_by: :email) वह रिकॉर्ड को डेटाबेस में डाल देगा। यदि ईमेल पर कोई विरोध है (जिसके लिए ईमेल पर एक अद्वितीय अनुक्रमणिका की आवश्यकता होगी) तो यह केवल सम्मिलन को अनदेखा कर देगा। 2. पता लगाएं और पुनर्प्राप्त करें कभी-कभी आप महत्वपूर्ण अनुभाग को नहीं हटा सकते. यह संभव है कि कोई परमाणु क्रिया हो, लेकिन यह उस तरीके से काम नहीं करता है जिसकी कोड को आवश्यकता होती है। उन स्थितियों में, आप पता लगाने और पुनर्प्राप्त करने का तरीका आज़मा सकते हैं। इस दृष्टिकोण के साथ, सुरक्षा उपाय स्थापित किए जाते हैं जो दौड़ की स्थिति होने पर आपको सूचित करेंगे। आप या तो शालीनतापूर्वक ऑपरेशन को निरस्त कर सकते हैं या पुनः प्रयास कर सकते हैं। पढ़ने-संशोधित-लिखने के उदाहरण के लिए, यह के साथ किया जा सकता है। आशावादी लॉकिंग को रेल्स में बनाया गया है और यह पता लगाने की अनुमति दे सकता है कि एक ही समय में एक ही रिकॉर्ड पर कई प्रक्रियाएं कब चल रही हैं। आशावादी लॉकिंग को सक्षम करने के लिए, आपको केवल अपनी तालिका में एक कॉलम जोड़ना होगा और रेल स्वचालित रूप से इसे सक्षम कर देगी। आशावादी लॉकिंग lock_version change_table :ideas do |t| t.integer :lock_version, default: 0 end फिर जब आप किसी रिकॉर्ड को अपडेट करने का प्रयास करते हैं, तो रेल्स इसे केवल तभी अपडेट करेगा यदि वही संस्करण है जो यह मेमोरी में था। यदि ऐसा नहीं है, तो यह एक अपवाद उठाएगा, जिसे इसे संभालने के लिए बचाया जा सकता है। इसे संभालना एक हो सकता है या यह उपयोगकर्ता को वापस रिपोर्ट किया गया एक त्रुटि संदेश हो सकता है। lock_version ActiveRecord::StaleObjectError retry def vote @idea = Idea.find(params[:id]) @idea.votes += 1 @idea.save! rescue ActiveRecord::StaleObjectError retry end चेक-तब-एक्ट उदाहरण के लिए, यह कॉलम पर एक अद्वितीय इंडेक्स के साथ किया जा सकता है, फिर डेटा को जारी रखते समय अपवाद को बचाया जा सकता है। add_index :users, [:email], unique: true एक अद्वितीय इंडेक्स के साथ, यदि उस के साथ डेटाबेस में डेटा पहले से मौजूद है, तो रेल एक त्रुटि उत्पन्न करेगी और उसे बचाया जा सकता है और उचित रूप से प्रबंधित किया जा सकता है। email ActiveRecord::RecordNotUnique begin user = User.create(email: "demo@example.com") rescue ActiveRecord::RecordNotUnique user = User.find_by(email: "demo@example.com") end नपुंसकता कार्रवाइयों को पुनः प्रयास करने के लिए, यह महत्वपूर्ण है कि संपूर्ण ऑपरेशन निष्क्रिय हो। इसका मतलब यह है कि यदि कोई ऑपरेशन कई बार किया जाता है, तो परिणाम वैसा ही होगा जैसे कि इसे केवल एक बार लागू किया गया हो। उदाहरण के लिए, कल्पना करें कि यदि किसी नौकरी ने एक ईमेल भेजा हो और जब भी किसी विचार के वोट बदले गए हों तो उसे निष्पादित किया जाए। यह सचमुच बहुत बुरा होगा यदि प्रत्येक पुनः प्रयास के लिए एक ईमेल भेजा जाए। ऑपरेशन को निष्प्रभावी बनाने के लिए, आप संपूर्ण वोटिंग ऑपरेशन पूरा होने तक ईमेल भेजना बंद कर सकते हैं। वैकल्पिक रूप से, आप उस प्रक्रिया के कार्यान्वयन को अपडेट कर सकते हैं जो ईमेल भेजती है ताकि ईमेल केवल तभी भेजा जा सके जब वोट पिछली बार भेजे जाने के बाद बदल गए हों। यदि दौड़ की स्थिति उत्पन्न होती है और आपको पुनः प्रयास करने की आवश्यकता होती है, तो ईमेल भेजने के पहले प्रयास के परिणामस्वरूप नो-ऑप हो सकता है और इसे फिर से ट्रिगर करना सुरक्षित है। कई ऑपरेशन निष्प्रभावी नहीं हो सकते हैं - जैसे पृष्ठभूमि कार्य को सूचीबद्ध करना, ईमेल भेजना, या तृतीय-पक्ष कॉल करना। एपीआई को 3. कोड को सुरक्षित रखें यदि आप पता नहीं लगा सकते और पुनर्प्राप्त नहीं कर सकते, तो आप कोड को सुरक्षित रखने का प्रयास कर सकते हैं। यहां लक्ष्य एक ऐसा अनुबंध बनाना है जहां एक समय में केवल एक ही प्रक्रिया साझा संसाधन तक पहुंच सके। प्रभावी रूप से, आप समवर्तीता को हटा रहे हैं - चूंकि केवल एक प्रक्रिया ही साझा संसाधन तक पहुंच सकती है, हम अधिकांश दौड़ स्थितियों से बच सकते हैं। हालाँकि, व्यापार यह है कि जितनी अधिक समवर्तीता हटा दी जाएगी, आवेदन उतना ही धीमा हो सकता है क्योंकि अन्य प्रक्रियाएँ तब तक प्रतीक्षा करेंगी जब तक उन्हें पहुँच की अनुमति नहीं मिल जाती। इसे रेल्स के साथ निर्मित निराशावादी लॉकिंग का उपयोग करके नियंत्रित किया जा सकता है। का उपयोग करने के लिए, आप बनाई जा रही क्वेरी में जोड़ सकते हैं, और रेल डेटाबेस को उन रिकॉर्ड्स पर एक पंक्ति लॉक रखने के लिए कहेगी। डेटाबेस तब तक किसी अन्य प्रक्रिया को लॉक प्राप्त करने से रोक देगा जब तक कि यह पूरा न हो जाए। में कोड को लपेटना सुनिश्चित करें ताकि डेटाबेस को पता चले कि लॉक कब जारी करना है। निराशावादी लॉकिंग lock transaction Idea.transaction do @idea = Idea.lock.find(params[:id]) @idea.votes += 1 @idea.save! end यदि पंक्ति-स्तरीय लॉकिंग संभव नहीं है, तो Redlock या with_advisory_lock जैसे अन्य उपकरण हैं जिनका उपयोग किया जा सकता है। ये कोड के एक मनमाने ब्लॉक को लॉक करने की अनुमति देंगे। इसका उपयोग करना कुछ इस तरह सरल हो सकता है: email = "demo@example.com" User.with_advisory_lock("user_uniqueness_#{email}"} do User.find_or_create_by(email: email) end ये रणनीतियाँ लॉक प्राप्त होने तक प्रक्रियाओं को प्रतीक्षा करने का कारण बनेंगी। इसलिए, किसी प्रक्रिया को हमेशा के लिए प्रतीक्षा करने से रोकने के लिए वे कुछ प्रकार का टाइमआउट भी चाहेंगे - साथ ही टाइमआउट की स्थिति में क्या करना है इसके लिए कुछ प्रबंधन भी चाहेंगे। हालाँकि दौड़ की स्थितियों को ठीक करने का कोई रामबाण इलाज नहीं है, लेकिन इन रणनीतियों के माध्यम से दौड़ की कई स्थितियों को ठीक किया जा सकता है। हालाँकि, प्रत्येक समस्या थोड़ी अलग है, इसलिए समाधान का विवरण भिन्न हो सकता है। आप पर एक नज़र डाल सकते हैं जो दौड़ की स्थितियों के बारे में अधिक विस्तार से बताती है। RailsConf 2023 से मेरी बातचीत लेखक के बारे में काइल डी'ओलिवेरा काइल को अमूर्त विचारों को सॉफ्टवेयर के कामकाजी टुकड़ों में बदलने का शौक है। वह । जब काइल विकास नहीं कर रहा होता है, तो वह कनाडा के वैंकूवर में अपने घर के पास अद्भुत भोजन और शिल्प ब्रुअरीज का आनंद लेता है। अहा में एक प्रमुख सॉफ्टवेयर इंजीनियर हैं! - दुनिया का #1 उत्पाद विकास सॉफ्टवेयर यहाँ भी प्रकाशित किया गया।