activerecord-turntable 3.1.0 → 4.0.0

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.
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