ActiveRecord
. के अंदर तंग युग्मन के कारण यह एक मुश्किल समस्या है , लेकिन मैं अवधारणा के कुछ सबूत बनाने में कामयाब रहा जो काम करता है। या कम से कम ऐसा लगता है कि यह काम करता है।
कुछ पृष्ठभूमि
ActiveRecord
एक ActiveRecord::ConnectionAdapters::ConnectionHandler
. का उपयोग करता है वर्ग जो प्रति मॉडल कनेक्शन पूल संग्रहीत करने के लिए ज़िम्मेदार है। डिफ़ॉल्ट रूप से सभी मॉडलों के लिए केवल एक कनेक्शन पूल होता है, क्योंकि सामान्य रेल ऐप एक डेटाबेस से जुड़ा होता है।
establish_connection
executing को क्रियान्वित करने के बाद विशेष मॉडल में विभिन्न डेटाबेस के लिए, उस मॉडल के लिए नया कनेक्शन पूल बनाया जाता है। और उन सभी मॉडलों के लिए भी जो इससे इनहेरिट हो सकते हैं।
किसी भी क्वेरी को निष्पादित करने से पहले, ActiveRecord
पहले प्रासंगिक मॉडल के लिए कनेक्शन पूल पुनर्प्राप्त करता है और फिर पूल से कनेक्शन पुनर्प्राप्त करता है।
ध्यान दें कि उपरोक्त स्पष्टीकरण 100% सटीक नहीं हो सकता है, लेकिन यह करीब होना चाहिए।
समाधान
तो विचार यह है कि डिफ़ॉल्ट कनेक्शन हैंडलर को कस्टम एक के साथ बदलें जो प्रदान किए गए शार्प विवरण के आधार पर कनेक्शन पूल लौटाएगा।
इसे कई अलग-अलग तरीकों से लागू किया जा सकता है। मैंने इसे प्रॉक्सी ऑब्जेक्ट बनाकर किया है जो छद्म नामों को प्रच्छन्न के रूप में पास कर रहा है ActiveRecord
कक्षाएं। कनेक्शन हैंडलर एआर मॉडल प्राप्त करने की उम्मीद कर रहा है और name
. को देखता है संपत्ति और superclass
. पर भी मॉडल की पदानुक्रम श्रृंखला चलने के लिए। मैंने DatabaseModel
क्रियान्वित किया है वर्ग जो मूल रूप से शार्प नाम है, लेकिन यह एआर मॉडल की तरह व्यवहार कर रहा है।
कार्यान्वयन
यहां उदाहरण कार्यान्वयन है। मैंने सादगी के लिए sqlite डेटाबेस का उपयोग किया है, आप इस फ़ाइल को बिना किसी सेटअप के चला सकते हैं। आप इस सार पर भी एक नज़र डाल सकते हैं
# Define some required dependencies
require "bundler/inline"
gemfile(false) do
source "https://rubygems.org"
gem "activerecord", "~> 4.2.8"
gem "sqlite3"
end
require "active_record"
class User < ActiveRecord::Base
end
DatabaseModel = Struct.new(:name) do
def superclass
ActiveRecord::Base
end
end
# Setup database connections and create databases if not present
connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({
"users_shard_1" => { adapter: "sqlite3", database: "users_shard_1.sqlite3" },
"users_shard_2" => { adapter: "sqlite3", database: "users_shard_2.sqlite3" }
})
databases = %w{users_shard_1 users_shard_2}
databases.each do |database|
filename = "#{database}.sqlite3"
ActiveRecord::Base.establish_connection({
adapter: "sqlite3",
database: filename
})
spec = resolver.spec(database.to_sym)
connection_handler.establish_connection(DatabaseModel.new(database), spec)
next if File.exists?(filename)
ActiveRecord::Schema.define(version: 1) do
create_table :users do |t|
t.string :name
t.string :email
end
end
end
# Create custom connection handler
class ShardHandler
def initialize(original_handler)
@original_handler = original_handler
end
def use_database(name)
@model= DatabaseModel.new(name)
end
def retrieve_connection_pool(klass)
@original_handler.retrieve_connection_pool(@model)
end
def retrieve_connection(klass)
pool = retrieve_connection_pool(klass)
raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
conn = pool.connection
raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
puts "Using database \"#{conn.instance_variable_get("@config")[:database]}\" (##{conn.object_id})"
conn
end
end
User.connection_handler = ShardHandler.new(connection_handler)
User.connection_handler.use_database("users_shard_1")
User.create(name: "John Doe", email: "[email protected]")
puts User.count
User.connection_handler.use_database("users_shard_2")
User.create(name: "Jane Doe", email: "[email protected]")
puts User.count
User.connection_handler.use_database("users_shard_1")
puts User.count
मुझे लगता है कि यह एक विचार देना चाहिए कि उत्पादन तैयार समाधान को कैसे कार्यान्वित किया जाए। मुझे आशा है कि मुझे यहाँ कुछ भी स्पष्ट याद नहीं आया। मैं दो अलग-अलग तरीकों का सुझाव दे सकता हूं:
- उपवर्ग
ActiveRecord::ConnectionAdapters::ConnectionHandler
और उन तरीकों को अधिलेखित कर दें जो कनेक्शन पूल को पुनः प्राप्त करने के लिए जिम्मेदार हैं ConnectionHandler
के समान एपीआई को लागू करते हुए पूरी तरह से नया वर्ग बनाएं- मुझे लगता है कि
retrieve_connection
. को ओवरराइट करना भी संभव है तरीका। मुझे याद नहीं है कि इसे कहाँ परिभाषित किया गया है, लेकिन मुझे लगता है कि यहActiveRecord::Core
में है ।
मुझे लगता है कि दृष्टिकोण 1 और 2 जाने का रास्ता है और डेटाबेस के साथ काम करते समय सभी मामलों को कवर करना चाहिए।