রেল কাঠামোর সৌন্দর্যগুলির মধ্যে একটি হল আপনার মডেলগুলিতে রুবি অন রেল অ্যাসোসিয়েশনগুলি ব্যবহার করার ক্ষমতা। এই অ্যাসোসিয়েশনগুলি আপনাকে অন্তর্নিহিত SQL প্রশ্নগুলি লেখার প্রয়োজনীয়তাকে বিমূর্ত করে, মনোরম সিনট্যাক্স সহ আপনার কোডে রেকর্ডের সংগ্রহগুলি অ্যাক্সেস করার অনুমতি দেয়। আপনার সমস্ত ডেটা এক জায়গায় থাকা পর্যন্ত এই বিমূর্ততা ধরে রাখে। যে মুহুর্তে আপনার টেবিলগুলি পৃথক ডাটাবেস ক্লাস্টার জুড়ে ছড়িয়ে পড়ে, নির্দিষ্ট অ্যাসোসিয়েশনের ধরন কাজ করা বন্ধ করে দেয়৷
এই নিবন্ধটি সেই সীমানাটি ঠিক কোথায় এবং এর মধ্যে কাজ করার জন্য রেলগুলি কী প্রদান করে তার মধ্য দিয়ে চলে। আমরা কেন সমস্যাটি ঘটে এবং রেলের কোন সংস্থাগুলি প্রভাবিত হয় তা দিয়ে শুরু করি এবং ডাটাবেস কনফিগারেশন এবং মডেল শ্রেণিবিন্যাসে চলে যাই যা একাধিক ক্লাস্টার এবং অনেকগুলি সম্পর্কের সমর্থন করে৷ সেখান থেকে আমরা কভার করব কিভাবে বিভিন্ন ডেটা অ্যাক্সেস প্যাটার্ন প্রতিটি সেই পচনের সাথে ইন্টারঅ্যাক্ট করে।
আপনি যদি একটি রেল অ্যাসোসিয়েশন টিউটোরিয়াল খুঁজছেন যা বিশেষভাবে মাল্টি-ডাটাবেস সেটআপগুলিকে কভার করে, এটিই। আমরা আরও অনেক বিষয়ে আলোচনা করব, তাই চারপাশে লেগে থাকুন।
কেন ডাটাবেস বিভিন্ন ক্লাস্টারে শেষ হয়
যখন একটি Rails অ্যাপ্লিকেশন একটি একক ডাটাবেসে তার সমস্ত ডেটা সঞ্চয় করে, তখন অ্যাক্টিভ রেকর্ড অ্যাসোসিয়েশনগুলি স্বচ্ছভাবে পরিচালনা করা হয় এবং আপনি কখনই অন্তর্নিহিত SQL সম্পর্কে ভাবেন না। যে মুহূর্তে আপনার ডেটা একাধিক ডাটাবেস ক্লাস্টার জুড়ে থাকে, সেই স্বচ্ছতা ভেঙে যায়। একটি JOIN একই ডাটাবেস সার্ভারে উভয় টেবিল বিদ্যমান থাকা প্রয়োজন। ক্লাস্টার জুড়ে একটি চেষ্টা করা একটি ActiveRecord::StatementInvalid তৈরি করে এই মত ত্রুটি:
ActiveRecord::StatementInvalid (Table 'people_cluster.humans' doesn't exist)
এটি একটি কনফিগারেশন ভুল নয়। এটি একটি কঠিন শারীরিক সীমাবদ্ধতা:ডাটাবেস সার্ভার JOIN পারে না টেবিলের বিরুদ্ধে তারা হোস্ট না. আমাদের এই সমস্যাটি has_many :through এ আছে এবং has_one :through অ্যাসোসিয়েশন, কারণ সেগুলি হল অ্যাসোসিয়েশনের ধরন যা মধ্যবর্তী JOIN তৈরি করে প্রশ্ন সরাসরি has_many অথবা belongs_to সম্পর্কগুলির জন্য যোগদানের প্রয়োজন হয় না তাই তারা কোনও পরিবর্তন ছাড়াই ক্লাস্টার জুড়ে কাজ করে৷
কখন বোঝা আপনি এই বাউন্ডারি মারবেন প্রথম ধাপ। যদি একটি User accounts-এ থাকে ডাটাবেস এবং একটি Post content-এ থাকে ডাটাবেস, User has_many :posts ভাল কাজ করে কিন্তু যদি আপনি একটি মধ্যবর্তী Subscription যোগ করেন billing-এ মডেল ডাটাবেস এবং User has_many :posts, through: :subscriptions সংজ্ঞায়িত করুন , রেল subscriptions যোগদানের চেষ্টা করবে এবং posts একক প্রশ্নে সেখানেই ক্লাস্টার সীমানা একটি সমস্যা হয়ে দাঁড়ায়৷
তিন-স্তরের ডাটাবেস কনফিগারেশন
কোন মডেল কোড লেখার আগে, ডাটাবেস কনফিগারেশন মাল্টি-ক্লাস্টার লেআউট প্রতিফলিত করা প্রয়োজন। রেলগুলি config/database.yml এ তিন-স্তরের কাঠামো ব্যবহার করে এই উদ্দেশ্যে। প্রতিটি টপ-লেভেল এনভায়রনমেন্ট কী নেস্টেড ডাটাবেসের নাম ধারণ করে, এবং সেগুলির প্রত্যেকটিতে সেই ক্লাস্টারের জন্য সংযোগের বিশদ রয়েছে।
# config/database.yml
default: &default
adapter: postgresql
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
primary:
<<: *default
database: myapp_primary_dev
accounts:
<<: *default
database: myapp_accounts_dev
migrations_paths: db/accounts_migrate
content:
<<: *default
database: myapp_content_dev
migrations_paths: db/content_migrate
production:
primary:
<<: *default
database: myapp_primary_prod
username: <%= ENV['DB_USER'] %>
password: <%= ENV['DB_PASSWORD'] %>
accounts:
<<: *default
database: myapp_accounts_prod
username: <%= ENV['DB_USER'] %>
password: <%= ENV['DB_PASSWORD'] %>
content:
<<: *default
database: myapp_content_prod
username: <%= ENV['DB_USER'] %>
password: <%= ENV['DB_PASSWORD'] %>
migrations_paths আপনি যদি রেল জেনারেটর এবং db:migrate চান তাহলে কী অ-ঐচ্ছিক সঠিক ডিরেক্টরিতে মাইগ্রেশন রুট করতে। এটি ছাড়া, সমস্ত স্থানান্তর ডিফল্ট db/migrate এবং প্রাথমিক ডাটাবেসে প্রয়োগ করুন। প্রতিটি সেকেন্ডারি ডাটাবেসের একটি সংশ্লিষ্ট বিমূর্ত রেকর্ড ক্লাস থাকা উচিত যা রেল মডেলগুলি থেকে উত্তরাধিকারসূত্রে পাওয়া যায়। আপনি --database পাস করলে জেনারেটরগুলি স্বয়ংক্রিয়ভাবে এটি পরিচালনা করে পতাকা:
rails generate model Subscription plan:string --database accounts
এটি একটি AccountsRecord তৈরি করে ক্লাস যদি একটি ইতিমধ্যে বিদ্যমান না থাকে, এবং উত্পন্ন Subscription মডেল এটি থেকে উত্তরাধিকারসূত্রে পাওয়া যায়৷
বিমূর্ত রেকর্ড ক্লাস এবং সংযোগ রাউটিং
বিমূর্ত রেকর্ড ক্লাসগুলি হল পদ্ধতি যা রেলগুলি সঠিক ক্লাস্টারে রুট কোয়েরির জন্য ব্যবহার করে। প্রত্যেকে connects_to কল করে লেখা এবং পড়ার ক্রিয়াকলাপের জন্য এটি কোন ডাটাবেসের সাথে মানচিত্র তৈরি করে তা ঘোষণা করতে। এই শ্রেণিবিন্যাসে আপনার অ্যাপ্লিকেশনে সাধারণত তিনটি স্তর থাকবে৷
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :primary }
end
# app/models/accounts_record.rb
class AccountsRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :accounts, reading: :accounts }
end
# app/models/content_record.rb
class ContentRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :content, reading: :content }
end
ব্যবহারকারী মডেল এই শ্রেণিবিন্যাস বোঝার জন্য একটি ভাল অ্যাঙ্কর। এটি accounts-এ থাকে AccountsRecord থেকে ক্লাস্টার এবং উত্তরাধিকার . content-এ মডেলগুলি৷ ContentRecord থেকে ক্লাস্টার ইনহেরিট . বাকি সবকিছু ApplicationRecord থেকে উত্তরাধিকার সূত্রে প্রাপ্ত এবং প্রাথমিক ডাটাবেসে আঘাত করে। এই উত্তরাধিকার শৃঙ্খল কিভাবে সক্রিয় রেকর্ড নির্ধারণ করে যে কোন সংযোগ পুলটি একটি অনুসন্ধান চালানোর সময় ব্যবহার করতে হবে। এটি connects_to নামে একটি ক্লাস খুঁজে না পাওয়া পর্যন্ত এটি ক্লাসের অনুক্রমের উপরে চলে যায় . 
একটি সাধারণ ভুল হল establish_connection কল করা বিমূর্ত ক্লাস ব্যবহার করার পরিবর্তে পৃথক মডেলগুলিতে। প্রতিটি establish_connection কল একটি পৃথক সংযোগ পুল খোলে। যদি আপনার accounts-এ 50টি মডেল থাকে ডেটাবেস, প্রতিটি কলিং establish_connection , আপনি একই সার্ভারের দিকে নির্দেশ করে 50টি সংযোগ পুল দিয়ে শেষ করবেন৷ বিমূর্ত ক্লাসগুলি তাদের থেকে উত্তরাধিকারসূত্রে পাওয়া সমস্ত মডেল জুড়ে একটি একক পুল ভাগ করে এটি সমাধান করে৷
রেলের ক্রস-ক্লাস্টার অ্যাসোসিয়েশনগুলি আসলে কীভাবে কাজ করে
disable_joins: true বিকল্প হল through তৈরির সরাসরি প্রক্রিয়া অ্যাসোসিয়েশনগুলি কাজ করে যখন জড়িত টেবিলগুলি বিভিন্ন ক্লাস্টারে থাকে। রেল has_many সবচেয়ে বেশি ব্যবহৃত অ্যাসোসিয়েশন টাইপ, এবং এটি ক্লাস্টার সীমানা দ্বারা সবচেয়ে সরাসরি প্রভাবিত হয়। যখন রেলগুলি কোনও অ্যাসোসিয়েশনে এই বিকল্পের মুখোমুখি হয়, তখন এটি একক JOIN ত্যাগ করে ক্যোয়ারী কৌশল এবং পরিবর্তে দুটি (বা তার বেশি) অনুক্রমিক SELECT ইস্যু করে বিবৃতি, একটি WHERE ... IN (...) এ প্রথম ক্যোয়ারী থেকে আইডিগুলিকে পাইপ করে দ্বিতীয়টিতে ধারা।
এখানে তিনটি ক্লাস্টার বিস্তৃত একটি কংক্রিট মডেল সেটআপ। নীচের মডেল সেটআপটি একটি বহু-থেকে-অনেক সম্পর্ক, একজন ব্যবহারকারী সদস্যতার মাধ্যমে পোস্টের সাথে সংযোগ স্থাপন করে এবং এটি এমন প্যাটার্ন যা ক্রস-ক্লাস্টার সমস্যাটিকে সরাসরি প্রকাশ করে৷
# app/models/user.rb - lives in the accounts database
class User < AccountsRecord
has_many :subscriptions
has_many :posts, through: :subscriptions, disable_joins: true
end
# app/models/subscription.rb - lives in the accounts database
class Subscription < AccountsRecord
belongs_to :user
has_many :posts
end
# app/models/post.rb - lives in the content database
class Post < ContentRecord
belongs_to :subscription
end
যখন আপনি user.posts কল করেন , রেলগুলি একটি একক JOIN এর পরিবর্তে এই জোড়া প্রশ্নগুলি তৈরি করে :
-- Query 1: fetch subscription IDs from the accounts cluster
SELECT "subscriptions"."id"
FROM "subscriptions"
WHERE "subscriptions"."user_id" = 1
-- Query 2: fetch posts from the content cluster using those IDs
SELECT "posts".*
FROM "posts"
WHERE "posts"."subscription_id" IN (4, 7, 12)
প্রথম প্রশ্নটি accounts এর বিপরীতে চলে একটি প্রাথমিক কী সংগ্রহ করার জন্য ডাটাবেস। দ্বিতীয়টি content এর বিপরীতে চলে . রেলগুলি বিদেশী কীগুলি অনুসরণ করে সম্পর্ক সমাধান করে, user_id সদস্যতা এবং subscription_id-এ পোস্টে, দুটি ক্লাস্টার জুড়ে। প্রথম ক্যোয়ারী সাবস্ক্রিপশন থেকে প্রাথমিক কী মান সংগ্রহ করে, তারপর সেগুলিকে IN-এ পাঠায় দ্বিতীয় প্রশ্নের ধারা। কোন প্রশ্নই ক্রস-ক্লাস্টার যোগদানের চেষ্টা করে না। রেল অ্যাপ্লিকেশন মেমরিতে চূড়ান্ত ফলাফল সেট একত্রিত করে।
একই বিকল্প has_one :through এ একইভাবে কাজ করে :
# app/models/user.rb
class User < AccountsRecord
has_one :profile
has_one :avatar, through: :profile, disable_joins: true
end
# app/models/profile.rb - accounts database
class Profile < AccountsRecord
belongs_to :user
has_one :avatar
end
# app/models/avatar.rb - content database
class Avatar < ContentRecord
belongs_to :profile
end
user.avatar দুটি প্রশ্ন নির্বাহ করবে:একটি profile_id পেতে , কন্টেন্ট ক্লাস্টার থেকে অবতার রেকর্ড আনার জন্য আরেকটি।
যখন disable_joins স্পষ্টভাবে সেট করা আবশ্যক
রেলগুলি স্বয়ংক্রিয়ভাবে ক্লাস্টার সীমানা সনাক্ত করে না এবং disable_joins সন্নিবেশ করে তোমার জন্য অ্যাক্টিভ রেকর্ডে অ্যাসোসিয়েশন লোডিং অলস। একটি অ্যাসোসিয়েশনের জন্য এসকিউএল নির্ধারিত হয় যখন অ্যাসোসিয়েশনটি মডেলে সংজ্ঞায়িত করা হয়, এটি আসলে ট্রিগার হওয়ার সময় নয়। ততক্ষণে user.posts চালায়, রেল ইতিমধ্যেই সিদ্ধান্ত নিয়েছে একটি JOIN ব্যবহার করবে কিনা অথবা অ্যাসোসিয়েশন ঘোষণার উপর ভিত্তি করে পৃথক প্রশ্ন।
এর মানে প্রতিটি through যে অ্যাসোসিয়েশন একটি ক্লাস্টার সীমানা অতিক্রম করে তার প্রয়োজন disable_joins: true ঘোষণার উপর।
আপনার মডেল অডিট করার একটি ব্যবহারিক উপায় হল যেকোনো through: খোঁজা অ্যাসোসিয়েশন যেখানে সোর্স মডেল এবং টার্গেট মডেল বিভিন্ন বিমূর্ত রেকর্ড ক্লাস থেকে উত্তরাধিকারসূত্রে পাওয়া যায়। যদি User < AccountsRecord এবং Post < ContentRecord , তারপর has_many :posts, through: :subscriptions প্রয়োজন disable_joins: true যেখানেই হোক না কেন Subscription জীবন।
ক্লাস্টার জুড়ে আগ্রহী লোড হচ্ছে
disable_joins বিকল্পটি কীভাবে অ্যাসোসিয়েশনগুলিকে লোড করা হয় তা প্রভাবিত করে, তবে এটি ক্রস-ক্লাস্টার ডেটার সাথে কীভাবে আগ্রহী লোডিং কৌশলগুলি ইন্টারঅ্যাক্ট করে তা পরিবর্তন করে না। মাল্টি-ডাটাবেস সেটআপগুলিতে N+1 প্রশ্নগুলি এড়ানোর জন্য এই পার্থক্যটি বোঝা গুরুত্বপূর্ণ৷
eager_load ক্রস-ক্লাস্টার অ্যাসোসিয়েশনের জন্য টেবিলের বাইরে। এটি একটি LEFT OUTER JOIN তৈরি করে , যার একটি নিয়মিত JOIN এর মতোই শারীরিক সীমাবদ্ধতা রয়েছে৷ , উভয় টেবিল একই সার্ভারে হতে হবে। আপনি চেষ্টা করলে User.eager_load(:posts) যেখানে পোস্টগুলি একটি আলাদা ক্লাস্টারে থাকে, আপনি একই StatementInvalid পাবেন ত্রুটি।
preload সঠিক কৌশল। এটি প্রতিটি সমিতির জন্য পৃথক প্রশ্ন জারি করে এবং রুবিতে সম্পর্ককে একত্রিত করে। এটি disable_joins এর সাথে গঠনগতভাবে অভিন্ন একটি একক রেকর্ডের জন্য করে। পার্থক্য হল স্কেল:preload সমস্ত লোড করা অভিভাবক রেকর্ড জুড়ে দ্বিতীয় ক্যোয়ারী ব্যাচ করে।
# This works across clusters.
# Query 1: SELECT "users".* FROM "users"
# Query 2: SELECT "posts".* FROM "posts" WHERE "posts"."subscription_id" IN (...)
users = User.preload(:posts).all
users.each do |user|
user.posts.each { |post| puts post.title } # No additional queries fired
end
includes সেক্ষেত্রে কাজ করবে যেখানে এটি preload কে অর্পণ করে অভ্যন্তরীণভাবে, যা এটি ডিফল্টরূপে করে যখন সংশ্লিষ্ট টেবিলের উল্লেখ করার কোনো শর্ত থাকে না। আপনি যদি একটি .where যোগ করেন অনুচ্ছেদ যা সংশ্লিষ্ট টেবিলের কলাম স্পর্শ করে, includes eager_load এ স্যুইচ করে আচরণ, এবং ক্লাস্টার জুড়ে ব্যর্থ হবে। কোন কৌশল includes নিয়ে সন্দেহ হলে নির্বাচন করবে, স্পষ্ট হবে এবং preload ব্যবহার করবে সরাসরি।
# includes delegates to preload here, works across clusters
User.includes(:posts).all
# includes switches to eager_load because of the where clause, fails across clusters
User.includes(:posts).where("posts.published = ?", true)
# Use preload + a separate where for cross-cluster filtering
User.preload(:posts).all.select { |u| u.posts.any?(&:published?) }
# Or filter in application code after loading
স্কোপড অ্যাসোসিয়েশন এবং ক্রস-ক্লাস্টার ফিল্টারিং
মাল্টি-ডাটাবেস সেটআপে আরও সূক্ষ্ম মিথস্ক্রিয়াগুলির মধ্যে একটি হল স্কোপড অ্যাসোসিয়েশন। যখন আপনি একটি has_many এ একটি সুযোগ নির্ধারণ করেন যেটি ক্লাস্টারগুলিকে অতিক্রম করে, স্কোপের SQL টার্গেট ডাটাবেসের বিরুদ্ধে চলে, উত্সের নয়৷
class User < AccountsRecord
has_many :subscriptions
has_many :published_posts,
-> { where(published: true) },
through: :subscriptions,
source: :posts,
class_name: "Post",
disable_joins: true
end
where(published: true) ধারাটি দ্বিতীয় প্রশ্নের সাথে যুক্ত হয়, যেটি content এর বিপরীতে চলে ডাটাবেস এটি সঠিক আচরণ, এবং এর অর্থ হল আপনার স্কোপগুলি সমস্যা ছাড়াই লক্ষ্য টেবিলে কলামগুলি উল্লেখ করতে পারে। আপনি যা করতে পারবেন না তা হল সেই সুযোগের মধ্যবর্তী সারণী থেকে রেফারেন্স কলামগুলি, কারণ মধ্যবর্তী ক্যোয়ারীটি ইতিমধ্যেই স্কোপড ক্যোয়ারী কার্যকর করার সময় শেষ হয়ে গেছে৷
# This will fail because subscriptions.active is not a column in the content database
has_many :active_posts,
-> { where("subscriptions.active = ?", true) },
through: :subscriptions,
source: :posts,
disable_joins: true
পরিবর্তে মধ্যবর্তী অ্যাসোসিয়েশনে একটি সুযোগ যোগ করে মধ্যবর্তী রেকর্ডগুলি ফিল্টার করুন:
class User < AccountsRecord
has_many :active_subscriptions, -> { where(active: true) }, class_name: "Subscription"
has_many :active_posts, through: :active_subscriptions, source: :posts, disable_joins: true
end
এখন subscriptions.active এ ফিল্টারিং প্রথম ক্যোয়ারীতে ঘটে, accounts এর বিপরীতে ডাটাবেস, এবং শুধুমাত্র সক্রিয় সাবস্ক্রিপশন থেকে আইডি দ্বিতীয় প্রশ্নে পাস করা হয়।
অনুভূমিক শর্ডিং এবং ক্রস-শার্ড অ্যাসোসিয়েশনগুলি
tenant_id এর মত একটি পার্টিশন কী এর উপর ভিত্তি করে একাধিক সার্ভারে একটি লজিক্যাল ডাটাবেস বিভক্ত করা ক্রস-ক্লাস্টার সমস্যার একটি দ্বিতীয় মাত্রা প্রবর্তন করে। disable_joins প্রক্রিয়া এখনও প্রযোজ্য, কিন্তু সংযোগ রাউটিং আরও জড়িত হয়ে ওঠে।
রেল connected_to প্রদান করে একটি অনুরোধের মধ্যে শার্ডগুলির মধ্যে স্যুইচ করার জন্য:
ActiveRecord::Base.connected_to(role: :writing, shard: :shard_one) do
User.find(1) # Hits shard_one
end
যখন অ্যাসোসিয়েশনগুলি ক্লাস্টার এবং শার্ড উভয়ই বিস্তৃত হয়, তখন আপনাকে শার্ড প্রসঙ্গ এবং disable_joins উভয়ই নিশ্চিত করতে হবে বিকল্প জায়গায় আছে. একটি User shard_one-এ একটি পৃথক content-এ থাকা পোস্টগুলি অ্যাক্সেস করা ডাটাবেসের এখনও একই দ্বি-কোয়েরি পচন প্রয়োজন।
রেল 8 আত্মদর্শন পদ্ধতি যোগ করেছে যা রানটাইমে শার্ড টপোলজি সম্পর্কে যুক্তিকে সহজ করে তোলে:
class ShardedBase < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
shard_one: { writing: :shard_one },
shard_two: { writing: :shard_two }
}
end
class User < ShardedBase; end
User.shard_keys # => [:shard_one, :shard_two]
User.sharded? # => true
ShardedBase.connected_to_all_shards do
User.current_shard # Yields :shard_one, then :shard_two
end
connected_to_all_shards ব্যাকগ্রাউন্ড কাজের জন্য বিশেষভাবে উপযোগী যেগুলি প্রতিটি শার্ড জুড়ে রেকর্ড প্রক্রিয়া করতে হবে। এটি ক্রমানুসারে প্রতিটি শার্ডের উপর পুনরাবৃত্তি করে, প্রতিটি ব্লক কার্যকর করার জন্য সংযোগের প্রসঙ্গ পরিবর্তন করে।
ভাড়াটে-ভিত্তিক শার্ডিংয়ের জন্য, lock: true শার্ড স্যুইচিং-এ ডিফল্ট দুর্ঘটনাজনিত ভাড়াটে হপিং মিড-রিকুয়েস্ট প্রতিরোধ করে। এটি একটি নিরাপত্তা ব্যবস্থা:একবার একটি অনুরোধ একটি ভাড়াটেদের শার্ডে রুট করা হলে, স্পষ্টভাবে lock: false পাস না করেই অ্যাপ্লিকেশন কোডটি অন্য ভাড়াটেদের শার্ডে স্যুইচ করতে পারে না। . একক ভাড়াটেদের শার্ডের মধ্যে ক্রস-ক্লাস্টার অ্যাসোসিয়েশনগুলি এখনও disable_joins ব্যবহার করে অ্যাসোসিয়েশনগুলির জন্য যেগুলি একটি ভিন্ন ডাটাবেস ক্লাস্টারকে স্পর্শ করে৷
ক্রস-ক্লাস্টার অ্যাসোসিয়েশন পরীক্ষা করা হচ্ছে
মাল্টি-ডাটাবেস সেটআপ পরীক্ষা করার জন্য আপনার পরীক্ষার পরিবেশ উৎপাদন ডাটাবেস টপোলজির প্রতিফলন প্রয়োজন। রেলের পরীক্ষার কাঠামো এটিকে সমর্থন করে, তবে কনফিগারেশনটি অবশ্যই স্পষ্ট হতে হবে।
database.yml-এ প্রতিটি ডাটাবেস একটি test প্রয়োজন৷ পরিবেশ ব্লক। ফিক্সচার এবং ফ্যাক্টরি-ভিত্তিক পরীক্ষার ডেটা অবশ্যই সঠিক ডাটাবেসকে লক্ষ্য করতে হবে। যদি একটি User কারখানা accounts-এ একটি রেকর্ড তৈরি করে ডাটাবেস এবং একটি Post কারখানা content-এ একটি তৈরি করে , তাদের মধ্যে অ্যাসোসিয়েশন তখনই কাজ করে যখন উভয় রেকর্ড একই পরীক্ষা লেনদেনের মধ্যে তাদের নিজ নিজ ডাটাবেসে বিদ্যমান থাকে৷
রেল প্রতিটি পরীক্ষাকে ডিফল্টরূপে একটি লেনদেনে মোড়ানো, কিন্তু সেই লেনদেনটি প্রতি-সংযোগ। একাধিক ডাটাবেসের সাথে, প্রতিটি সংযোগ তার নিজস্ব লেনদেন পায়। এর মানে পরীক্ষা পরিষ্কার (প্রতিটি পরীক্ষার শেষে স্বয়ংক্রিয় রোলব্যাক) প্রতিটি ডাটাবেসে স্বাধীনভাবে ঘটে। যদি আপনার পরীক্ষা একটি User লিখে accounts এ এবং একটি Post content এ , উভয়ই রোল ব্যাক করা হবে, কিন্তু শুধুমাত্র যদি টেস্ট ফ্রেমওয়ার্ক উভয় সংযোগ সম্পর্কে জানে।
fixtures ঘোষণা স্বয়ংক্রিয়ভাবে এটি পরিচালনা করে যখন মডেলগুলি সঠিক বিমূর্ত শ্রেণি থেকে উত্তরাধিকারী হয়। কারখানা-ভিত্তিক সেটআপের জন্য (ফ্যাক্টরিবট, ফ্যাব্রিকেটর), প্রতিটি কারখানার create নিশ্চিত করুন মডেলের নিজস্ব connects_to দিয়ে কৌশলটি সঠিক ডাটাবেসকে আঘাত করে রাউটিং কাজ করে।
# spec/factories/users.rb
FactoryBot.define do
factory :user do
# User inherits from AccountsRecord and writes to accounts DB automatically
name { Faker::Name.name }
end
end
# spec/factories/posts.rb
FactoryBot.define do
factory :post do
# Post inherits from ContentRecord and writes to content DB automatically
association :subscription
title { Faker::Lorem.sentence }
end
end
ক্রস-ক্লাস্টার অ্যাসোসিয়েশনগুলি প্রত্যাশিত সংখ্যক প্রশ্নগুলি ফায়ার করছে তা যাচাই করতে, sql.active_record-এ সদস্যতা নিন বিজ্ঞপ্তি:
# spec/support/query_counter.rb
module QueryCounter
def assert_query_count(expected, &block)
count = 0
callback = ->(_name, _start, _finish, _id, payload) do
count += 1 unless payload[:name] == "SCHEMA" || payload[:sql].start_with?("EXPLAIN")
end
ActiveSupport::Notifications.subscribed(callback, "sql.active_record", &block)
assert_equal expected, count, "Expected #{expected} queries, got #{count}"
end
end
একটি has_many :through disable_joins: true সহ একটি একক রেকর্ডে ঠিক 2টি প্রশ্ন তৈরি করা উচিত। যদি আপনি 1 দেখতে পান, যোগদানের চেষ্টা করা হচ্ছে (এবং পৃথক সার্ভারের বিরুদ্ধে উৎপাদনে ব্যর্থ হবে)। আপনি যদি N+1 দেখেন, তাহলে আশানুরূপ লোডিং কাজ করছে না।
কিছু সতর্কতা
disable_joins অ্যাসোসিয়েশন লোডিং সমস্যা সমাধান করে, কিন্তু এটি ক্যোয়ারী চেইনিং পর্যন্ত প্রসারিত করে না। আপনি .where চেইন করতে পারবেন না , .order , অথবা .group একটি একক অ্যাক্টিভ রেকর্ড রিলেশনে ক্লাস্টার জুড়ে কলাম উল্লেখ করে এমন ধারা:
# This does not work, you cannot filter products by order columns across clusters
customer.purchased_products.where("orders.total > ?", 100)
একাধিক ক্লাস্টারে ডেটার উপর ভিত্তি করে ফিল্টার বা বাছাই করা প্রয়োজন এমন প্রশ্নের জন্য, সেগুলিকে ম্যানুয়ালি পচন। একটি ক্লাস্টার থেকে আপনার প্রয়োজনীয় আইডি বা মানগুলি আনুন, তারপর অন্যটির বিরুদ্ধে একটি ক্যোয়ারীতে ইনপুট হিসাবে ব্যবহার করুন:
high_value_order_ids = Order.where(customer_id: customer.id)
.where("total > ?", 100)
.pluck(:id)
line_item_product_ids = LineItem.where(order_id: high_value_order_ids).pluck(:product_id)
products = Product.where(id: line_item_product_ids)
এটি সেই একই পচন যা disable_joins অভ্যন্তরীণভাবে সঞ্চালিত হয়, কিন্তু স্পষ্টভাবে করা হয় যাতে আপনি প্রতিটি পর্যায়ে ফিল্টারিং প্রয়োগ করতে পারেন। এটি আরও ভার্বস, কিন্তু এটি রেল সিনট্যাক্সে অ্যাসোসিয়েশনের পিছনে লুকিয়ে রাখার পরিবর্তে কোডে ক্লাস্টার সীমানাগুলিকে দৃশ্যমান করে তোলে৷
সম্পাদকের দ্রষ্টব্য:এই পোস্টটি মূলত জানুয়ারী 2023 এ প্রকাশিত হয়েছিল এবং সঠিকতার জন্য আপডেট করা হয়েছে৷