misha-ar-octopus 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +46 -0
  5. data/.rubocop_todo.yml +56 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +18 -0
  8. data/Appraisals +16 -0
  9. data/Gemfile +4 -0
  10. data/README.mkdn +242 -0
  11. data/Rakefile +172 -0
  12. data/TODO.txt +7 -0
  13. data/ar-octopus.gemspec +42 -0
  14. data/gemfiles/rails32.gemfile +7 -0
  15. data/gemfiles/rails4.gemfile +7 -0
  16. data/gemfiles/rails41.gemfile +7 -0
  17. data/gemfiles/rails42.gemfile +7 -0
  18. data/init.rb +1 -0
  19. data/lib/ar-octopus.rb +1 -0
  20. data/lib/octopus.rb +181 -0
  21. data/lib/octopus/abstract_adapter.rb +35 -0
  22. data/lib/octopus/association.rb +13 -0
  23. data/lib/octopus/association_shard_tracking.rb +114 -0
  24. data/lib/octopus/collection_association.rb +11 -0
  25. data/lib/octopus/collection_proxy.rb +16 -0
  26. data/lib/octopus/exception.rb +4 -0
  27. data/lib/octopus/has_and_belongs_to_many_association.rb +9 -0
  28. data/lib/octopus/load_balancing.rb +4 -0
  29. data/lib/octopus/load_balancing/round_robin.rb +20 -0
  30. data/lib/octopus/log_subscriber.rb +22 -0
  31. data/lib/octopus/migration.rb +168 -0
  32. data/lib/octopus/model.rb +210 -0
  33. data/lib/octopus/persistence.rb +39 -0
  34. data/lib/octopus/proxy.rb +534 -0
  35. data/lib/octopus/railtie.rb +11 -0
  36. data/lib/octopus/relation_proxy.rb +35 -0
  37. data/lib/octopus/scope_proxy.rb +61 -0
  38. data/lib/octopus/shard_tracking.rb +41 -0
  39. data/lib/octopus/shard_tracking/attribute.rb +22 -0
  40. data/lib/octopus/shard_tracking/dynamic.rb +11 -0
  41. data/lib/octopus/singular_association.rb +9 -0
  42. data/lib/octopus/slave_group.rb +13 -0
  43. data/lib/octopus/version.rb +3 -0
  44. data/lib/tasks/octopus.rake +16 -0
  45. data/rails/init.rb +1 -0
  46. data/sample_app/.gitignore +4 -0
  47. data/sample_app/.rspec +1 -0
  48. data/sample_app/Gemfile +20 -0
  49. data/sample_app/README +3 -0
  50. data/sample_app/README.rdoc +261 -0
  51. data/sample_app/Rakefile +7 -0
  52. data/sample_app/app/assets/images/rails.png +0 -0
  53. data/sample_app/app/assets/javascripts/application.js +15 -0
  54. data/sample_app/app/assets/stylesheets/application.css +13 -0
  55. data/sample_app/app/controllers/application_controller.rb +4 -0
  56. data/sample_app/app/helpers/application_helper.rb +2 -0
  57. data/sample_app/app/mailers/.gitkeep +0 -0
  58. data/sample_app/app/models/.gitkeep +0 -0
  59. data/sample_app/app/models/item.rb +3 -0
  60. data/sample_app/app/models/user.rb +3 -0
  61. data/sample_app/app/views/layouts/application.html.erb +14 -0
  62. data/sample_app/autotest/discover.rb +2 -0
  63. data/sample_app/config.ru +4 -0
  64. data/sample_app/config/application.rb +62 -0
  65. data/sample_app/config/boot.rb +6 -0
  66. data/sample_app/config/cucumber.yml +8 -0
  67. data/sample_app/config/database.yml +28 -0
  68. data/sample_app/config/environment.rb +5 -0
  69. data/sample_app/config/environments/development.rb +37 -0
  70. data/sample_app/config/environments/production.rb +67 -0
  71. data/sample_app/config/environments/test.rb +37 -0
  72. data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
  73. data/sample_app/config/initializers/inflections.rb +15 -0
  74. data/sample_app/config/initializers/mime_types.rb +5 -0
  75. data/sample_app/config/initializers/secret_token.rb +7 -0
  76. data/sample_app/config/initializers/session_store.rb +8 -0
  77. data/sample_app/config/initializers/wrap_parameters.rb +14 -0
  78. data/sample_app/config/locales/en.yml +5 -0
  79. data/sample_app/config/routes.rb +58 -0
  80. data/sample_app/config/shards.yml +28 -0
  81. data/sample_app/db/migrate/20100720172715_create_users.rb +15 -0
  82. data/sample_app/db/migrate/20100720172730_create_items.rb +16 -0
  83. data/sample_app/db/migrate/20100720210335_create_sample_users.rb +11 -0
  84. data/sample_app/db/schema.rb +29 -0
  85. data/sample_app/db/seeds.rb +16 -0
  86. data/sample_app/doc/README_FOR_APP +2 -0
  87. data/sample_app/features/migrate.feature +45 -0
  88. data/sample_app/features/seed.feature +15 -0
  89. data/sample_app/features/step_definitions/seeds_steps.rb +13 -0
  90. data/sample_app/features/step_definitions/web_steps.rb +218 -0
  91. data/sample_app/features/support/database.rb +13 -0
  92. data/sample_app/features/support/env.rb +57 -0
  93. data/sample_app/features/support/paths.rb +33 -0
  94. data/sample_app/lib/assets/.gitkeep +0 -0
  95. data/sample_app/lib/tasks/.gitkeep +0 -0
  96. data/sample_app/lib/tasks/cucumber.rake +64 -0
  97. data/sample_app/log/.gitkeep +0 -0
  98. data/sample_app/public/404.html +26 -0
  99. data/sample_app/public/422.html +26 -0
  100. data/sample_app/public/500.html +26 -0
  101. data/sample_app/public/favicon.ico +0 -0
  102. data/sample_app/public/images/rails.png +0 -0
  103. data/sample_app/public/index.html +279 -0
  104. data/sample_app/public/javascripts/application.js +2 -0
  105. data/sample_app/public/javascripts/controls.js +965 -0
  106. data/sample_app/public/javascripts/dragdrop.js +974 -0
  107. data/sample_app/public/javascripts/effects.js +1123 -0
  108. data/sample_app/public/javascripts/prototype.js +4874 -0
  109. data/sample_app/public/javascripts/rails.js +118 -0
  110. data/sample_app/public/robots.txt +5 -0
  111. data/sample_app/public/stylesheets/.gitkeep +0 -0
  112. data/sample_app/script/cucumber +10 -0
  113. data/sample_app/script/rails +6 -0
  114. data/sample_app/spec/models/item_spec.rb +5 -0
  115. data/sample_app/spec/models/user_spec.rb +5 -0
  116. data/sample_app/spec/spec_helper.rb +27 -0
  117. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  118. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  119. data/sample_app/vendor/plugins/.gitkeep +0 -0
  120. data/spec/config/shards.yml +217 -0
  121. data/spec/migrations/10_create_users_using_replication.rb +9 -0
  122. data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
  123. data/spec/migrations/12_create_users_using_block.rb +23 -0
  124. data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
  125. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
  126. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +9 -0
  127. data/spec/migrations/1_create_users_on_master.rb +9 -0
  128. data/spec/migrations/2_create_users_on_canada.rb +11 -0
  129. data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
  130. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
  131. data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
  132. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
  133. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
  134. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
  135. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
  136. data/spec/octopus/association_shard_tracking_spec.rb +714 -0
  137. data/spec/octopus/collection_proxy_spec.rb +16 -0
  138. data/spec/octopus/log_subscriber_spec.rb +19 -0
  139. data/spec/octopus/migration_spec.rb +115 -0
  140. data/spec/octopus/model_spec.rb +693 -0
  141. data/spec/octopus/octopus_spec.rb +123 -0
  142. data/spec/octopus/proxy_spec.rb +307 -0
  143. data/spec/octopus/relation_proxy_spec.rb +93 -0
  144. data/spec/octopus/replicated_slave_grouped_spec.rb +91 -0
  145. data/spec/octopus/replication_spec.rb +137 -0
  146. data/spec/octopus/scope_proxy_spec.rb +63 -0
  147. data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
  148. data/spec/octopus/sharded_spec.rb +33 -0
  149. data/spec/spec_helper.rb +16 -0
  150. data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +15 -0
  151. data/spec/support/database_connection.rb +4 -0
  152. data/spec/support/database_models.rb +118 -0
  153. data/spec/support/octopus_helper.rb +55 -0
  154. data/spec/support/query_count.rb +17 -0
  155. data/spec/support/shared_contexts.rb +18 -0
  156. data/spec/tasks/octopus.rake_spec.rb +32 -0
  157. metadata +388 -0
@@ -0,0 +1,39 @@
1
+ module Octopus
2
+ module Rails3
3
+ module Persistence
4
+ def update_attribute(*args)
5
+ run_on_shard { super }
6
+ end
7
+
8
+ def update_attributes(*args)
9
+ run_on_shard { super }
10
+ end
11
+
12
+ def update_attributes!(*args)
13
+ run_on_shard { super }
14
+ end
15
+
16
+ def reload(*args)
17
+ run_on_shard { super }
18
+ end
19
+
20
+ def delete
21
+ run_on_shard { super }
22
+ end
23
+
24
+ def destroy
25
+ run_on_shard { super }
26
+ end
27
+
28
+ def touch(*args)
29
+ run_on_shard { super }
30
+ end
31
+
32
+ def update_column(*args)
33
+ run_on_shard { super }
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ ActiveRecord::Base.send(:include, Octopus::Rails3::Persistence)
@@ -0,0 +1,534 @@
1
+ require 'set'
2
+ require 'octopus/slave_group'
3
+ require 'octopus/load_balancing/round_robin'
4
+
5
+ module Octopus
6
+ class Proxy
7
+ attr_accessor :config, :sharded
8
+
9
+ CURRENT_MODEL_KEY = 'octopus.current_model'.freeze
10
+ CURRENT_SHARD_KEY = 'octopus.current_shard'.freeze
11
+ CURRENT_GROUP_KEY = 'octopus.current_group'.freeze
12
+ CURRENT_SLAVE_GROUP_KEY = 'octopus.current_slave_group'.freeze
13
+ CURRENT_OPTIONS_KEY = 'octopus.current_options'.freeze
14
+ BLOCK_KEY = 'octopus.block'.freeze
15
+ LAST_CURRENT_SHARD_KEY = 'octopus.last_current_shard'.freeze
16
+ FULLY_REPLICATED_KEY = 'octopus.fully_replicated'.freeze
17
+
18
+ def initialize(config = Octopus.config)
19
+ initialize_shards(config)
20
+ initialize_replication(config) if !config.nil? && config['replicated']
21
+ end
22
+
23
+ def initialize_shards(config)
24
+ @shards = HashWithIndifferentAccess.new
25
+ @shards_slave_groups = HashWithIndifferentAccess.new
26
+ @slave_groups = HashWithIndifferentAccess.new
27
+ @groups = {}
28
+ @adapters = Set.new
29
+ @config = ActiveRecord::Base.connection_pool_without_octopus.spec.config
30
+
31
+ unless config.nil?
32
+ @entire_sharded = config['entire_sharded']
33
+ @shards_config = config[Octopus.rails_env]
34
+ end
35
+
36
+ @shards_config ||= []
37
+
38
+ @shards_config.each do |key, value|
39
+ if value.is_a?(String)
40
+ value = resolve_string_connection(value).merge(:octopus_shard => key)
41
+ initialize_adapter(value['adapter'])
42
+ @shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
43
+ elsif value.is_a?(Hash) && value.key?('adapter')
44
+ value.merge!(:octopus_shard => key)
45
+ initialize_adapter(value['adapter'])
46
+ @shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
47
+
48
+ slave_group_configs = value.select do |_k, v|
49
+ structurally_slave_group? v
50
+ end
51
+
52
+ if slave_group_configs.present?
53
+ slave_groups = HashWithIndifferentAccess.new
54
+ slave_group_configs.each do |slave_group_name, slave_configs|
55
+ slaves = HashWithIndifferentAccess.new
56
+ slave_configs.each do |slave_name, slave_config|
57
+ @shards[slave_name.to_sym] = connection_pool_for(slave_config, "#{value['adapter']}_connection")
58
+ slaves[slave_name.to_sym] = slave_name.to_sym
59
+ end
60
+ slave_groups[slave_group_name.to_sym] = Octopus::SlaveGroup.new(slaves)
61
+ end
62
+ @shards_slave_groups[key.to_sym] = slave_groups
63
+ @sharded = true
64
+ end
65
+ elsif value.is_a?(Hash)
66
+ @groups[key.to_s] = []
67
+
68
+ value.each do |k, v|
69
+ fail 'You have duplicated shard names!' if @shards.key?(k.to_sym)
70
+
71
+ initialize_adapter(v['adapter'])
72
+ config_with_octopus_shard = v.merge(:octopus_shard => k)
73
+
74
+ @shards[k.to_sym] = connection_pool_for(config_with_octopus_shard, "#{v['adapter']}_connection")
75
+ @groups[key.to_s] << k.to_sym
76
+ end
77
+
78
+ if structurally_slave_group? value
79
+ slaves = Hash[@groups[key.to_s].map { |v| [v, v] }]
80
+ @slave_groups[key.to_sym] = Octopus::SlaveGroup.new(slaves)
81
+ end
82
+ end
83
+ end
84
+
85
+ @shards[:master] ||= ActiveRecord::Base.connection_pool_without_octopus if Octopus.master_shard == :master
86
+ end
87
+
88
+ def initialize_replication(config)
89
+ @replicated = true
90
+ if config.key?('fully_replicated')
91
+ @fully_replicated = config['fully_replicated']
92
+ else
93
+ @fully_replicated = true
94
+ end
95
+
96
+ @slaves_list = @shards.keys.map(&:to_s).sort
97
+ @slaves_list.delete('master')
98
+ @slaves_load_balancer = Octopus.load_balancer.new(@slaves_list)
99
+ end
100
+
101
+ def current_model
102
+ Thread.current[CURRENT_MODEL_KEY]
103
+ end
104
+
105
+ def current_model=(model)
106
+ Thread.current[CURRENT_MODEL_KEY] = model.is_a?(ActiveRecord::Base) ? model.class : model
107
+ end
108
+
109
+ def current_shard
110
+ Thread.current[CURRENT_SHARD_KEY] ||= Octopus.master_shard
111
+ end
112
+
113
+ def current_shard=(shard_symbol)
114
+ if shard_symbol.is_a?(Array)
115
+ self.current_slave_group = nil
116
+ shard_symbol.each { |symbol| fail "Nonexistent Shard Name: #{symbol}" if @shards[symbol].nil? }
117
+ elsif shard_symbol.is_a?(Hash)
118
+ hash = shard_symbol
119
+ shard_symbol = hash[:shard]
120
+ slave_group_symbol = hash[:slave_group]
121
+
122
+ if shard_symbol.nil? && slave_group_symbol.nil?
123
+ fail 'Neither shard or slave group must be specified'
124
+ end
125
+
126
+ if shard_symbol.present?
127
+ fail "Nonexistent Shard Name: #{shard_symbol}" if @shards[shard_symbol].nil?
128
+ end
129
+
130
+ if slave_group_symbol.present?
131
+ if (@shards_slave_groups.try(:[], shard_symbol).present? && @shards_slave_groups[shard_symbol][slave_group_symbol].nil?) ||
132
+ (@shards_slave_groups.try(:[], shard_symbol).nil? && @slave_groups[slave_group_symbol].nil?)
133
+ fail "Nonexistent Slave Group Name: #{slave_group_symbol} in shards config: #{@shards_config.inspect}"
134
+ end
135
+ end
136
+ self.current_slave_group = slave_group_symbol
137
+ else
138
+ fail "Nonexistent Shard Name: #{shard_symbol}" if @shards[shard_symbol].nil?
139
+ end
140
+
141
+ Thread.current[CURRENT_SHARD_KEY] = shard_symbol
142
+ end
143
+
144
+ def current_group
145
+ Thread.current[CURRENT_GROUP_KEY]
146
+ end
147
+
148
+ def current_group=(group_symbol)
149
+ # TODO: Error message should include all groups if given more than one bad name.
150
+ [group_symbol].flatten.compact.each do |group|
151
+ fail "Nonexistent Group Name: #{group}" unless has_group?(group)
152
+ end
153
+
154
+ Thread.current[CURRENT_GROUP_KEY] = group_symbol
155
+ end
156
+
157
+ def current_slave_group
158
+ Thread.current[CURRENT_SLAVE_GROUP_KEY]
159
+ end
160
+
161
+ def current_slave_group=(slave_group_symbol)
162
+ Thread.current[CURRENT_SLAVE_GROUP_KEY] = slave_group_symbol
163
+ end
164
+
165
+ def current_options
166
+ Thread.current[CURRENT_OPTIONS_KEY]
167
+ end
168
+
169
+ def current_options=(options)
170
+ Thread.current[CURRENT_OPTIONS_KEY] = options
171
+ end
172
+
173
+ def block
174
+ Thread.current[BLOCK_KEY]
175
+ end
176
+
177
+ def block=(block)
178
+ Thread.current[BLOCK_KEY] = block
179
+ end
180
+
181
+ def last_current_shard
182
+ Thread.current[LAST_CURRENT_SHARD_KEY]
183
+ end
184
+
185
+ def last_current_shard=(last_current_shard)
186
+ Thread.current[LAST_CURRENT_SHARD_KEY] = last_current_shard
187
+ end
188
+
189
+ def fully_replicated?
190
+ @fully_replicated || Thread.current[FULLY_REPLICATED_KEY]
191
+ end
192
+
193
+ # Public: Whether or not a group exists with the given name converted to a
194
+ # string.
195
+ #
196
+ # Returns a boolean.
197
+ def has_group?(group)
198
+ @groups.key?(group.to_s)
199
+ end
200
+
201
+ # Public: Retrieves names of all loaded shards.
202
+ #
203
+ # Returns an array of shard names as symbols
204
+ def shard_names
205
+ @shards.keys
206
+ end
207
+
208
+ # Public: Retrieves the defined shards for a given group.
209
+ #
210
+ # Returns an array of shard names as symbols or nil if the group is not
211
+ # defined.
212
+ def shards_for_group(group)
213
+ @groups.fetch(group.to_s, nil)
214
+ end
215
+
216
+ # Rails 3.1 sets automatic_reconnect to false when it removes
217
+ # connection pool. Octopus can potentially retain a reference to a closed
218
+ # connection pool. Previously, that would work since the pool would just
219
+ # reconnect, but in Rails 3.1 the flag prevents this.
220
+ def safe_connection(connection_pool)
221
+ connection_pool.automatic_reconnect ||= true
222
+ if !connection_pool.connected? && @shards[Octopus.master_shard].connection.query_cache_enabled
223
+ connection_pool.connection.enable_query_cache!
224
+ end
225
+ connection_pool.connection
226
+ end
227
+
228
+ def select_connection
229
+ safe_connection(@shards[shard_name])
230
+ end
231
+
232
+ def shard_name
233
+ current_shard.is_a?(Array) ? current_shard.first : current_shard
234
+ end
235
+
236
+ def should_clean_table_name?
237
+ @adapters.size > 1
238
+ end
239
+
240
+ def run_queries_on_shard(shard, options={}, &_block)
241
+ keeping_connection_proxy(shard) do
242
+ using_shard(shard, options) do
243
+ yield
244
+ end
245
+ end
246
+ end
247
+
248
+ def send_queries_to_multiple_shards(shards, &block)
249
+ shards.map do |shard|
250
+ run_queries_on_shard(shard, &block)
251
+ end
252
+ end
253
+
254
+ def send_queries_to_group(group, &block)
255
+ using_group(group) do
256
+ send_queries_to_multiple_shards(shards_for_group(group), &block)
257
+ end
258
+ end
259
+
260
+ def send_queries_to_all_shards(&block)
261
+ send_queries_to_multiple_shards(shard_names.uniq { |shard_name| @shards[shard_name] }, &block)
262
+ end
263
+
264
+ def clean_connection_proxy
265
+ self.current_shard = Octopus.master_shard
266
+ self.current_model = nil
267
+ self.current_group = nil
268
+ self.block = nil
269
+ end
270
+
271
+ def check_schema_migrations(shard)
272
+ OctopusModel.using(shard).connection.table_exists?(
273
+ ActiveRecord::Migrator.schema_migrations_table_name,
274
+ ) || OctopusModel.using(shard).connection.initialize_schema_migrations_table
275
+ end
276
+
277
+ def transaction(options = {}, &block)
278
+ if !sharded && current_model_replicated?
279
+ run_queries_on_shard(Octopus.master_shard) do
280
+ select_connection.transaction(options, &block)
281
+ end
282
+ else
283
+ select_connection.transaction(options, &block)
284
+ end
285
+ end
286
+
287
+ def method_missing(method, *args, &block)
288
+ if should_clean_connection_proxy?(method)
289
+ conn = select_connection
290
+ self.last_current_shard = current_shard
291
+ clean_connection_proxy
292
+ conn.send(method, *args, &block)
293
+ elsif should_send_queries_to_shard_slave_group?(method)
294
+ send_queries_to_shard_slave_group(method, *args, &block)
295
+ elsif should_send_queries_to_slave_group?(method)
296
+ send_queries_to_slave_group(method, *args, &block)
297
+ elsif should_send_queries_to_replicated_databases?(method)
298
+ send_queries_to_selected_slave(method, *args, &block)
299
+ else
300
+ select_connection.send(method, *args, &block)
301
+ end
302
+ end
303
+
304
+ def respond_to?(method, include_private = false)
305
+ super || select_connection.respond_to?(method, include_private)
306
+ end
307
+
308
+ def connection_pool
309
+ @shards[current_shard]
310
+ end
311
+
312
+ def enable_query_cache!
313
+ clear_query_cache
314
+ with_each_healthy_shard { |v| v.connected? && safe_connection(v).enable_query_cache! }
315
+ end
316
+
317
+ def disable_query_cache!
318
+ with_each_healthy_shard { |v| v.connected? && safe_connection(v).disable_query_cache! }
319
+ end
320
+
321
+ def clear_query_cache
322
+ with_each_healthy_shard { |v| v.connected? && safe_connection(v).clear_query_cache }
323
+ end
324
+
325
+ def clear_active_connections!
326
+ with_each_healthy_shard(&:release_connection)
327
+ end
328
+
329
+ def clear_all_connections!
330
+ with_each_healthy_shard(&:disconnect!)
331
+ end
332
+
333
+ def connected?
334
+ @shards.any? { |_k, v| v.connected? }
335
+ end
336
+
337
+ def should_send_queries_to_shard_slave_group?(method)
338
+ should_use_slaves_for_method?(method) && @shards_slave_groups.try(:[], current_shard).try(:[], current_slave_group).present?
339
+ end
340
+
341
+ def send_queries_to_shard_slave_group(method, *args, &block)
342
+ send_queries_to_balancer(@shards_slave_groups[current_shard][current_slave_group], method, *args, &block)
343
+ end
344
+
345
+ def should_send_queries_to_slave_group?(method)
346
+ should_use_slaves_for_method?(method) && @slave_groups.try(:[], current_slave_group).present?
347
+ end
348
+
349
+ def send_queries_to_slave_group(method, *args, &block)
350
+ send_queries_to_balancer(@slave_groups[current_slave_group], method, *args, &block)
351
+ end
352
+
353
+ protected
354
+
355
+ # Ensure that a single failing slave doesn't take down the entire application
356
+ def with_each_healthy_shard
357
+ @shards.each do |shard_name, v|
358
+ begin
359
+ yield(v)
360
+ rescue => e
361
+ if Octopus.robust_environment?
362
+ Octopus.logger.error "Error on shard #{shard_name}: #{e.message}"
363
+ else
364
+ raise
365
+ end
366
+ end
367
+ end
368
+
369
+ conn_handler = ActiveRecord::Base.connection_handler
370
+ if conn_handler.respond_to?(:connection_pool_list)
371
+ # Rails 4+
372
+ ar_pools = conn_handler.connection_pool_list
373
+ else
374
+ # Rails 3.2
375
+ ar_pools = conn_handler.connection_pools.values
376
+ end
377
+
378
+ ar_pools.each do |pool|
379
+ next if pool == @shards[:master] # Already handled this
380
+
381
+ begin
382
+ yield(pool)
383
+ rescue => e
384
+ if Octopus.robust_environment?
385
+ Octopus.logger.error "Error on pool (spec: #{pool.spec}): #{e.message}"
386
+ else
387
+ raise
388
+ end
389
+ end
390
+ end
391
+ end
392
+
393
+ def connection_pool_for(adapter, config)
394
+ if Octopus.rails4?
395
+ arg = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(adapter.dup, config)
396
+ else
397
+ arg = ActiveRecord::Base::ConnectionSpecification.new(adapter.dup, config)
398
+ end
399
+
400
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(arg)
401
+ end
402
+
403
+ def initialize_adapter(adapter)
404
+ @adapters << adapter
405
+ begin
406
+ require "active_record/connection_adapters/#{adapter}_adapter"
407
+ rescue LoadError
408
+ raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$ERROR_INFO})"
409
+ end
410
+ end
411
+
412
+ def resolve_string_connection(spec)
413
+ if Octopus.rails41?
414
+ resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
415
+ HashWithIndifferentAccess.new(resolver.spec(spec).config)
416
+ else
417
+ if Octopus.rails4?
418
+ resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(spec, {})
419
+ else
420
+ resolver = ActiveRecord::Base::ConnectionSpecification::Resolver.new(spec, {})
421
+ end
422
+ HashWithIndifferentAccess.new(resolver.spec.config)
423
+ end
424
+ end
425
+
426
+ def should_clean_connection_proxy?(method)
427
+ method.to_s =~ /insert|select|execute/ && !current_model_replicated? && (!block || block != current_shard)
428
+ end
429
+
430
+ # Try to use slaves if and only if `replicated: true` is specified in `shards.yml` and no slaves groups are defined
431
+ def should_send_queries_to_replicated_databases?(method)
432
+ @replicated && method.to_s =~ /select/ && !block && !slaves_grouped?
433
+ end
434
+
435
+ def current_model_replicated?
436
+ @replicated && (current_model.try(:replicated) || fully_replicated?)
437
+ end
438
+
439
+ def send_queries_to_selected_slave(method, *args, &block)
440
+ if current_model.replicated || fully_replicated?
441
+ selected_slave = @slaves_load_balancer.next current_options
442
+ else
443
+ selected_slave = Octopus.master_shard
444
+ end
445
+
446
+ send_queries_to_slave(selected_slave, method, *args, &block)
447
+ end
448
+
449
+ # We should use slaves if and only if its safe to do so.
450
+ #
451
+ # We can safely use slaves when:
452
+ # (1) `replicated: true` is specified in `shards.yml`
453
+ # (2) The current model is `replicated()`, or `fully_replicated: true` is specified in `shards.yml` which means that
454
+ # all the model is `replicated()`
455
+ # (3) It's a SELECT query
456
+ # while ensuring that we revert `current_shard` from the selected slave to the (shard's) master
457
+ # not to make queries other than SELECT leak to the slave.
458
+ def should_use_slaves_for_method?(method)
459
+ current_model_replicated? && method.to_s =~ /select/
460
+ end
461
+
462
+ def slaves_grouped?
463
+ @slave_groups.present?
464
+ end
465
+
466
+ # Temporarily switch `current_shard` to the next slave in a slave group and send queries to it
467
+ # while preserving `current_shard`
468
+ def send_queries_to_balancer(balancer, method, *args, &block)
469
+ send_queries_to_slave(balancer.next(current_options), method, *args, &block)
470
+ end
471
+
472
+ # Temporarily switch `current_shard` to the specified slave and send queries to it
473
+ # while preserving `current_shard`
474
+ def send_queries_to_slave(slave, method, *args, &block)
475
+ using_shard(slave) do
476
+ select_connection.send(method, *args, &block)
477
+ end
478
+ end
479
+
480
+ # Temporarily block cleaning connection proxy and run the block
481
+ #
482
+ # @see Octopus::Proxy#should_clean_connection?
483
+ # @see Octopus::Proxy#clean_connection_proxy
484
+ def keeping_connection_proxy(shard, &_block)
485
+ last_block = block
486
+
487
+ begin
488
+ self.block = shard
489
+ yield
490
+ ensure
491
+ self.block = last_block || nil
492
+ end
493
+ end
494
+
495
+ # Temporarily switch `current_shard` and run the block
496
+ def using_shard(shard, options = {}, &_block)
497
+ older_shard = current_shard
498
+ older_slave_group = current_slave_group
499
+ older_options = current_options
500
+
501
+ begin
502
+ unless current_model && !current_model.allowed_shard?(shard)
503
+ self.current_shard = shard
504
+ self.current_options = options
505
+ end
506
+ yield
507
+ ensure
508
+ self.current_shard = older_shard
509
+ self.current_slave_group = older_slave_group
510
+ self.current_options = older_options
511
+ end
512
+ end
513
+
514
+ # Temporarily switch `current_group` and run the block
515
+ def using_group(group, &_block)
516
+ older_group = current_group
517
+
518
+ begin
519
+ self.current_group = group
520
+ yield
521
+ ensure
522
+ self.current_group = older_group
523
+ end
524
+ end
525
+
526
+ def structurally_slave?(config)
527
+ config.is_a?(Hash) && config.key?('adapter')
528
+ end
529
+
530
+ def structurally_slave_group?(config)
531
+ config.is_a?(Hash) && config.values.any? { |v| structurally_slave? v }
532
+ end
533
+ end
534
+ end