কখনও কখনও অনন্য পরিস্থিতি এবং আমাদের নিয়ন্ত্রণের বাইরের জিনিসগুলি অত্যন্ত অপ্রথাগত প্রয়োজনীয়তার দিকে নিয়ে যায়। সম্প্রতি, আমার একটি অভিজ্ঞতা ছিল যেখানে আমাকে কোনো রেকর্ডের জন্য ডাটাবেস আইডির উপর নির্ভর না করে ActiveRecord ব্যবহার করতে হবে। কেউ যদি একই কাজ বিবেচনা করে, আমি অত্যন্ত অন্য উপায় খোঁজার সুপারিশ! তবে, গল্পের বাকি অংশে যাওয়া যাক।
সিদ্ধান্ত নেওয়া হয়েছিল। ছোট ডেটাবেস (কাঠামোতে ক্লোন কিন্তু ডেটাতে নয়) মার্জ করা দরকার। আমি প্রজেক্টে যোগ দিয়েছিলাম ঠিক যেভাবে দলটি একটি স্ক্রিপ্টে ফিনিশিং টাচ দিচ্ছে যা ডাটাবেস রেকর্ডগুলিকে এক ডাটাবেস থেকে অন্য ডাটাবেসে কপি করে পেস্ট করে। এটি আইডি সহ সবকিছু হুবহু কপি করেছে।
ডাটাবেস A
id | ফল | user_id |
---|---|---|
... | ... | ... |
123 | কমলা | 456 |
... | ... | ... |
ডেটাবেস B
id | ফল | user_id |
---|---|---|
... | ... | ... |
123 | কলা | 74 |
... | ... | ... |
একত্রিত হওয়ার পরে ডেটাবেস A
id | ফল | 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
ব্যবহার করা পদ্ধতি। - কোডটি আরও জটিল, কম পরিষ্কার এবং কম রক্ষণাবেক্ষণযোগ্য। এটি এমন প্রযুক্তিগত ঋণ যা উন্নয়নের গতিকে আরও বেশি করে কমিয়ে দেয় যখন প্রকল্পটি এগিয়ে যায়।
- পরীক্ষা অনেক ধীর হয়ে যায়। আপনাকে পরীক্ষা করতে হবে যে শুধুমাত্র একটি ফাংশন কাজ করে কিন্তু এটি কাজ করে যখন বিভিন্ন বস্তুর ডুপ্লিকেট আইডি থাকে। পরীক্ষা লিখতে আরও সময় লাগে, এবং তারপর প্রতিবার টেস্ট স্যুট চালানো হলে, সমস্ত অতিরিক্ত পরীক্ষার মাধ্যমে চালানোর জন্য আরও সময় লাগে। যদি প্রকল্পের প্রতিটি বিকাশকারী সমস্ত সম্ভাব্য পরিস্থিতিতে সাবধানতার সাথে পরীক্ষা না করে তবে পরীক্ষা করা সহজেই বাগগুলি মিস করতে পারে৷
এই সমস্যার আসল সমাধান হল প্রথম স্থানে কখনই ডুপ্লিকেট আইডি না থাকা। যদি একটি ডাটাবেস থেকে অন্য ডাটাবেসে ডেটা স্থানান্তর করার প্রয়োজন হয়, তবে স্ক্রিপ্টটি যেটি করছে তা আইডি ছাড়াই ডেটা সংগ্রহ এবং সন্নিবেশ করা উচিত, গ্রহীতা ডাটাবেসকে প্রতিটি রেকর্ডকে তার নিজস্ব অনন্য আইডি দেওয়ার জন্য তার স্ট্যান্ডার্ডাইজড অটো-ইনক্রিমেন্ট কাউন্টার ব্যবহার করার অনুমতি দেয়।
আরেকটি সমাধান হল সমস্ত রেকর্ডের জন্য UUID ব্যবহার করা। এই ধরনের আইডি হল অক্ষরগুলির একটি দীর্ঘ স্ট্রিং যা এলোমেলোভাবে তৈরি করা হয় (একটি পূর্ণসংখ্যা আইডির মতো ধাপে ধাপে গণনার পরিবর্তে)। তারপরে, অন্যান্য ডাটাবেসে ডেটা স্থানান্তর করা হলে কোন দ্বন্দ্ব বা সমস্যা থাকবে না।
মূল কথা হল রেলগুলি এই বোঝার সাথে তৈরি করা হয়েছিল যে আইডিগুলি রেকর্ড প্রতি অনন্য এবং ডাটাবেসের নির্দিষ্ট ডেটা ম্যানিপুলেট করার একটি দ্রুত এবং সহজ উপায়৷ রেলগুলি একটি মতামতযুক্ত কাঠামো, এবং এর সৌন্দর্য হল সবকিছু কতটা মসৃণভাবে চলে, যতক্ষণ না আপনি কাজ করার রেল পদ্ধতিতে লেগে থাকেন। এটি শুধুমাত্র রেলের ক্ষেত্রেই নয়, প্রোগ্রামিংয়ের অন্যান্য অনেক ক্ষেত্রেও প্রযোজ্য। যখন বিষয়গুলি জটিল হয়ে যায়, তখন আমাদের জানা উচিত কীভাবে সমস্যাটি সনাক্ত করতে হয়; যাইহোক, যদি আমরা পরিষ্কার, রক্ষণাবেক্ষণযোগ্য এবং প্রচলিত কোড লিখি, তাহলে আমরা প্রথমে এই জটিলতার অনেকগুলি এড়াতে পারি।