yam-db-charmer 1.7.01
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/CHANGES +184 -0
- data/LICENSE +21 -0
- data/Makefile +2 -0
- data/README.rdoc +612 -0
- data/Rakefile +4 -0
- data/db-charmer.gemspec +29 -0
- data/init.rb +1 -0
- data/lib/db_charmer/action_controller/force_slave_reads.rb +69 -0
- data/lib/db_charmer/active_record/association_preload.rb +23 -0
- data/lib/db_charmer/active_record/class_attributes.rb +101 -0
- data/lib/db_charmer/active_record/connection_switching.rb +81 -0
- data/lib/db_charmer/active_record/db_magic.rb +85 -0
- data/lib/db_charmer/active_record/migration/multi_db_migrations.rb +71 -0
- data/lib/db_charmer/active_record/multi_db_proxy.rb +77 -0
- data/lib/db_charmer/active_record/sharding.rb +40 -0
- data/lib/db_charmer/connection_factory.rb +76 -0
- data/lib/db_charmer/connection_proxy.rb +27 -0
- data/lib/db_charmer/core_extensions.rb +23 -0
- data/lib/db_charmer/force_slave_reads.rb +36 -0
- data/lib/db_charmer/rails2/abstract_adapter/log_formatting.rb +24 -0
- data/lib/db_charmer/rails2/active_record/master_slave_routing.rb +49 -0
- data/lib/db_charmer/rails2/active_record/named_scope/scope_proxy.rb +26 -0
- data/lib/db_charmer/rails3/abstract_adapter/connection_name.rb +38 -0
- data/lib/db_charmer/rails3/active_record/log_subscriber.rb +23 -0
- data/lib/db_charmer/rails3/active_record/master_slave_routing.rb +46 -0
- data/lib/db_charmer/rails3/active_record/relation/connection_routing.rb +147 -0
- data/lib/db_charmer/rails3/active_record/relation_method.rb +28 -0
- data/lib/db_charmer/sharding/connection.rb +31 -0
- data/lib/db_charmer/sharding/method/db_block_group_map.rb +257 -0
- data/lib/db_charmer/sharding/method/db_block_map.rb +211 -0
- data/lib/db_charmer/sharding/method/hash_map.rb +23 -0
- data/lib/db_charmer/sharding/method/range.rb +33 -0
- data/lib/db_charmer/sharding/method.rb +10 -0
- data/lib/db_charmer/sharding/stub_connection.rb +60 -0
- data/lib/db_charmer/sharding.rb +18 -0
- data/lib/db_charmer/version.rb +10 -0
- data/lib/db_charmer.rb +192 -0
- data/lib/tasks/databases.rake +82 -0
- metadata +178 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module MasterSlaveRouting
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
SLAVE_METHODS = [ :find_by_sql, :count_by_sql ]
|
7
|
+
MASTER_METHODS = [ ] # I don't know any methods in AR::Base that change data directly w/o going to the relation object
|
8
|
+
|
9
|
+
SLAVE_METHODS.each do |slave_method|
|
10
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
11
|
+
def #{slave_method}(*args, &block)
|
12
|
+
first_level_on_slave do
|
13
|
+
super(*args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
EOF
|
17
|
+
end
|
18
|
+
|
19
|
+
MASTER_METHODS.each do |master_method|
|
20
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
21
|
+
def #{master_method}(*args, &block)
|
22
|
+
on_master do
|
23
|
+
super(*args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
EOF
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module InstanceMethods
|
31
|
+
MASTER_METHODS = [ :reload ]
|
32
|
+
|
33
|
+
MASTER_METHODS.each do |master_method|
|
34
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
35
|
+
def #{master_method}(*args, &block)
|
36
|
+
self.class.on_master do
|
37
|
+
super(*args, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
EOF
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module Relation
|
4
|
+
module ConnectionRouting
|
5
|
+
|
6
|
+
# All the methods that could be querying the database
|
7
|
+
SLAVE_METHODS = [ :calculate, :exists? ]
|
8
|
+
MASTER_METHODS = [ :delete, :delete_all, :destroy, :destroy_all, :reload, :update, :update_all ]
|
9
|
+
ALL_METHODS = SLAVE_METHODS + MASTER_METHODS
|
10
|
+
|
11
|
+
DB_CHARMER_ATTRIBUTES = [ :db_charmer_connection, :db_charmer_connection_is_forced, :db_charmer_enable_slaves ]
|
12
|
+
|
13
|
+
# Define the default relation connection + override all the query methods here
|
14
|
+
def self.included(base)
|
15
|
+
init_attributes(base)
|
16
|
+
init_routing(base)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Define our attributes + spawn methods shit needs to be changed to make sure our accessors are copied over to the new instances
|
20
|
+
def self.init_attributes(base)
|
21
|
+
DB_CHARMER_ATTRIBUTES.each do |attr|
|
22
|
+
base.send(:attr_accessor, attr)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override spawn methods
|
26
|
+
base.alias_method_chain :except, :db_charmer
|
27
|
+
base.alias_method_chain :only, :db_charmer
|
28
|
+
end
|
29
|
+
|
30
|
+
# Override all query methods
|
31
|
+
def self.init_routing(base)
|
32
|
+
ALL_METHODS.each do |meth|
|
33
|
+
base.alias_method_chain meth, :db_charmer
|
34
|
+
end
|
35
|
+
|
36
|
+
# Special case: for normal selects we go to the slave, but for selects with a lock we should use master
|
37
|
+
base.alias_method_chain :to_a, :db_charmer
|
38
|
+
end
|
39
|
+
|
40
|
+
# Copy db_charmer attributes in addition to what they're copying
|
41
|
+
def except_with_db_charmer(*args)
|
42
|
+
except_without_db_charmer(*args).tap do |result|
|
43
|
+
copy_db_charmer_options(self, result)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Copy db_charmer attributes in addition to what they're copying
|
48
|
+
def only_with_db_charmer(*args)
|
49
|
+
only_without_db_charmer(*args).tap do |result|
|
50
|
+
copy_db_charmer_options(self, result)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Copy our accessors from one instance to another
|
55
|
+
def copy_db_charmer_options(src, dst)
|
56
|
+
DB_CHARMER_ATTRIBUTES.each do |attr|
|
57
|
+
dst.send("#{attr}=".to_sym, src.send(attr))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Connection switching (changes the default relation connection)
|
62
|
+
def on_db(con, &block)
|
63
|
+
if block_given?
|
64
|
+
@klass.on_db(con, &block)
|
65
|
+
else
|
66
|
+
clone.tap do |result|
|
67
|
+
result.db_charmer_connection = con
|
68
|
+
result.db_charmer_connection_is_forced = true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Make sure we get the right connection here
|
74
|
+
def connection
|
75
|
+
@klass.on_db(db_charmer_connection).connection
|
76
|
+
end
|
77
|
+
|
78
|
+
# Selects preferred destination (master/slave/default) for a query
|
79
|
+
def select_destination(method, recommendation = :default)
|
80
|
+
# If this relation was created within a forced connection block (e.g Model.on_db(:foo).relation)
|
81
|
+
# Then we should use that connection everywhere except cases when a model is slave-enabled
|
82
|
+
# in those cases DML queries go to the master
|
83
|
+
if db_charmer_connection_is_forced
|
84
|
+
return :master if db_charmer_enable_slaves && MASTER_METHODS.member?(method)
|
85
|
+
return :default
|
86
|
+
end
|
87
|
+
|
88
|
+
# If this relation is created from a slave-enabled model, let's do the routing if possible
|
89
|
+
if db_charmer_enable_slaves
|
90
|
+
return :slave if SLAVE_METHODS.member?(method)
|
91
|
+
return :master if MASTER_METHODS.member?(method)
|
92
|
+
else
|
93
|
+
# Make sure we do not use recommended destination
|
94
|
+
recommendation = :default
|
95
|
+
end
|
96
|
+
|
97
|
+
# If nothing else came up, let's use the default or recommended connection
|
98
|
+
return recommendation
|
99
|
+
end
|
100
|
+
|
101
|
+
# Switch the model to default relation connection
|
102
|
+
def switch_connection_for_method(method, recommendation = nil)
|
103
|
+
# Choose where to send the query
|
104
|
+
destination ||= select_destination(method, recommendation)
|
105
|
+
|
106
|
+
# What method to use
|
107
|
+
on_db_method = [ :on_db, db_charmer_connection ]
|
108
|
+
on_db_method = :on_master if destination == :master
|
109
|
+
on_db_method = :first_level_on_slave if destination == :slave
|
110
|
+
|
111
|
+
# Perform the query
|
112
|
+
@klass.send(*on_db_method) do
|
113
|
+
yield
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# For normal selects we go to the slave, but for selects with a lock we should use master
|
118
|
+
def to_a_with_db_charmer(*args, &block)
|
119
|
+
preferred_destination = :slave
|
120
|
+
preferred_destination = :master if lock_value
|
121
|
+
|
122
|
+
switch_connection_for_method(:to_a, preferred_destination) do
|
123
|
+
to_a_without_db_charmer(*args, &block)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Need this to mimick alias_method_chain name generation (exists? => exists_with_db_charmer?)
|
128
|
+
def self.aliased_method_name(target, with)
|
129
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
130
|
+
"#{aliased_target}_#{with}_db_charmer#{punctuation}"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Override all the query methods here
|
134
|
+
ALL_METHODS.each do |method|
|
135
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
136
|
+
def #{aliased_method_name method, :with}(*args, &block)
|
137
|
+
switch_connection_for_method(:#{method.to_s}) do
|
138
|
+
#{aliased_method_name method, :without}(*args, &block)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
EOF
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module RelationMethod
|
4
|
+
|
5
|
+
def self.extended(base)
|
6
|
+
class << base
|
7
|
+
alias_method_chain :relation, :db_charmer
|
8
|
+
alias_method_chain :arel_engine, :db_charmer
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Create a relation object and initialize its default connection
|
13
|
+
def relation_with_db_charmer(*args, &block)
|
14
|
+
relation_without_db_charmer(*args, &block).tap do |rel|
|
15
|
+
rel.db_charmer_connection = self.connection
|
16
|
+
rel.db_charmer_enable_slaves = self.db_charmer_slaves.any?
|
17
|
+
rel.db_charmer_connection_is_forced = !db_charmer_top_level_connection?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Use the model itself an engine for Arel, do not fall back to AR::Base
|
22
|
+
def arel_engine_with_db_charmer(*)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module Sharding
|
3
|
+
class Connection
|
4
|
+
attr_accessor :config, :sharder
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
@sharder = self.instantiate_sharder
|
9
|
+
end
|
10
|
+
|
11
|
+
def instantiate_sharder
|
12
|
+
raise ArgumentError, "No :method passed!" unless config[:method]
|
13
|
+
sharder_class_name = "DbCharmer::Sharding::Method::#{config[:method].to_s.classify}"
|
14
|
+
sharder_class = sharder_class_name.constantize
|
15
|
+
sharder_class.new(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
def shard_connections
|
19
|
+
sharder.respond_to?(:shard_connections) ? sharder.shard_connections : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def support_default_shard?
|
23
|
+
sharder.respond_to?(:support_default_shard?) && sharder.support_default_shard?
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_connection
|
27
|
+
@default_connection ||= DbCharmer::Sharding::StubConnection.new(self)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# This is a more sophisticated sharding method based on a two layer database-backed
|
2
|
+
# blocks map that holds block-shard associations. Record blocks are mapped to tablegroups
|
3
|
+
# and groups are mapped to shards.
|
4
|
+
#
|
5
|
+
# It automatically creates new blocks for new keys and assigns them to existing groups.
|
6
|
+
# Warning: make sure to create at least one shard and one group before inserting any records.
|
7
|
+
#
|
8
|
+
module DbCharmer
|
9
|
+
module Sharding
|
10
|
+
module Method
|
11
|
+
class DbBlockGroupMap
|
12
|
+
# Shard connection info model
|
13
|
+
class Shard < ::ActiveRecord::Base
|
14
|
+
validates_presence_of :db_host
|
15
|
+
validates_presence_of :db_port
|
16
|
+
validates_presence_of :db_user
|
17
|
+
validates_presence_of :db_pass
|
18
|
+
validates_presence_of :db_name_prefix
|
19
|
+
|
20
|
+
has_many :groups, :class_name => 'DbCharmer::Sharding::Method::DbBlockGroupMap::Group'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Table group info model
|
24
|
+
class Group < ::ActiveRecord::Base
|
25
|
+
validates_presence_of :shard_id
|
26
|
+
belongs_to :shard, :class_name => 'DbCharmer::Sharding::Method::DbBlockGroupMap::Shard'
|
27
|
+
end
|
28
|
+
|
29
|
+
#---------------------------------------------------------------------------------------------------------------
|
30
|
+
# Sharder name
|
31
|
+
attr_accessor :name
|
32
|
+
|
33
|
+
# Mapping db connection
|
34
|
+
attr_accessor :connection, :connection_name
|
35
|
+
|
36
|
+
# Mapping table name
|
37
|
+
attr_accessor :map_table
|
38
|
+
|
39
|
+
# Tablegroups table name
|
40
|
+
attr_accessor :groups_table
|
41
|
+
|
42
|
+
# Shards table name
|
43
|
+
attr_accessor :shards_table
|
44
|
+
|
45
|
+
# Sharding keys block size
|
46
|
+
attr_accessor :block_size
|
47
|
+
|
48
|
+
def initialize(config)
|
49
|
+
@name = config[:name] or raise(ArgumentError, "Missing required :name parameter!")
|
50
|
+
@connection = DbCharmer::ConnectionFactory.connect(config[:connection], true)
|
51
|
+
@block_size = (config[:block_size] || 10000).to_i
|
52
|
+
|
53
|
+
@map_table = config[:map_table] or raise(ArgumentError, "Missing required :map_table parameter!")
|
54
|
+
@groups_table = config[:groups_table] or raise(ArgumentError, "Missing required :groups_table parameter!")
|
55
|
+
@shards_table = config[:shards_table] or raise(ArgumentError, "Missing required :shards_table parameter!")
|
56
|
+
|
57
|
+
# Local caches
|
58
|
+
@shard_info_cache = {}
|
59
|
+
@group_info_cache = {}
|
60
|
+
|
61
|
+
@blocks_cache = Rails.cache
|
62
|
+
@blocks_cache_prefix = config[:blocks_cache_prefix] || "#{@name}_block:"
|
63
|
+
end
|
64
|
+
|
65
|
+
#---------------------------------------------------------------------------------------------------------------
|
66
|
+
def shard_for_key(key)
|
67
|
+
block = block_for_key(key)
|
68
|
+
|
69
|
+
# Auto-allocate new blocks
|
70
|
+
block ||= allocate_new_block_for_key(key)
|
71
|
+
raise ArgumentError, "Invalid key value, no shards found for this key and could not create a new block!" unless block
|
72
|
+
|
73
|
+
# Load shard
|
74
|
+
group_id = block['group_id'].to_i
|
75
|
+
shard_info = shard_info_by_group_id(group_id)
|
76
|
+
|
77
|
+
# Get config
|
78
|
+
shard_connection_config(shard_info, group_id)
|
79
|
+
end
|
80
|
+
|
81
|
+
#---------------------------------------------------------------------------------------------------------------
|
82
|
+
# Returns a block for a key
|
83
|
+
def block_for_key(key, cache = true)
|
84
|
+
# Cleanup the cache if asked to
|
85
|
+
key_range = [ block_start_for_key(key), block_end_for_key(key) ]
|
86
|
+
block_cache_key = "%d-%d" % key_range
|
87
|
+
|
88
|
+
if cache
|
89
|
+
cached_block = get_cached_block(block_cache_key)
|
90
|
+
return cached_block if cached_block
|
91
|
+
end
|
92
|
+
|
93
|
+
# Fetch cached value or load from db
|
94
|
+
block = begin
|
95
|
+
sql = "SELECT * FROM #{map_table} WHERE start_id = #{key_range.first} AND end_id = #{key_range.last} LIMIT 1"
|
96
|
+
connection.select_one(sql, 'Find a shard block')
|
97
|
+
end
|
98
|
+
|
99
|
+
set_cached_block(block_cache_key, block)
|
100
|
+
|
101
|
+
return block
|
102
|
+
end
|
103
|
+
|
104
|
+
#---------------------------------------------------------------------------------------------------------------
|
105
|
+
def get_cached_block(block_cache_key)
|
106
|
+
@blocks_cache.read("#{@blocks_cache_prefix}#{block_cache_key}")
|
107
|
+
end
|
108
|
+
|
109
|
+
def set_cached_block(block_cache_key, block)
|
110
|
+
@blocks_cache.write("#{@blocks_cache_prefix}#{block_cache_key}", block)
|
111
|
+
end
|
112
|
+
|
113
|
+
#---------------------------------------------------------------------------------------------------------------
|
114
|
+
# Load group info
|
115
|
+
def group_info_by_id(group_id, cache = true)
|
116
|
+
# Cleanup the cache if asked to
|
117
|
+
@group_info_cache[group_id] = nil unless cache
|
118
|
+
|
119
|
+
# Either load from cache or from db
|
120
|
+
@group_info_cache[group_id] ||= begin
|
121
|
+
prepare_shard_models
|
122
|
+
Group.find_by_id(group_id)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Load shard info
|
127
|
+
def shard_info_by_id(shard_id, cache = true)
|
128
|
+
# Cleanup the cache if asked to
|
129
|
+
@shard_info_cache[shard_id] = nil unless cache
|
130
|
+
|
131
|
+
# Either load from cache or from db
|
132
|
+
@shard_info_cache[shard_id] ||= begin
|
133
|
+
prepare_shard_models
|
134
|
+
Shard.find_by_id(shard_id)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Load shard info using mapping info for a group
|
139
|
+
def shard_info_by_group_id(group_id)
|
140
|
+
# Load group
|
141
|
+
group_info = group_info_by_id(group_id)
|
142
|
+
raise ArgumentError, "Invalid group_id: #{group_id}" unless group_info
|
143
|
+
|
144
|
+
shard_info = shard_info_by_id(group_info.shard_id)
|
145
|
+
raise ArgumentError, "Invalid shard_id: #{group_info.shard_id}" unless shard_info
|
146
|
+
|
147
|
+
return shard_info
|
148
|
+
end
|
149
|
+
|
150
|
+
#---------------------------------------------------------------------------------------------------------------
|
151
|
+
def allocate_new_block_for_key(key)
|
152
|
+
# Can't find any groups to use for blocks allocation!
|
153
|
+
return nil unless group = least_loaded_group
|
154
|
+
|
155
|
+
# Figure out block limits
|
156
|
+
start_id = block_start_for_key(key)
|
157
|
+
end_id = block_end_for_key(key)
|
158
|
+
|
159
|
+
# Try to insert a new mapping (ignore duplicate key errors)
|
160
|
+
sql = <<-SQL
|
161
|
+
INSERT INTO #{map_table}
|
162
|
+
(start_id, end_id, group_id, block_size, created_at, updated_at) VALUES
|
163
|
+
(#{start_id}, #{end_id}, #{group.id}, #{block_size}, NOW(), NOW())
|
164
|
+
SQL
|
165
|
+
connection.execute(sql, "Allocate new block")
|
166
|
+
|
167
|
+
# Increment the blocks counter on the shard
|
168
|
+
Group.update_counters(group.id, :blocks_count => +1)
|
169
|
+
|
170
|
+
# Retry block search after creation
|
171
|
+
block_for_key(key)
|
172
|
+
end
|
173
|
+
|
174
|
+
def least_loaded_group
|
175
|
+
prepare_shard_models
|
176
|
+
|
177
|
+
# Select group
|
178
|
+
group = Group.first(:conditions => { :enabled => true, :open => true }, :order => 'blocks_count ASC')
|
179
|
+
raise "Can't find any tablegroups to use for blocks allocation!" unless group
|
180
|
+
return group
|
181
|
+
end
|
182
|
+
|
183
|
+
#---------------------------------------------------------------------------------------------------------------
|
184
|
+
def block_start_for_key(key)
|
185
|
+
block_size.to_i * (key.to_i / block_size.to_i)
|
186
|
+
end
|
187
|
+
|
188
|
+
def block_end_for_key(key)
|
189
|
+
block_size.to_i + block_start_for_key(key)
|
190
|
+
end
|
191
|
+
|
192
|
+
#---------------------------------------------------------------------------------------------------------------
|
193
|
+
# Create configuration (use mapping connection as a template)
|
194
|
+
def shard_connection_config(shard, group_id)
|
195
|
+
# Format connection name
|
196
|
+
shard_name = "db_charmer_db_block_group_map_#{name}_s%d_g%d" % [ shard.id, group_id]
|
197
|
+
|
198
|
+
# Here we get the mapping connection's configuration
|
199
|
+
# They do not expose configs so we hack in and get the instance var
|
200
|
+
# FIXME: Find a better way, maybe move config method to our ar extenstions
|
201
|
+
connection.instance_variable_get(:@config).clone.merge(
|
202
|
+
# Name for the connection factory
|
203
|
+
:connection_name => shard_name,
|
204
|
+
# Connection params
|
205
|
+
:host => shard.db_host,
|
206
|
+
:port => shard.db_port,
|
207
|
+
:username => shard.db_user,
|
208
|
+
:password => shard.db_pass,
|
209
|
+
:database => group_database_name(shard, group_id)
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
def group_database_name(shard, group_id)
|
214
|
+
"%s_%05d" % [ shard.db_name_prefix, group_id ]
|
215
|
+
end
|
216
|
+
|
217
|
+
#---------------------------------------------------------------------------------------------------------------
|
218
|
+
def create_shard(params)
|
219
|
+
params = params.symbolize_keys
|
220
|
+
[ :db_host, :db_port, :db_user, :db_pass, :db_name_prefix ].each do |arg|
|
221
|
+
raise ArgumentError, "Missing required parameter: #{arg}" unless params[arg]
|
222
|
+
end
|
223
|
+
|
224
|
+
# Prepare model
|
225
|
+
prepare_shard_models
|
226
|
+
|
227
|
+
# Create the record
|
228
|
+
Shard.create! do |shard|
|
229
|
+
shard.db_host = params[:db_host]
|
230
|
+
shard.db_port = params[:db_port]
|
231
|
+
shard.db_user = params[:db_user]
|
232
|
+
shard.db_pass = params[:db_pass]
|
233
|
+
shard.db_name_prefix = params[:db_name_prefix]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def shard_connections
|
238
|
+
# Find all groups
|
239
|
+
prepare_shard_models
|
240
|
+
groups = Group.all(:conditions => { :enabled => true }, :include => :shard)
|
241
|
+
# Map them to shards
|
242
|
+
groups.map { |group| shard_connection_config(group.shard, group.id) }
|
243
|
+
end
|
244
|
+
|
245
|
+
# Prepare model for working with our shards table
|
246
|
+
def prepare_shard_models
|
247
|
+
Shard.set_table_name(shards_table)
|
248
|
+
Shard.switch_connection_to(connection)
|
249
|
+
|
250
|
+
Group.set_table_name(groups_table)
|
251
|
+
Group.switch_connection_to(connection)
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|