कभी-कभी अनोखी स्थितियां और हमारे नियंत्रण से बाहर की चीजें बेतहाशा अपरंपरागत आवश्यकताओं की ओर ले जाती हैं। हाल ही में, मेरे पास एक अनुभव था जहां मुझे किसी भी रिकॉर्ड के लिए डेटाबेस आईडी पर भरोसा किए बिना ActiveRecord का उपयोग करने की आवश्यकता थी। अगर कोई ऐसा करने पर विचार कर रहा है, तो मैं अत्यधिक दूसरा तरीका खोजने की सलाह देता हूं! लेकिन, चलिए बाकी की कहानी पर चलते हैं।
निर्णय किए गए। छोटे डेटाबेस (संरचना में क्लोन लेकिन डेटा में नहीं) को मर्ज करने की आवश्यकता है। मैं प्रोजेक्ट में उसी तरह शामिल हुआ जैसे टीम एक स्क्रिप्ट पर अंतिम रूप दे रही थी जो डेटाबेस रिकॉर्ड को एक डेटाबेस से दूसरे डेटाबेस में कॉपी और पेस्ट करती है। इसने आईडी सहित, सब कुछ ठीक वैसा ही कॉपी किया जैसा है।
डेटाबेस A
आईडी | <थ>फलuser_id | |
---|---|---|
... | ... | ... |
123 | नारंगी | 456 |
... | ... | ... |
डेटाबेस बी
आईडी | <थ>फलuser_id | |
---|---|---|
... | ... | ... |
123 | केला | 74 |
... | ... | ... |
डेटाबेस ए मर्ज के बाद
आईडी | <थ>फलuser_id | |
---|---|---|
... | ... | ... |
123 | नारंगी | 456 |
123 | केला | 74 |
... | ... | ... |
यह आईडी होने के मूल कारण को तोड़ता है:विशिष्ट पहचान। मैं विशिष्टताओं को नहीं जानता था, लेकिन मुझे लगा कि सिस्टम में डुप्लीकेट आईडी पेश किए जाने के बाद सभी प्रकार की समस्याएं दिखाई देंगी। मैंने कुछ कहने की कोशिश की, लेकिन मैं इस परियोजना के लिए नया था, और दूसरों को यकीन था कि यह आगे का सबसे अच्छा रास्ता था। कुछ दिनों में, हम कोड को लागू करने जा रहे थे और डुप्लिकेट आईडी के साथ डेटा को संभालना शुरू कर दिया था। अब सवाल यह नहीं था, "क्या हमें यह करना चाहिए?"; इसके बजाय, सवाल थे, "हम यह कैसे करते हैं?" और "इसमें कितना समय लगेगा?"
डुप्लिकेट आईडी के साथ कार्य करना
तो, आप डुप्लीकेट आईडी वाले डेटा को कैसे हैंडल करते हैं? समाधान कई क्षेत्रों की एक समग्र आईडी बनाना था। हमारे अधिकांश डीबी फ़ेच इस तरह दिखते थे:
# This doesn't work, there may be 2 users with id: 123
FavoriteFruit.find(123)
# Multiple IDs scope the query to the correct record
FavoriteFruit.find_by(id: 123, user_id: 456)
सभी ActiveRecord कॉल इस तरह से अपडेट किए गए थे, और जैसा कि मैंने कोड के माध्यम से देखा, यह समझ में आया। जब तक हम इसे तैनात नहीं करते।
सभी नरक टूट जाते हैं
कोड लागू करने के कुछ ही समय बाद, फोन बजने लगे। ग्राहक ऐसे नंबर देख रहे थे जो जुड़ नहीं रहे थे। वे अपने स्वयं के रिकॉर्ड अपडेट नहीं कर सके। सभी प्रकार की सुविधाएँ टूट रही थीं।
क्या करे? हमने केवल कोड परिनियोजित नहीं किया; हमने डेटा को एक डेटाबेस से दूसरे डेटाबेस में भी स्थानांतरित किया (और हमारे द्वारा तैनात किए जाने के बाद नया डेटा बनाया/अपडेट किया गया)। यह एक साधारण रोलबैक स्थिति नहीं थी। हमें चीजों को तेजी से ठीक करने की जरूरत थी।
रेल क्या कर रही है?
डिबगिंग में पहला कदम यह देखना था कि वर्तमान व्यवहार क्या था और त्रुटि को कैसे पुन:उत्पन्न किया जाए। मैंने उत्पादन डेटा का क्लोन लिया और रेल कंसोल शुरू किया। आपके सेटअप के आधार पर, जब आप ActiveRecord क्वेरी निष्पादित करते हैं, तो आप स्वचालित रूप से SQL क्वेरी को रेल नहीं देख सकते हैं। यह सुनिश्चित करने का तरीका यहां बताया गया है कि SQL कथन आपके कंसोल पर दिखाई दे रहे हैं:
ActiveRecord::Base.logger = Logger.new(STDOUT)
उसके बाद, मैंने कुछ सामान्य रेल प्रश्नों की कोशिश की:
$ FavoriteFruit.find_by(id: 123, user_id: 456)
FavoriteFruit Load (0.6ms)
SELECT "favorite_fruits".*
FROM "favorite_fruits"
WHERE "favorite_fruits"."id" = $1
AND "favorite_fruits"."user_id" = $2
[["id", "123"], ["user_id", "456"]]
find_by
ठीक काम करने लग रहा था, लेकिन फिर मैंने कुछ इस तरह का कोड देखा:
fruit = FavoriteFruit.find_by(id: 123, user_id: 456)
...
...
fruit.reload
वह reload
मुझे जिज्ञासु बनाया, इसलिए मैंने उसका भी परीक्षण किया:
$ fruit.reload
FavoriteFruit Load (0.3ms)
SELECT "favorite_fruits".*
FROM "favorite_fruits"
WHERE "favorite_fruits"."id" = $1
LIMIT $2
[["id", 123], ["LIMIT", 1]]
उह ओह। इसलिए, भले ही हमने शुरू में find_by
. के साथ सही रिकॉर्ड प्राप्त किया हो , जब भी हम reload
. कहते हैं , यह रिकॉर्ड की आईडी लेगा और एक साधारण खोज-दर-आईडी क्वेरी करेगा, जो निश्चित रूप से, हमारे डुप्लिकेट आईडी के कारण अक्सर गलत डेटा देगा।
ऐसा क्यों किया? मैंने सुराग के लिए रेल स्रोत कोड की जांच की। यह रूबी ऑन रेल्स के साथ कोडिंग का एक बड़ा पहलू है, स्रोत कोड सादा रूबी है और एक्सेस के लिए स्वतंत्र रूप से उपलब्ध है। मैंने बस "ActiveRecord पुनः लोड" को गुगल किया और जल्दी से यह पाया:
# File activerecord/lib/active_record/persistence.rb, line 602
def reload(options = nil)
self.class.connection.clear_query_cache
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
else
self.class.unscoped { self.class.find(id) }
end
@attributes = fresh_object.instance_variable_get("@attributes")
@new_record = false
self
end
यह दर्शाता है कि reload
कमोबेश, self.class.find(id)
. के लिए एक आवरण है . इस पद्धति में केवल एक आईडी द्वारा क्वेरी करना हार्डवायर्ड था। डुप्लिकेट आईडी के साथ काम करने के लिए, हमें या तो कोर रेल विधियों को ओवरराइड करना होगा (कभी अनुशंसित नहीं) या reload
का उपयोग करना बंद करना होगा पूरी तरह से।
हमारा समाधान
इसलिए, हमने प्रत्येक reload
. के माध्यम से जाने का निर्णय लिया कोड में और इसे find_by
. में बदलें डेटाबेस को एकाधिक कुंजियों के माध्यम से लाने के लिए।
हालाँकि, यह केवल कुछ बगों का समाधान था। अधिक खुदाई के बाद, मैंने अपने update
. का परीक्षण करने का निर्णय लिया कॉल:
$ fruit = FavoriteFruit.find_by(id: 123, user_id: 456)
$ fruit.update(last_eaten: Time.now)
FavoriteFruit Update (43.3ms)
UPDATE "favorite_fruits"
SET "last_eaten" = $1
WHERE "favorite_fruits"."id" = $2
[["updated_at", "2020-04-16 06:24:57.989195"], ["id", 123]]
उह ओह। आप देख सकते हैं कि भले ही find_by
जब हमने update
. को कॉल किया, तो विशिष्ट क्षेत्रों द्वारा रिकॉर्ड को स्कोप किया रेल रिकॉर्ड पर, इसने एक साधारण WHERE id = x
. बनाया क्वेरी, जो डुप्लिकेट आईडी के साथ भी टूट जाती है। हम इसके आसपास कैसे पहुंचे?
हमने एक कस्टम अपडेट विधि बनाई है, update_unique
, जो इस तरह दिखता है:
class FavoriteFruit
def update_unique(attributes)
run_callbacks :save do
self.class
.where(id: id, user_id: user_id)
.update_all(attributes)
end
self.class.find_by(id: id, user_id: user_id)
end
end
जो हमें आईडी से अधिक के दायरे में रिकॉर्ड अपडेट करने देता है:
$ fruit.update_unique(last_eaten: Time.now)
FavoriteFruit Update All (3.2ms)
UPDATE "favorite_fruits"
SET "last_eaten" = '2020-04-16 06:24:57.989195'
WHERE "favorite_fruits"."id" = $1
AND "favorite_fruits"."user_id" = $2
[["id", "123"], ["user_id", "456"]]
इस कोड ने रिकॉर्ड्स को अपडेट करने की एक सीमित गुंजाइश सुनिश्चित की, लेकिन क्लास के update_all
को कॉल करके विधि, हमने कॉलबैक खो दिया है जो आम तौर पर एक रिकॉर्ड को अपडेट करने के साथ आता है। इसलिए, हमें मैन्युअल रूप से कॉलबैक चलाना पड़ा और update_all
के बाद से अपडेट किए गए रिकॉर्ड को पुनः प्राप्त करने के लिए एक और डेटाबेस कॉल करना पड़ा। अद्यतन रिकॉर्ड वापस नहीं करता है। अंतिम उत्पाद भी नहीं है गन्दा, लेकिन यह निश्चित रूप से fruit.update
. की तुलना में पढ़ना अधिक कठिन है ।
असली समाधान
धँसी हुई लागत, प्रबंधन और समय की कमी के कारण, हमारा समाधान सभी डेटाबेस कॉल के लिए कई कुंजियों का उपयोग करने के लिए मंकी पैच रेल्स का था। इसने काम किया, इस अर्थ में कि ग्राहक अभी भी उत्पाद खरीदेंगे और उसका उपयोग करेंगे, लेकिन यह कई कारणों से एक बुरा विचार था:
- भविष्य में कोई भी विकास सामान्य रेल विधियों का उपयोग करके अनजाने में बग को फिर से प्रस्तुत कर सकता है। नए डेवलपर्स को कोड को छिपे हुए बग से मुक्त रखने के लिए सख्त प्रशिक्षण की आवश्यकता होगी, जैसे कि
reload
. का उपयोग करना विधि। - कोड अधिक जटिल, कम स्पष्ट और कम रखरखाव योग्य है। यह तकनीकी ऋण है जो परियोजना के आगे बढ़ने के साथ-साथ विकास की गति को धीमा कर देता है।
- परीक्षण बहुत धीमा हो जाता है। आपको न केवल यह जांचने की आवश्यकता है कि कोई फ़ंक्शन काम करता है बल्कि यह भी काम करता है कि जब विभिन्न ऑब्जेक्ट्स में डुप्लिकेट आईडी हों। परीक्षण लिखने में अधिक समय लगता है, और फिर हर बार जब परीक्षण सूट चलाया जाता है, तो सभी अतिरिक्त परीक्षणों को चलाने में अधिक समय लगता है। यदि प्रोजेक्ट का प्रत्येक डेवलपर सभी संभावित परिदृश्यों का सावधानीपूर्वक परीक्षण नहीं करता है, तो परीक्षण में आसानी से बग छूट सकते हैं।
इस समस्या का असली समाधान यह है कि पहली बार में कभी भी डुप्लीकेट आईडी न हों। यदि डेटा को एक डेटाबेस से दूसरे डेटाबेस में स्थानांतरित करने की आवश्यकता है, तो ऐसा करने वाली स्क्रिप्ट को आईडी के बिना डेटा एकत्र करना और सम्मिलित करना चाहिए, जिससे प्राप्त करने वाले डेटाबेस को प्रत्येक रिकॉर्ड को अपनी विशिष्ट आईडी देने के लिए अपने मानकीकृत ऑटो-इंक्रीमेंट काउंटर का उपयोग करने की अनुमति मिलती है।
एक अन्य समाधान सभी रिकॉर्ड के लिए यूयूआईडी का उपयोग करना होगा। इस प्रकार की आईडी यादृच्छिक रूप से बनाए गए वर्णों की एक लंबी स्ट्रिंग है (चरण-दर-चरण गणना के बजाय, एक पूर्णांक आईडी के साथ)। फिर, डेटा को अन्य डेटाबेस में ले जाने से कोई विरोध या समस्या नहीं होगी।
लब्बोलुआब यह है कि रेल को इस समझ के साथ बनाया गया था कि आईडी प्रति रिकॉर्ड अद्वितीय हैं और डेटाबेस में विशिष्ट डेटा में हेरफेर करने का एक त्वरित और आसान तरीका है। रेल एक विचारित ढांचा है, और इसकी सुंदरता यह है कि जब तक आप चीजों को करने के रेल तरीके से चिपके रहते हैं, तब तक सब कुछ कितनी आसानी से चलता है। यह न केवल रेल पर बल्कि प्रोग्रामिंग के कई अन्य पहलुओं पर भी लागू होता है। जब चीजें जटिल हो जाती हैं, तो हमें पता होना चाहिए कि समस्या की पहचान कैसे करें; हालांकि, अगर हम स्पष्ट, अनुरक्षणीय और पारंपरिक कोड लिखते हैं, तो हम इनमें से कई जटिलताओं से बच सकते हैं।