active_record_shards 3.16.0 → 3.19.1
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.
- checksums.yaml +4 -4
- data/README.md +14 -14
- data/lib/active_record_shards/association_collection_connection_selection.rb +23 -14
- data/lib/active_record_shards/configuration_parser.rb +13 -4
- data/lib/active_record_shards/connection_handler.rb +1 -0
- data/lib/active_record_shards/connection_pool.rb +1 -0
- data/lib/active_record_shards/connection_specification.rb +1 -0
- data/lib/active_record_shards/connection_switcher-4-2.rb +2 -2
- data/lib/active_record_shards/connection_switcher-5-0.rb +1 -1
- data/lib/active_record_shards/connection_switcher-5-1.rb +1 -1
- data/lib/active_record_shards/connection_switcher.rb +59 -38
- data/lib/active_record_shards/default_replica_patches.rb +278 -0
- data/lib/active_record_shards/default_slave_patches.rb +3 -140
- data/lib/active_record_shards/deprecation.rb +12 -0
- data/lib/active_record_shards/migration.rb +1 -0
- data/lib/active_record_shards/model.rb +16 -11
- data/lib/active_record_shards/patches-4-2.rb +1 -0
- data/lib/active_record_shards/schema_dumper_extension.rb +6 -5
- data/lib/active_record_shards/shard_selection.rb +17 -14
- data/lib/active_record_shards/shard_support.rb +1 -0
- data/lib/active_record_shards/sql_comments.rb +7 -4
- data/lib/active_record_shards/tasks.rb +13 -10
- data/lib/active_record_shards.rb +109 -6
- metadata +55 -39
@@ -0,0 +1,278 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordShards
|
4
|
+
module DefaultReplicaPatches
|
5
|
+
def self.wrap_method_in_on_replica(class_method, base, method, force_on_replica: false)
|
6
|
+
base_methods =
|
7
|
+
if class_method
|
8
|
+
base.methods + base.private_methods
|
9
|
+
else
|
10
|
+
base.instance_methods + base.private_instance_methods
|
11
|
+
end
|
12
|
+
|
13
|
+
return unless base_methods.include?(method)
|
14
|
+
|
15
|
+
_, method, punctuation = method.to_s.match(/^(.*?)([\?\!]?)$/).to_a
|
16
|
+
# _ALWAYS_ on replica, or only for on `on_replica_by_default = true` models?
|
17
|
+
wrapper = force_on_replica ? 'force_on_replica' : 'on_replica_unless_tx'
|
18
|
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
19
|
+
#{class_method ? 'class << self' : ''}
|
20
|
+
def #{method}_with_default_replica#{punctuation}(*args, &block)
|
21
|
+
#{wrapper} do
|
22
|
+
#{method}_without_default_replica#{punctuation}(*args, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :#{method}_without_default_replica#{punctuation}, :#{method}#{punctuation}
|
27
|
+
alias_method :#{method}#{punctuation}, :#{method}_with_default_replica#{punctuation}
|
28
|
+
#{class_method ? 'end' : ''}
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.wrap_method_in_on_slave(*args)
|
33
|
+
ActiveRecordShards::Deprecation.deprecation_warning(
|
34
|
+
:'self.wrap_method_in_on_slave',
|
35
|
+
:'self.wrap_method_in_on_replica'
|
36
|
+
)
|
37
|
+
wrap_method_in_on_replica(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def transaction_with_replica_off(*args, &block)
|
41
|
+
if on_replica_by_default?
|
42
|
+
begin
|
43
|
+
old_val = Thread.current[:_active_record_shards_in_tx]
|
44
|
+
Thread.current[:_active_record_shards_in_tx] = true
|
45
|
+
transaction_without_replica_off(*args, &block)
|
46
|
+
ensure
|
47
|
+
Thread.current[:_active_record_shards_in_tx] = old_val
|
48
|
+
end
|
49
|
+
else
|
50
|
+
transaction_without_replica_off(*args, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
alias_method :transaction_with_slave_off, :transaction_with_replica_off
|
54
|
+
|
55
|
+
module InstanceMethods
|
56
|
+
# fix ActiveRecord to do the right thing, and use our aliased quote_value
|
57
|
+
def quote_value(*args, &block)
|
58
|
+
self.class.quote_value(*args, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_replica_unless_tx
|
62
|
+
self.class.on_replica_unless_tx { yield }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
CLASS_REPLICA_METHODS = [
|
67
|
+
:calculate,
|
68
|
+
:count_by_sql,
|
69
|
+
:exists?,
|
70
|
+
:find,
|
71
|
+
:find_by,
|
72
|
+
:find_by_sql,
|
73
|
+
:find_every,
|
74
|
+
:find_one,
|
75
|
+
:find_some,
|
76
|
+
:get_primary_key
|
77
|
+
].freeze
|
78
|
+
|
79
|
+
CLASS_FORCE_REPLICA_METHODS = [
|
80
|
+
:replace_bind_variable,
|
81
|
+
:replace_bind_variables,
|
82
|
+
:sanitize_sql_array,
|
83
|
+
:sanitize_sql_hash_for_assignment,
|
84
|
+
:table_exists?
|
85
|
+
].freeze
|
86
|
+
|
87
|
+
CLASS_SLAVE_METHODS = CLASS_REPLICA_METHODS
|
88
|
+
CLASS_FORCE_SLAVE_METHODS = CLASS_FORCE_REPLICA_METHODS
|
89
|
+
|
90
|
+
def self.extended(base)
|
91
|
+
CLASS_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m) }
|
92
|
+
CLASS_FORCE_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m, force_on_replica: true) }
|
93
|
+
|
94
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
95
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, :load_schema!, force_on_replica: true)
|
96
|
+
else
|
97
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, :columns, force_on_replica: true)
|
98
|
+
end
|
99
|
+
|
100
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :reload)
|
101
|
+
|
102
|
+
base.class_eval do
|
103
|
+
include InstanceMethods
|
104
|
+
|
105
|
+
class << self
|
106
|
+
alias_method :transaction_without_replica_off, :transaction
|
107
|
+
alias_method :transaction, :transaction_with_replica_off
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_replica_unless_tx(&block)
|
113
|
+
return yield if Thread.current[:_active_record_shards_in_migration]
|
114
|
+
return yield if Thread.current[:_active_record_shards_in_tx]
|
115
|
+
|
116
|
+
if on_replica_by_default?
|
117
|
+
on_replica(&block)
|
118
|
+
else
|
119
|
+
yield
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias_method :on_slave_unless_tx, :on_replica_unless_tx
|
123
|
+
|
124
|
+
def force_on_replica(&block)
|
125
|
+
return yield if Thread.current[:_active_record_shards_in_migration]
|
126
|
+
|
127
|
+
on_cx_switch_block(:replica, construct_ro_scope: false, force: true, &block)
|
128
|
+
end
|
129
|
+
|
130
|
+
module ActiveRelationPatches
|
131
|
+
def self.included(base)
|
132
|
+
[:calculate, :exists?, :pluck, :load].each do |m|
|
133
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, m)
|
134
|
+
end
|
135
|
+
|
136
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
137
|
+
# `where` and `having` clauses call `create_binds`, which will use the primary connection
|
138
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :create_binds, force_on_replica: true)
|
139
|
+
end
|
140
|
+
|
141
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :to_sql, force_on_replica: true)
|
142
|
+
end
|
143
|
+
|
144
|
+
def on_replica_unless_tx
|
145
|
+
@klass.on_replica_unless_tx { yield }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
module Rails52RelationPatches
|
150
|
+
def connection
|
151
|
+
return super if Thread.current[:_active_record_shards_in_migration]
|
152
|
+
return super if Thread.current[:_active_record_shards_in_tx]
|
153
|
+
|
154
|
+
if @klass.on_replica_by_default?
|
155
|
+
@klass.on_replica.connection
|
156
|
+
else
|
157
|
+
super
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# in rails 4.1+, they create a join class that's used to pull in records for HABTM.
|
163
|
+
# this simplifies the hell out of our existence, because all we have to do is inerit on-replica-by-default
|
164
|
+
# down from the parent now.
|
165
|
+
module Rails41HasAndBelongsToManyBuilderExtension
|
166
|
+
def self.included(base)
|
167
|
+
base.class_eval do
|
168
|
+
alias_method :through_model_without_inherit_default_replica_from_lhs, :through_model
|
169
|
+
alias_method :through_model, :through_model_with_inherit_default_replica_from_lhs
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def through_model_with_inherit_default_replica_from_lhs
|
174
|
+
model = through_model_without_inherit_default_replica_from_lhs
|
175
|
+
def model.on_replica_by_default?
|
176
|
+
left_reflection.klass.on_replica_by_default?
|
177
|
+
end
|
178
|
+
|
179
|
+
# also transfer the sharded-ness of the left table to the join model
|
180
|
+
model.not_sharded unless model.left_reflection.klass.is_sharded?
|
181
|
+
model
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
module AssociationsAssociationAssociationScopePatch
|
186
|
+
def association_scope
|
187
|
+
if klass
|
188
|
+
on_replica_unless_tx { super }
|
189
|
+
else
|
190
|
+
super
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def on_replica_unless_tx
|
195
|
+
klass.on_replica_unless_tx { yield }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
module AssociationsAssociationFindTargetPatch
|
200
|
+
def find_target
|
201
|
+
if klass
|
202
|
+
on_replica_unless_tx { super }
|
203
|
+
else
|
204
|
+
super
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def on_replica_unless_tx
|
209
|
+
klass.on_replica_unless_tx { yield }
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
module AssociationsAssociationGetRecordsPatch
|
214
|
+
def get_records # rubocop:disable Naming/AccessorMethodName
|
215
|
+
if klass
|
216
|
+
on_replica_unless_tx { super }
|
217
|
+
else
|
218
|
+
super
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def on_replica_unless_tx
|
223
|
+
klass.on_replica_unless_tx { yield }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
module AssociationsPreloaderAssociationAssociatedRecordsByOwnerPatch
|
228
|
+
def associated_records_by_owner(preloader)
|
229
|
+
if klass
|
230
|
+
on_replica_unless_tx { super }
|
231
|
+
else
|
232
|
+
super
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def on_replica_unless_tx
|
237
|
+
klass.on_replica_unless_tx { yield }
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
module AssociationsPreloaderAssociationLoadRecordsPatch
|
242
|
+
def load_records
|
243
|
+
if klass
|
244
|
+
on_replica_unless_tx { super }
|
245
|
+
else
|
246
|
+
super
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def on_replica_unless_tx
|
251
|
+
klass.on_replica_unless_tx { yield }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
module TypeCasterConnectionConnectionPatch
|
256
|
+
def connection
|
257
|
+
return super if Thread.current[:_active_record_shards_in_migration]
|
258
|
+
return super if Thread.current[:_active_record_shards_in_tx]
|
259
|
+
|
260
|
+
if @klass.on_replica_by_default?
|
261
|
+
@klass.on_replica.connection
|
262
|
+
else
|
263
|
+
super
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
module SchemaDefinePatch
|
269
|
+
def define(info, &block)
|
270
|
+
old_val = Thread.current[:_active_record_shards_in_migration]
|
271
|
+
Thread.current[:_active_record_shards_in_migration] = true
|
272
|
+
super
|
273
|
+
ensure
|
274
|
+
Thread.current[:_active_record_shards_in_migration] = old_val
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -1,142 +1,5 @@
|
|
1
|
-
|
2
|
-
module ActiveRecordShards
|
3
|
-
module DefaultSlavePatches
|
4
|
-
def self.wrap_method_in_on_slave(class_method, base, method, force_on_slave: false)
|
5
|
-
base_methods =
|
6
|
-
if class_method
|
7
|
-
base.methods + base.private_methods
|
8
|
-
else
|
9
|
-
base.instance_methods + base.private_instance_methods
|
10
|
-
end
|
11
|
-
|
12
|
-
return unless base_methods.include?(method)
|
13
|
-
_, method, punctuation = method.to_s.match(/^(.*?)([\?\!]?)$/).to_a
|
14
|
-
# _ALWAYS_ on slave, or only for on `on_slave_by_default = true` models?
|
15
|
-
wrapper = force_on_slave ? 'force_on_slave' : 'on_slave_unless_tx'
|
16
|
-
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
17
|
-
#{class_method ? 'class << self' : ''}
|
18
|
-
def #{method}_with_default_slave#{punctuation}(*args, &block)
|
19
|
-
#{wrapper} do
|
20
|
-
#{method}_without_default_slave#{punctuation}(*args, &block)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
alias_method :#{method}_without_default_slave#{punctuation}, :#{method}#{punctuation}
|
25
|
-
alias_method :#{method}#{punctuation}, :#{method}_with_default_slave#{punctuation}
|
26
|
-
#{class_method ? 'end' : ''}
|
27
|
-
RUBY
|
28
|
-
end
|
29
|
-
|
30
|
-
def transaction_with_slave_off(*args, &block)
|
31
|
-
if on_slave_by_default?
|
32
|
-
begin
|
33
|
-
old_val = Thread.current[:_active_record_shards_slave_off]
|
34
|
-
Thread.current[:_active_record_shards_slave_off] = true
|
35
|
-
transaction_without_slave_off(*args, &block)
|
36
|
-
ensure
|
37
|
-
Thread.current[:_active_record_shards_slave_off] = old_val
|
38
|
-
end
|
39
|
-
else
|
40
|
-
transaction_without_slave_off(*args, &block)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
module InstanceMethods
|
45
|
-
# fix ActiveRecord to do the right thing, and use our aliased quote_value
|
46
|
-
def quote_value(*args, &block)
|
47
|
-
self.class.quote_value(*args, &block)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
CLASS_SLAVE_METHODS = [
|
52
|
-
:calculate,
|
53
|
-
:count_by_sql,
|
54
|
-
:exists?,
|
55
|
-
:find_by_sql,
|
56
|
-
:find_every,
|
57
|
-
:find_one,
|
58
|
-
:find_some
|
59
|
-
].freeze
|
60
|
-
|
61
|
-
CLASS_FORCE_SLAVE_METHODS = [
|
62
|
-
:columns,
|
63
|
-
:replace_bind_variable,
|
64
|
-
:replace_bind_variables,
|
65
|
-
:sanitize_sql_array,
|
66
|
-
:sanitize_sql_hash_for_assignment,
|
67
|
-
:table_exists?
|
68
|
-
].freeze
|
69
|
-
|
70
|
-
def self.extended(base)
|
71
|
-
CLASS_SLAVE_METHODS.each { |m| ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(true, base, m) }
|
72
|
-
CLASS_FORCE_SLAVE_METHODS.each { |m| ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(true, base, m, force_on_slave: true) }
|
73
|
-
|
74
|
-
base.class_eval do
|
75
|
-
include InstanceMethods
|
76
|
-
|
77
|
-
class << self
|
78
|
-
alias_method :transaction_without_slave_off, :transaction
|
79
|
-
alias_method :transaction, :transaction_with_slave_off
|
80
|
-
end
|
81
|
-
end
|
82
|
-
if ActiveRecord::Associations.const_defined?(:HasAndBelongsToManyAssociation)
|
83
|
-
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_sql)
|
84
|
-
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_find_options!)
|
85
|
-
end
|
86
|
-
end
|
1
|
+
ActiveRecordShards::Deprecation.warn('`DefaultSlavePatches` is deprecated, please use `DefaultReplicaPatches`.')
|
87
2
|
|
88
|
-
|
89
|
-
|
90
|
-
on_slave(&block)
|
91
|
-
else
|
92
|
-
yield
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def force_on_slave(&block)
|
97
|
-
on_cx_switch_block(:slave, construct_ro_scope: false, force: true, &block)
|
98
|
-
end
|
99
|
-
|
100
|
-
module ActiveRelationPatches
|
101
|
-
def self.included(base)
|
102
|
-
[:calculate, :exists?, :pluck, :load].each do |m|
|
103
|
-
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, m)
|
104
|
-
end
|
105
|
-
|
106
|
-
if ActiveRecord::VERSION::MAJOR == 4
|
107
|
-
# `where` and `having` clauses call `create_binds`, which will use the master connection
|
108
|
-
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, :create_binds, force_on_slave: true)
|
109
|
-
end
|
110
|
-
|
111
|
-
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, :to_sql, force_on_slave: true)
|
112
|
-
end
|
113
|
-
|
114
|
-
def on_slave_unless_tx(&block)
|
115
|
-
@klass.on_slave_unless_tx(&block)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# in rails 4.1+, they create a join class that's used to pull in records for HABTM.
|
120
|
-
# this simplifies the hell out of our existence, because all we have to do is inerit on-slave-by-default
|
121
|
-
# down from the parent now.
|
122
|
-
module Rails41HasAndBelongsToManyBuilderExtension
|
123
|
-
def self.included(base)
|
124
|
-
base.class_eval do
|
125
|
-
alias_method :through_model_without_inherit_default_slave_from_lhs, :through_model
|
126
|
-
alias_method :through_model, :through_model_with_inherit_default_slave_from_lhs
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def through_model_with_inherit_default_slave_from_lhs
|
131
|
-
model = through_model_without_inherit_default_slave_from_lhs
|
132
|
-
def model.on_slave_by_default?
|
133
|
-
left_reflection.klass.on_slave_by_default?
|
134
|
-
end
|
135
|
-
|
136
|
-
# also transfer the sharded-ness of the left table to the join model
|
137
|
-
model.not_sharded unless model.left_reflection.klass.is_sharded?
|
138
|
-
model
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
3
|
+
module ActiveRecordShards
|
4
|
+
DefaultSlavePatches = DefaultReplicaPatches
|
142
5
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
class Deprecation < ActiveSupport::Deprecation
|
3
|
+
# This allows us to define separate deprecation behavior for ActiveRecordShards, but defaults to
|
4
|
+
# the same behavior globally configured with ActiveSupport.
|
5
|
+
#
|
6
|
+
# For example, this allows us to silence our own deprecation warnings in test while still being
|
7
|
+
# able to fail tests for upstream deprecation warnings.
|
8
|
+
def behavior
|
9
|
+
@behavior ||= ActiveSupport::Deprecation.behavior
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -6,6 +6,7 @@ module ActiveRecordShards
|
|
6
6
|
if self != ActiveRecord::Base && self != base_class
|
7
7
|
raise "You should only call not_sharded on direct descendants of ActiveRecord::Base"
|
8
8
|
end
|
9
|
+
|
9
10
|
self.sharded = false
|
10
11
|
end
|
11
12
|
|
@@ -23,34 +24,38 @@ module ActiveRecordShards
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
+
def on_replica_by_default?
|
27
28
|
if self == ActiveRecord::Base
|
28
29
|
false
|
29
30
|
else
|
30
31
|
base = base_class
|
31
|
-
if base.instance_variable_defined?(:@
|
32
|
-
base.instance_variable_get(:@
|
32
|
+
if base.instance_variable_defined?(:@on_replica_by_default)
|
33
|
+
base.instance_variable_get(:@on_replica_by_default)
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
37
|
+
alias_method :on_slave_by_default?, :on_replica_by_default?
|
36
38
|
|
37
|
-
def
|
39
|
+
def on_replica_by_default=(value)
|
38
40
|
if self == ActiveRecord::Base
|
39
|
-
raise ArgumentError, "Cannot set
|
41
|
+
raise ArgumentError, "Cannot set on_replica_by_default on ActiveRecord::Base"
|
40
42
|
else
|
41
|
-
base_class.instance_variable_set(:@
|
43
|
+
base_class.instance_variable_set(:@on_replica_by_default, value)
|
42
44
|
end
|
43
45
|
end
|
46
|
+
alias_method :on_slave_by_default=, :on_replica_by_default=
|
44
47
|
|
45
48
|
module InstanceMethods
|
46
|
-
def
|
47
|
-
@
|
49
|
+
def initialize_shard_and_replica
|
50
|
+
@from_replica = !!self.class.current_shard_selection.options[:replica]
|
48
51
|
@from_shard = self.class.current_shard_selection.options[:shard]
|
49
52
|
end
|
53
|
+
alias_method :initialize_shard_and_slave, :initialize_shard_and_replica
|
50
54
|
|
51
|
-
def
|
52
|
-
@
|
55
|
+
def from_replica?
|
56
|
+
@from_replica
|
53
57
|
end
|
58
|
+
alias_method :from_slave?, :from_replica?
|
54
59
|
|
55
60
|
def from_shard
|
56
61
|
@from_shard
|
@@ -59,7 +64,7 @@ module ActiveRecordShards
|
|
59
64
|
|
60
65
|
def self.extended(base)
|
61
66
|
base.send(:include, InstanceMethods)
|
62
|
-
base.after_initialize :
|
67
|
+
base.after_initialize :initialize_shard_and_replica
|
63
68
|
end
|
64
69
|
|
65
70
|
private
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module ActiveRecordShards
|
3
4
|
module SchemaDumperExtension
|
4
5
|
def dump(stream)
|
@@ -23,15 +24,15 @@ module ActiveRecordShards
|
|
23
24
|
def shard_header(stream)
|
24
25
|
define_params = @version ? "version: #{@version}" : ""
|
25
26
|
|
26
|
-
stream.puts
|
27
|
+
stream.puts <<~HEADER
|
27
28
|
|
28
29
|
|
29
|
-
# This section generated by active_record_shards
|
30
|
+
# This section generated by active_record_shards
|
30
31
|
|
31
|
-
ActiveRecord::Base.on_all_shards do
|
32
|
-
ActiveRecord::Schema.define(#{define_params}) do
|
32
|
+
ActiveRecord::Base.on_all_shards do
|
33
|
+
ActiveRecord::Schema.define(#{define_params}) do
|
33
34
|
|
34
|
-
HEADER
|
35
|
+
HEADER
|
35
36
|
end
|
36
37
|
|
37
38
|
def shard_trailer(stream)
|
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module ActiveRecordShards
|
3
4
|
class ShardSelection
|
4
5
|
NO_SHARD = :_no_shard
|
5
6
|
cattr_accessor :default_shard
|
6
7
|
|
7
8
|
def initialize
|
8
|
-
@
|
9
|
+
@on_replica = false
|
9
10
|
@shard = nil
|
10
11
|
end
|
11
12
|
|
@@ -21,17 +22,17 @@ module ActiveRecordShards
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
def shard_name(klass = nil,
|
25
|
+
def shard_name(klass = nil, try_replica = true)
|
25
26
|
the_shard = shard(klass)
|
26
27
|
|
27
28
|
@shard_names ||= {}
|
28
29
|
@shard_names[ActiveRecordShards.rails_env] ||= {}
|
29
30
|
@shard_names[ActiveRecordShards.rails_env][the_shard] ||= {}
|
30
|
-
@shard_names[ActiveRecordShards.rails_env][the_shard][
|
31
|
-
@shard_names[ActiveRecordShards.rails_env][the_shard][
|
31
|
+
@shard_names[ActiveRecordShards.rails_env][the_shard][try_replica] ||= {}
|
32
|
+
@shard_names[ActiveRecordShards.rails_env][the_shard][try_replica][@on_replica] ||= begin
|
32
33
|
s = ActiveRecordShards.rails_env.dup
|
33
34
|
s << "_shard_#{the_shard}" if the_shard
|
34
|
-
s << "
|
35
|
+
s << "_replica" if @on_replica && try_replica
|
35
36
|
s
|
36
37
|
end
|
37
38
|
end
|
@@ -46,7 +47,7 @@ module ActiveRecordShards
|
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
49
|
-
PRIMARY = "primary"
|
50
|
+
PRIMARY = "primary"
|
50
51
|
def resolve_connection_name(sharded:, configurations:)
|
51
52
|
resolved_shard = sharded ? shard : nil
|
52
53
|
env = ActiveRecordShards.rails_env
|
@@ -54,11 +55,11 @@ module ActiveRecordShards
|
|
54
55
|
@connection_names ||= {}
|
55
56
|
@connection_names[env] ||= {}
|
56
57
|
@connection_names[env][resolved_shard] ||= {}
|
57
|
-
@connection_names[env][resolved_shard][@
|
58
|
+
@connection_names[env][resolved_shard][@on_replica] ||= begin
|
58
59
|
name = env.dup
|
59
60
|
name << "_shard_#{resolved_shard}" if resolved_shard
|
60
|
-
if @
|
61
|
-
"#{name}
|
61
|
+
if @on_replica && configurations["#{name}_replica"]
|
62
|
+
"#{name}_replica"
|
62
63
|
else
|
63
64
|
# ActiveRecord always names its default connection pool 'primary'
|
64
65
|
# while everything else is named by the configuration name
|
@@ -73,16 +74,18 @@ module ActiveRecordShards
|
|
73
74
|
@shard = (new_shard || NO_SHARD)
|
74
75
|
end
|
75
76
|
|
76
|
-
def
|
77
|
-
@
|
77
|
+
def on_replica?
|
78
|
+
@on_replica
|
78
79
|
end
|
80
|
+
alias_method :on_slave?, :on_replica?
|
79
81
|
|
80
|
-
def
|
81
|
-
@
|
82
|
+
def on_replica=(new_replica)
|
83
|
+
@on_replica = (new_replica == true)
|
82
84
|
end
|
85
|
+
alias_method :on_slave=, :on_replica=
|
83
86
|
|
84
87
|
def options
|
85
|
-
{ shard: @shard,
|
88
|
+
{ shard: @shard, replica: @on_replica }
|
86
89
|
end
|
87
90
|
end
|
88
91
|
end
|
@@ -1,16 +1,19 @@
|
|
1
|
-
# show which connection was picked to debug
|
1
|
+
# show which connection was picked to debug primary/replica slowness when both servers are the same
|
2
2
|
module ActiveRecordShards
|
3
3
|
module SqlComments
|
4
4
|
module Methods
|
5
5
|
def execute(query, name = nil)
|
6
|
-
|
7
|
-
|
6
|
+
shard = ActiveRecord::Base.current_shard_selection.shard
|
7
|
+
shard_text = shard ? "shard #{shard}" : 'unsharded'
|
8
|
+
replica = ActiveRecord::Base.current_shard_selection.on_replica?
|
9
|
+
replica_text = replica ? 'replica' : 'primary'
|
10
|
+
query = "/* #{shard_text} #{replica_text} */ " + query
|
8
11
|
super(query, name)
|
9
12
|
end
|
10
13
|
end
|
11
14
|
|
12
15
|
def self.enable
|
13
|
-
ActiveRecord::Base.
|
16
|
+
ActiveRecord::Base.on_replica do
|
14
17
|
ActiveRecord::Base.on_shard(nil) do
|
15
18
|
ActiveRecord::Base.connection.class.prepend(Methods)
|
16
19
|
end
|