activerecord-turntable 3.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +25 -9
  4. data/CHANGELOG.md +25 -0
  5. data/Gemfile +2 -0
  6. data/Guardfile +1 -1
  7. data/README.md +202 -8
  8. data/Rakefile +42 -67
  9. data/activerecord-turntable.gemspec +3 -2
  10. data/gemfiles/rails5_0_0.gemfile +2 -0
  11. data/gemfiles/rails5_0_1.gemfile +2 -0
  12. data/gemfiles/rails5_0_2.gemfile +2 -0
  13. data/gemfiles/rails5_0_3.gemfile +2 -0
  14. data/gemfiles/rails5_0_4.gemfile +8 -0
  15. data/gemfiles/rails5_0_5.gemfile +8 -0
  16. data/gemfiles/rails5_1_0.gemfile +2 -0
  17. data/gemfiles/rails5_1_1.gemfile +2 -0
  18. data/gemfiles/rails5_1_2.gemfile +9 -0
  19. data/gemfiles/rails5_1_3.gemfile +9 -0
  20. data/gemfiles/rails5_1_4.gemfile +9 -0
  21. data/gemfiles/rails5_1_5.gemfile +9 -0
  22. data/lib/active_record/turntable.rb +10 -20
  23. data/lib/active_record/turntable/active_record_ext.rb +1 -1
  24. data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +5 -1
  25. data/lib/active_record/turntable/active_record_ext/sequencer.rb +11 -11
  26. data/lib/active_record/turntable/active_record_ext/transactions.rb +5 -4
  27. data/lib/active_record/turntable/algorithm.rb +12 -0
  28. data/lib/active_record/turntable/algorithm/base.rb +6 -2
  29. data/lib/active_record/turntable/algorithm/hash_slot_algorithm.rb +35 -0
  30. data/lib/active_record/turntable/algorithm/modulo_algorithm.rb +3 -7
  31. data/lib/active_record/turntable/algorithm/range_algorithm.rb +3 -34
  32. data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +16 -28
  33. data/lib/active_record/turntable/base.rb +43 -39
  34. data/lib/active_record/turntable/cluster.rb +31 -29
  35. data/lib/active_record/turntable/cluster_helper_methods.rb +12 -2
  36. data/lib/active_record/turntable/cluster_registry.rb +7 -0
  37. data/lib/active_record/turntable/configuration.rb +50 -0
  38. data/lib/active_record/turntable/configuration/dsl.rb +79 -0
  39. data/lib/active_record/turntable/configuration/loader.rb +10 -0
  40. data/lib/active_record/turntable/configuration/loader/dsl.rb +23 -0
  41. data/lib/active_record/turntable/configuration/loader/yaml.rb +62 -0
  42. data/lib/active_record/turntable/configuration_methods.rb +26 -0
  43. data/lib/active_record/turntable/connection_proxy.rb +51 -40
  44. data/lib/active_record/turntable/{master_shard.rb → default_shard.rb} +6 -2
  45. data/lib/active_record/turntable/deprecation.rb +8 -0
  46. data/lib/active_record/turntable/error.rb +2 -1
  47. data/lib/active_record/turntable/migration.rb +3 -7
  48. data/lib/active_record/turntable/mixer.rb +10 -10
  49. data/lib/active_record/turntable/mixer/fader.rb +1 -1
  50. data/lib/active_record/turntable/pool_proxy.rb +5 -3
  51. data/lib/active_record/turntable/railtie.rb +11 -4
  52. data/lib/active_record/turntable/seq_shard.rb +8 -9
  53. data/lib/active_record/turntable/sequencer.rb +18 -43
  54. data/lib/active_record/turntable/sequencer/api.rb +3 -5
  55. data/lib/active_record/turntable/sequencer/barrage.rb +1 -2
  56. data/lib/active_record/turntable/sequencer/katsubushi.rb +27 -0
  57. data/lib/active_record/turntable/sequencer/mysql.rb +14 -6
  58. data/lib/active_record/turntable/sequencer_registry.rb +30 -0
  59. data/lib/active_record/turntable/shard.rb +31 -10
  60. data/lib/active_record/turntable/shard_registry.rb +36 -0
  61. data/lib/active_record/turntable/slave_registry.rb +21 -0
  62. data/lib/active_record/turntable/slave_shard.rb +9 -0
  63. data/lib/active_record/turntable/version.rb +1 -1
  64. data/lib/generators/templates/turntable.rb +50 -0
  65. data/lib/generators/templates/turntable.yml +18 -0
  66. metadata +54 -20
  67. data/lib/active_record/turntable/config.rb +0 -26
@@ -0,0 +1,26 @@
1
+ module ActiveRecord::Turntable
2
+ module ConfigurationMethods
3
+ DEFAULT_PATH = File.dirname(File.dirname(__FILE__))
4
+
5
+ def turntable_configuration_file
6
+ @turntable_configuration_file ||= File.join(turntable_app_root_path, "config/turntable.yml")
7
+ end
8
+ alias_method :turntable_config_file, :turntable_configuration_file
9
+ deprecate turntable_config_file: "use turntable_configuration_file instead", deprecator: ActiveRecord::Turntable::Deprecation.instance
10
+
11
+ def turntable_configuration_file=(filename)
12
+ @turntable_configuration_file = filename
13
+ end
14
+ alias_method :turntable_config_file=, :turntable_configuration_file=
15
+ deprecate "turntable_config_file=": "use turntable_configuration_file= instead", deprecator: ActiveRecord::Turntable::Deprecation.instance
16
+
17
+ def turntable_app_root_path
18
+ defined?(::Rails.root) ? ::Rails.root.to_s : DEFAULT_PATH
19
+ end
20
+
21
+ def turntable_config
22
+ turntable_configuration
23
+ end
24
+ deprecate turntable_config: "use turntable_configuration instead", deprecator: ActiveRecord::Turntable::Deprecation.instance
25
+ end
26
+ end
@@ -7,14 +7,13 @@ module ActiveRecord::Turntable
7
7
  # for expiring query cache
8
8
  CLEAR_CACHE_METHODS = [:update, :insert, :delete, :exec_insert, :exec_update, :exec_delete, :insert_many].freeze
9
9
 
10
- attr_reader :klass
10
+ attr_reader :klass, :default_shard, :default_current_shard
11
11
  attr_writer :spec
12
12
 
13
- def initialize(klass, cluster, options = {})
14
- @klass = klass
15
- @cluster = cluster
16
- @master_shard = MasterShard.new(klass)
17
- @default_current_shard = @master_shard
13
+ def initialize(klass, options = {})
14
+ @klass = klass
15
+ @default_shard = DefaultShard.new(klass)
16
+ @default_current_shard = @default_shard
18
17
  @mixer = ActiveRecord::Turntable::Mixer.new(self)
19
18
  end
20
19
 
@@ -26,10 +25,16 @@ module ActiveRecord::Turntable
26
25
  :change_column, :change_column_default, :rename_column, :add_index,
27
26
  :remove_index, :initialize_schema_information,
28
27
  :dump_schema_information, :execute_ignore_duplicate,
29
- :query_cache_enabled, to: :master_connection
28
+ :query_cache_enabled, to: :default_connection
29
+
30
+ def cluster
31
+ klass.turntable_cluster
32
+ end
30
33
 
31
34
  def transaction(options = {}, &block)
32
- connection.transaction(options, &block)
35
+ with_master {
36
+ connection.transaction(options, &block)
37
+ }
33
38
  end
34
39
 
35
40
  def cache
@@ -52,7 +57,7 @@ module ActiveRecord::Turntable
52
57
  end
53
58
 
54
59
  def enable_query_cache!
55
- master_connection.enable_query_cache!
60
+ default_connection.enable_query_cache!
56
61
 
57
62
  klass.turntable_connections.each do |_k, v|
58
63
  v.connection.enable_query_cache!
@@ -60,7 +65,7 @@ module ActiveRecord::Turntable
60
65
  end
61
66
 
62
67
  def disable_query_cache!
63
- master_connection.disable_query_cache!
68
+ default_connection.disable_query_cache!
64
69
 
65
70
  klass.turntable_connections.each do |_k, v|
66
71
  v.connection.disable_query_cache!
@@ -72,7 +77,7 @@ module ActiveRecord::Turntable
72
77
  end
73
78
 
74
79
  def clear_query_cache
75
- master_connection.clear_query_cache
80
+ default_connection.clear_query_cache
76
81
 
77
82
  klass.turntable_connections.each do |_k, v|
78
83
  v.connection.clear_query_cache
@@ -104,13 +109,11 @@ module ActiveRecord::Turntable
104
109
  end
105
110
 
106
111
  def to_sql(arel, binds = [])
107
- master.connection.to_sql(arel, binds)
112
+ default_connection.to_sql(arel, binds)
108
113
  end
109
114
 
110
- attr_reader :cluster
111
-
112
115
  def shards
113
- @cluster.shards
116
+ cluster.shards
114
117
  end
115
118
 
116
119
  def shard_fixed?
@@ -125,16 +128,8 @@ module ActiveRecord::Turntable
125
128
  fixed_shard_entry[object_id] = shard
126
129
  end
127
130
 
128
- def master
129
- @master_shard
130
- end
131
-
132
- def master_connection
133
- master.connection
134
- end
135
-
136
- def seq
137
- @cluster.seq || master
131
+ def default_connection
132
+ default_shard.connection
138
133
  end
139
134
 
140
135
  def current_shard
@@ -176,7 +171,7 @@ module ActiveRecord::Turntable
176
171
  end
177
172
 
178
173
  def with_recursive_shards(connection_name, *klasses, &block)
179
- with_shard(shards[connection_name]) do
174
+ with_shard(cluster.shard_registry[connection_name]) do
180
175
  if klasses.blank?
181
176
  yield
182
177
  else
@@ -186,10 +181,26 @@ module ActiveRecord::Turntable
186
181
  end
187
182
  end
188
183
 
184
+ def with_master
185
+ old = cluster.slave_enabled?
186
+ cluster.set_slave_enabled(false)
187
+ yield
188
+ ensure
189
+ cluster.set_slave_enabled(old)
190
+ end
191
+
192
+ def with_slave
193
+ old = cluster.slave_enabled?
194
+ cluster.set_slave_enabled(true)
195
+ yield
196
+ ensure
197
+ cluster.set_slave_enabled(old)
198
+ end
199
+
189
200
  # Send queries to all shards in this cluster
190
201
  # @param [Boolean] continue_on_error when a shard raises error, ignore exception and continue
191
202
  def with_all(continue_on_error = false)
192
- @cluster.shards.values.map do |shard|
203
+ cluster.shards.map do |shard|
193
204
  begin
194
205
  with_shard(shard) {
195
206
  yield
@@ -203,10 +214,10 @@ module ActiveRecord::Turntable
203
214
  end
204
215
  end
205
216
 
206
- # Send queries to master connection and all shards in this cluster
217
+ # Send queries to default connection and all shards in this cluster
207
218
  # @param [Boolean] continue_on_error when a shard raises error, ignore exception and continue
208
- def with_master_and_all(continue_on_error = false)
209
- ([master] + @cluster.shards.values).map do |shard|
219
+ def with_default_and_all(continue_on_error = false)
220
+ ([default_shard] + cluster.shards).map do |shard|
210
221
  begin
211
222
  with_shard(shard) {
212
223
  yield
@@ -220,8 +231,8 @@ module ActiveRecord::Turntable
220
231
  end
221
232
  end
222
233
 
223
- def with_master(&block)
224
- with_shard(master) do
234
+ def with_default_shard(&block)
235
+ with_shard(default_shard) do
225
236
  yield
226
237
  end
227
238
  end
@@ -231,13 +242,13 @@ module ActiveRecord::Turntable
231
242
 
232
243
  %w(columns columns_hash column_defaults primary_keys).each do |name|
233
244
  define_method(name.to_sym) do
234
- master.connection_pool.send(name.to_sym)
245
+ default_shard.connection_pool.send(name.to_sym)
235
246
  end
236
247
  end
237
248
 
238
249
  %w(data_source_exists?).each do |name|
239
250
  define_method(name.to_sym) do |*args|
240
- master.connection_pool.with_connection do |c|
251
+ default_shard.connection_pool.with_connection do |c|
241
252
  c.schema_cache.send(name.to_sym, *args)
242
253
  end
243
254
  end
@@ -245,26 +256,26 @@ module ActiveRecord::Turntable
245
256
 
246
257
  def columns(*args)
247
258
  if args.size > 0
248
- master.connection_pool.columns[*args]
259
+ default_shard.connection_pool.columns[*args]
249
260
  else
250
- master.connection_pool.columns
261
+ default_shard.connection_pool.columns
251
262
  end
252
263
  end
253
264
 
254
265
  def pk_and_sequence_for(*args)
255
- master.connection.send(:pk_and_sequence_for, *args)
266
+ default_shard.connection.send(:pk_and_sequence_for, *args)
256
267
  end
257
268
 
258
269
  def primary_key(*args)
259
- master.connection.send(:primary_key, *args)
270
+ default_shard.connection.send(:primary_key, *args)
260
271
  end
261
272
 
262
273
  def supports_views?(*args)
263
- master.connection.send(:supports_views?, *args)
274
+ default_shard.connection.send(:supports_views?, *args)
264
275
  end
265
276
 
266
277
  def spec
267
- @spec ||= master.connection_pool.spec
278
+ @spec ||= default_shard.connection_pool.spec
268
279
  end
269
280
 
270
281
  private
@@ -1,8 +1,8 @@
1
1
  module ActiveRecord::Turntable
2
- class MasterShard < Shard
2
+ class DefaultShard < Shard
3
3
  def initialize(klass)
4
4
  (klass and original_connection_pool(klass)) or
5
- raise MasterShardNotConnected, "connection_pool is nil"
5
+ raise DefaultShardNotConnected, "connection_pool is nil"
6
6
  @klass = klass
7
7
  @name = "master"
8
8
  end
@@ -16,6 +16,10 @@ module ActiveRecord::Turntable
16
16
  end
17
17
  end
18
18
 
19
+ def support_slave?
20
+ false
21
+ end
22
+
19
23
  private
20
24
 
21
25
  def original_connection_pool(klass = @klass)
@@ -0,0 +1,8 @@
1
+ module ActiveRecord::Turntable
2
+ class Deprecation < ActiveSupport::Deprecation
3
+ def initialize(deprecation_horizon = "4.1",
4
+ gem_name = "activerecord-turntable")
5
+ super
6
+ end
7
+ end
8
+ end
@@ -2,6 +2,7 @@ module ActiveRecord::Turntable
2
2
  class TurntableError < StandardError; end
3
3
  class SequenceNotFoundError < TurntableError; end
4
4
  class CannotSpecifyShardError < TurntableError; end
5
- class MasterShardNotConnected < TurntableError; end
5
+ class DefaultShardNotConnected < TurntableError; end
6
6
  class UnknownOperatorError < TurntableError; end
7
+ class InvalidConfigurationError < TurntableError; end
7
8
  end
@@ -12,17 +12,13 @@ module ActiveRecord::Turntable::Migration
12
12
 
13
13
  module ShardDefinition
14
14
  def clusters(*cluster_names)
15
- config = ActiveRecord::Base.turntable_config
15
+ config = ActiveRecord::Base.turntable_configuration
16
16
  (self.target_shards ||= []).concat(
17
17
  if cluster_names.first == :all
18
- config[:clusters].map do |_name, cluster_conf|
19
- cluster_conf[:shards].map { |shard| shard[:connection] }
20
- end
18
+ config.clusters.map(&:shards).flatten
21
19
  else
22
20
  cluster_names.map do |cluster_name|
23
- config[:clusters][cluster_name][:shards].map do |shard|
24
- shard[:connection]
25
- end
21
+ config.cluster(cluster_name).shards
26
22
  end.flatten
27
23
  end
28
24
  )
@@ -43,9 +43,9 @@ module ActiveRecord::Turntable
43
43
  when SQLTree::Node::InsertQuery
44
44
  build_insert_fader(tree, method, query, *args, &block)
45
45
  else
46
- # send to master shard
46
+ # send to default shard
47
47
  Fader::SpecifiedShard.new(@proxy,
48
- { @proxy.master => query },
48
+ { @proxy.default_shard => query },
49
49
  method, query, *args, &block)
50
50
  end
51
51
  rescue Exception => err
@@ -119,7 +119,7 @@ module ActiveRecord::Turntable
119
119
  query = sql.is_a?(String) ? sql : @proxy.to_sql(sql, binds)
120
120
  if query.include?("\0") && binds.is_a?(Array) && binds[0].is_a?(Array) && binds[0][0].is_a?(ActiveRecord::ConnectionAdapters::Column)
121
121
  binds = binds.dup
122
- query.gsub("\0") { @proxy.master.connection.quote(*binds.shift.reverse) }
122
+ query.gsub("\0") { @proxy.default_shard.connection.quote(*binds.shift.reverse) }
123
123
  else
124
124
  query
125
125
  end
@@ -144,7 +144,7 @@ module ActiveRecord::Turntable
144
144
  elsif SQLTree::Node::SelectDeclaration === tree.select.first &&
145
145
  tree.select.first.to_sql == '1 AS "one"' # for `SELECT 1 AS one` (AR::Base.exists?)
146
146
  return Fader::SelectShardsMergeResult.new(@proxy,
147
- build_shards_with_same_query(@proxy.shards.values, query),
147
+ build_shards_with_same_query(@proxy.shards, query),
148
148
  method, query, *args, &block)
149
149
  elsif tree.group_by || tree.order_by || tree.limit.try(:value).to_i > 0
150
150
  raise CannotSpecifyShardError, "cannot specify shard for query: #{tree.to_sql}"
@@ -152,7 +152,7 @@ module ActiveRecord::Turntable
152
152
  if SQLTree::Node::SelectDeclaration === tree.select.first &&
153
153
  SQLTree::Node::CountAggregrate === tree.select.first.expression
154
154
  return Fader::CalculateShardsSumResult.new(@proxy,
155
- build_shards_with_same_query(@proxy.shards.values, query),
155
+ build_shards_with_same_query(@proxy.shards, query),
156
156
  method, query, *args, &block)
157
157
  else
158
158
  return Fader::SelectShardsMergeResult.new(@proxy,
@@ -167,7 +167,7 @@ module ActiveRecord::Turntable
167
167
  raise CannotSpecifyShardError, "[Performance Notice] PLEASE FIX: #{tree.to_sql}"
168
168
  end
169
169
  return Fader::CalculateShardsSumResult.new(@proxy,
170
- build_shards_with_same_query(@proxy.shards.values, query),
170
+ build_shards_with_same_query(@proxy.shards, query),
171
171
  method, query, *args, &block)
172
172
  elsif SQLTree::Node::AllFieldsDeclaration === tree.select.first ||
173
173
  SQLTree::Node::Expression::Value === tree.select.first.expression ||
@@ -177,7 +177,7 @@ module ActiveRecord::Turntable
177
177
  raise CannotSpecifyShardError, "[Performance Notice] PLEASE FIX: #{tree.to_sql}"
178
178
  end
179
179
  return Fader::SelectShardsMergeResult.new(@proxy,
180
- build_shards_with_same_query(@proxy.shards.values, query),
180
+ build_shards_with_same_query(@proxy.shards, query),
181
181
  method, query, *args, &block)
182
182
  else
183
183
  raise CannotSpecifyShardError, "cannot specify shard for query: #{tree.to_sql}"
@@ -190,7 +190,7 @@ module ActiveRecord::Turntable
190
190
  shards_with_query = if shard_keys.present?
191
191
  build_shards_with_same_query(shard_keys.map { |k| @proxy.cluster.shard_for(k) }, query)
192
192
  else
193
- build_shards_with_same_query(@proxy.shards.values, query)
193
+ build_shards_with_same_query(@proxy.shards, query)
194
194
  end
195
195
 
196
196
  if shards_with_query.size == 1
@@ -223,11 +223,11 @@ module ActiveRecord::Turntable
223
223
  end
224
224
 
225
225
  def raise_on_not_specified_shard_query?
226
- ActiveRecord::Base.turntable_config[:raise_on_not_specified_shard_query]
226
+ ActiveRecord::Base.turntable_configuration.raise_on_not_specified_shard_query
227
227
  end
228
228
 
229
229
  def raise_on_not_specified_shard_update?
230
- ActiveRecord::Base.turntable_config[:raise_on_not_specified_shard_update]
230
+ ActiveRecord::Base.turntable_configuration.raise_on_not_specified_shard_update
231
231
  end
232
232
  end
233
233
  end
@@ -11,7 +11,7 @@ module ActiveRecord::Turntable
11
11
  autoload :SelectShardsMergeResult
12
12
  autoload :InsertShardsMergeResult
13
13
  autoload :UpdateShardsMergeResult
14
- # calcurations
14
+ # calculations
15
15
  autoload :CalculateShardsSumResult
16
16
  end
17
17
 
@@ -46,9 +46,11 @@ module ActiveRecord::Turntable
46
46
 
47
47
  def connection_pools_list
48
48
  pools = []
49
- pools << proxy.master.connection_pool
50
- pools << proxy.seq.try(:connection_pool) if proxy.respond_to?(:seq)
51
- pools.concat(proxy.shards.values.map(&:connection_pool))
49
+ pools << proxy.default_shard.connection_pool
50
+ if proxy.respond_to?(:sequencers)
51
+ pools.concat proxy.cluster.sequencers.values.map { |s| s.try(:connection_pool) }.compact
52
+ end
53
+ pools.concat(proxy.cluster.shards.map(&:connection_pool))
52
54
  pools.compact
53
55
  end
54
56
  end
@@ -13,12 +13,19 @@ module ActiveRecord::Turntable
13
13
  end
14
14
 
15
15
  # initialize
16
- initializer "turntable.initialize_clusters" do
16
+ initializer "turntable.initialize_clusters" do |app|
17
+ app.paths.add "config/turntable", with: "config/turntable.rb"
18
+ app.paths.add "config/turntable", with: "config/turntable.yml"
19
+
17
20
  ActiveSupport.on_load(:active_record) do
18
- if File.exist?(ActiveRecord::Base.turntable_config_file)
19
- ActiveRecord::Turntable::Config.load!
21
+ path = app.paths["config/turntable"].existent.first
22
+ self.turntable_configuration_file = path
23
+
24
+ if path
25
+ reset_turntable_configuration(Configuration.load(turntable_configuration_file, Rails.env))
20
26
  else
21
- warn("[activerecord-turntable] config/turntable.yml is not found. skipped initliazing cluster.")
27
+ # FIXME: suppress this warning during rails g turntable:install
28
+ warn("[activerecord-turntable] config/turntable.{rb,yml} is not found. skipped initliazing cluster.")
22
29
  end
23
30
  end
24
31
  end
@@ -1,5 +1,13 @@
1
1
  module ActiveRecord::Turntable
2
2
  class SeqShard < Shard
3
+ def initialize(name = defined?(Rails) ? Rails.env : "development")
4
+ super(nil, name)
5
+ end
6
+
7
+ def support_slave?
8
+ false
9
+ end
10
+
3
11
  private
4
12
 
5
13
  def create_connection_class
@@ -8,14 +16,5 @@ module ActiveRecord::Turntable
8
16
  klass.establish_connection ActiveRecord::Base.connection_pool.spec.config[:seq][name].with_indifferent_access
9
17
  klass
10
18
  end
11
-
12
- def retrieve_connection_pool
13
- ActiveRecord::Base.turntable_connections[name] ||=
14
- begin
15
- config = ActiveRecord::Base.configurations[Rails.env]["seq"][name]
16
- raise ArgumentError, "Unknown database config: #{name}, have #{ActiveRecord::Base.configurations.inspect}" unless config
17
- ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec_for(config))
18
- end
19
- end
20
19
  end
21
20
  end