activerecord-turntable 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/Gemfile +25 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +290 -0
- data/Rakefile +101 -0
- data/activerecord-turntable.gemspec +47 -0
- data/lib/active_record/turntable.rb +58 -0
- data/lib/active_record/turntable/active_record_ext.rb +26 -0
- data/lib/active_record/turntable/active_record_ext/.gitkeep +0 -0
- data/lib/active_record/turntable/active_record_ext/abstract_adapter.rb +50 -0
- data/lib/active_record/turntable/active_record_ext/clever_load.rb +90 -0
- data/lib/active_record/turntable/active_record_ext/fixtures.rb +131 -0
- data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +64 -0
- data/lib/active_record/turntable/active_record_ext/persistence.rb +95 -0
- data/lib/active_record/turntable/active_record_ext/schema_dumper.rb +107 -0
- data/lib/active_record/turntable/active_record_ext/sequencer.rb +28 -0
- data/lib/active_record/turntable/active_record_ext/transactions.rb +33 -0
- data/lib/active_record/turntable/algorithm.rb +7 -0
- data/lib/active_record/turntable/algorithm/.gitkeep +0 -0
- data/lib/active_record/turntable/algorithm/base.rb +11 -0
- data/lib/active_record/turntable/algorithm/range_algorithm.rb +37 -0
- data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +41 -0
- data/lib/active_record/turntable/base.rb +130 -0
- data/lib/active_record/turntable/cluster.rb +70 -0
- data/lib/active_record/turntable/compatible.rb +19 -0
- data/lib/active_record/turntable/config.rb +24 -0
- data/lib/active_record/turntable/connection_proxy.rb +218 -0
- data/lib/active_record/turntable/connection_proxy/mixable.rb +39 -0
- data/lib/active_record/turntable/error.rb +8 -0
- data/lib/active_record/turntable/helpers.rb +5 -0
- data/lib/active_record/turntable/helpers/test_helper.rb +25 -0
- data/lib/active_record/turntable/master_shard.rb +28 -0
- data/lib/active_record/turntable/migration.rb +132 -0
- data/lib/active_record/turntable/mixer.rb +203 -0
- data/lib/active_record/turntable/mixer/fader.rb +34 -0
- data/lib/active_record/turntable/mixer/fader/calculate_shards_sum_result.rb +15 -0
- data/lib/active_record/turntable/mixer/fader/insert_shards_merge_result.rb +24 -0
- data/lib/active_record/turntable/mixer/fader/select_shards_merge_result.rb +22 -0
- data/lib/active_record/turntable/mixer/fader/specified_shard.rb +12 -0
- data/lib/active_record/turntable/mixer/fader/update_shards_merge_result.rb +17 -0
- data/lib/active_record/turntable/pool_proxy.rb +56 -0
- data/lib/active_record/turntable/rack.rb +5 -0
- data/lib/active_record/turntable/rack/connection_management.rb +18 -0
- data/lib/active_record/turntable/railtie.rb +14 -0
- data/lib/active_record/turntable/railties/databases.rake +205 -0
- data/lib/active_record/turntable/seq_shard.rb +14 -0
- data/lib/active_record/turntable/sequencer.rb +46 -0
- data/lib/active_record/turntable/sequencer/api.rb +36 -0
- data/lib/active_record/turntable/sequencer/mysql.rb +32 -0
- data/lib/active_record/turntable/shard.rb +48 -0
- data/lib/active_record/turntable/sql_tree_patch.rb +199 -0
- data/lib/active_record/turntable/version.rb +5 -0
- data/lib/activerecord-turntable.rb +2 -0
- data/lib/generators/active_record/turntable/install_generator.rb +14 -0
- data/lib/generators/templates/turntable.yml +40 -0
- data/sample_app/.gitignore +16 -0
- data/sample_app/Gemfile +41 -0
- data/sample_app/README.rdoc +261 -0
- data/sample_app/Rakefile +7 -0
- data/sample_app/app/assets/images/rails.png +0 -0
- data/sample_app/app/assets/javascripts/application.js +15 -0
- data/sample_app/app/assets/stylesheets/application.css +13 -0
- data/sample_app/app/controllers/application_controller.rb +3 -0
- data/sample_app/app/helpers/application_helper.rb +2 -0
- data/sample_app/app/mailers/.gitkeep +0 -0
- data/sample_app/app/models/.gitkeep +0 -0
- data/sample_app/app/models/user.rb +4 -0
- data/sample_app/app/views/layouts/application.html.erb +14 -0
- data/sample_app/config.ru +4 -0
- data/sample_app/config/application.rb +65 -0
- data/sample_app/config/boot.rb +6 -0
- data/sample_app/config/database.yml +70 -0
- data/sample_app/config/environment.rb +5 -0
- data/sample_app/config/environments/development.rb +37 -0
- data/sample_app/config/environments/production.rb +67 -0
- data/sample_app/config/environments/test.rb +37 -0
- data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
- data/sample_app/config/initializers/inflections.rb +15 -0
- data/sample_app/config/initializers/mime_types.rb +5 -0
- data/sample_app/config/initializers/secret_token.rb +7 -0
- data/sample_app/config/initializers/session_store.rb +8 -0
- data/sample_app/config/initializers/wrap_parameters.rb +14 -0
- data/sample_app/config/locales/en.yml +5 -0
- data/sample_app/config/routes.rb +58 -0
- data/sample_app/config/turntable.yml +64 -0
- data/sample_app/db/migrate/20120316073058_create_users.rb +11 -0
- data/sample_app/db/seeds.rb +7 -0
- data/sample_app/lib/assets/.gitkeep +0 -0
- data/sample_app/lib/tasks/.gitkeep +0 -0
- data/sample_app/log/.gitkeep +0 -0
- data/sample_app/public/404.html +26 -0
- data/sample_app/public/422.html +26 -0
- data/sample_app/public/500.html +25 -0
- data/sample_app/public/favicon.ico +0 -0
- data/sample_app/public/index.html +241 -0
- data/sample_app/public/robots.txt +5 -0
- data/sample_app/script/rails +6 -0
- data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/sample_app/vendor/plugins/.gitkeep +0 -0
- data/script/performance/algorithm +32 -0
- data/spec/active_record/turntable/active_record_ext/clever_load_spec.rb +81 -0
- data/spec/active_record/turntable/active_record_ext/persistence_spec.rb +151 -0
- data/spec/active_record/turntable/algorithm/range_algorithm_spec.rb +35 -0
- data/spec/active_record/turntable/algorithm_spec.rb +69 -0
- data/spec/active_record/turntable/base_spec.rb +13 -0
- data/spec/active_record/turntable/cluster_spec.rb +18 -0
- data/spec/active_record/turntable/config_spec.rb +17 -0
- data/spec/active_record/turntable/connection_proxy_spec.rb +186 -0
- data/spec/active_record/turntable/finder_spec.rb +27 -0
- data/spec/active_record/turntable/mixer/fader_spec.rb +4 -0
- data/spec/active_record/turntable/mixer_spec.rb +114 -0
- data/spec/active_record/turntable/shard_spec.rb +21 -0
- data/spec/active_record/turntable_spec.rb +30 -0
- data/spec/config/database.yml +45 -0
- data/spec/config/turntable.yml +17 -0
- data/spec/fabricators/.gitkeep +0 -0
- data/spec/fabricators/turntable_fabricator.rb +14 -0
- data/spec/migrations/.gitkeep +0 -0
- data/spec/migrations/001_create_users.rb +16 -0
- data/spec/migrations/002_create_user_statuses.rb +16 -0
- data/spec/migrations/003_create_cards.rb +14 -0
- data/spec/migrations/004_create_cards_users.rb +15 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/test_models.rb +27 -0
- data/spec/turntable_helper.rb +29 -0
- metadata +367 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
module ActiveRecord::Turntable::ActiveRecordExt
|
2
|
+
module Persistence
|
3
|
+
if ActiveRecord::VERSION::STRING < '3.1'
|
4
|
+
::ActiveRecord::Persistence.class_eval do
|
5
|
+
def destroy
|
6
|
+
klass = self.class
|
7
|
+
if persisted?
|
8
|
+
condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
|
9
|
+
if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
|
10
|
+
condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
|
11
|
+
end
|
12
|
+
condition_scope.delete_all
|
13
|
+
end
|
14
|
+
|
15
|
+
@destroyed = true
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# overrides ActiveRecord::Persistence's original method so that
|
22
|
+
def update(attribute_names = @attributes.keys)
|
23
|
+
klass = self.class
|
24
|
+
attributes_with_values = arel_attributes_values(false, false, attribute_names)
|
25
|
+
return 0 if attributes_with_values.empty?
|
26
|
+
condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
|
27
|
+
if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
|
28
|
+
condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
|
29
|
+
end
|
30
|
+
condition_scope.arel.update(attributes_with_values)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
else
|
35
|
+
::ActiveRecord::Persistence.class_eval do
|
36
|
+
def destroy
|
37
|
+
klass = self.class
|
38
|
+
destroy_associations
|
39
|
+
|
40
|
+
if persisted?
|
41
|
+
ActiveRecord::IdentityMap.remove(self) if ActiveRecord::IdentityMap.enabled?
|
42
|
+
pk = klass.primary_key
|
43
|
+
column = klass.columns_hash[pk]
|
44
|
+
substitute = connection.substitute_at(column, 0)
|
45
|
+
|
46
|
+
relation = klass.unscoped.where(klass.arel_table[pk].eq(substitute))
|
47
|
+
if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
|
48
|
+
relation = relation.where(klass.turntable_shard_key => self.send(turntable_shard_key))
|
49
|
+
end
|
50
|
+
relation.bind_values = [[column, id]]
|
51
|
+
relation.delete_all
|
52
|
+
end
|
53
|
+
|
54
|
+
@destroyed = true
|
55
|
+
freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy_without_callbacks
|
59
|
+
klass = self.class
|
60
|
+
destroy_associations
|
61
|
+
|
62
|
+
if persisted?
|
63
|
+
ActiveRecord::IdentityMap.remove(self) if ActiveRecord::IdentityMap.enabled?
|
64
|
+
pk = klass.primary_key
|
65
|
+
column = klass.columns_hash[pk]
|
66
|
+
substitute = connection.substitute_at(column, 0)
|
67
|
+
|
68
|
+
relation = klass.unscoped.where(klass.arel_table[pk].eq(substitute))
|
69
|
+
if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
|
70
|
+
relation = relation.where(klass.turntable_shard_key => self.send(turntable_shard_key))
|
71
|
+
end
|
72
|
+
relation.bind_values = [[column, id]]
|
73
|
+
relation.delete_all
|
74
|
+
end
|
75
|
+
|
76
|
+
@destroyed = true
|
77
|
+
freeze
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def update(attribute_names = @attributes.keys)
|
82
|
+
attributes_with_values = arel_attributes_values(false, false, attribute_names)
|
83
|
+
return 0 if attributes_with_values.empty?
|
84
|
+
klass = self.class
|
85
|
+
condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
|
86
|
+
if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
|
87
|
+
condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
|
88
|
+
end
|
89
|
+
stmt = condition_scope.arel.compile_update(attributes_with_values)
|
90
|
+
klass.connection.update stmt.to_sql
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module ActiveRecord::Turntable
|
3
|
+
module ActiveRecordExt
|
4
|
+
module SchemaDumper
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
alias_method_chain :table, :turntable
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def table_with_turntable(table, stream)
|
14
|
+
columns = @connection.columns(table)
|
15
|
+
begin
|
16
|
+
tbl = StringIO.new
|
17
|
+
|
18
|
+
# first dump primary key column
|
19
|
+
if @connection.respond_to?(:pk_and_sequence_for)
|
20
|
+
pk, _ = @connection.pk_and_sequence_for(table)
|
21
|
+
elsif @connection.respond_to?(:primary_key)
|
22
|
+
pk = @connection.primary_key(table)
|
23
|
+
end
|
24
|
+
|
25
|
+
# turntable sequencer dump
|
26
|
+
if table =~ /\A(.*)_id_seq\z/
|
27
|
+
tbl.print " create_sequence_for #{$1.inspect}"
|
28
|
+
else
|
29
|
+
tbl.print " create_table #{table.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
if columns.detect { |c| c.name == pk }
|
33
|
+
if pk != 'id'
|
34
|
+
tbl.print %Q(, :primary_key => "#{pk}")
|
35
|
+
end
|
36
|
+
else
|
37
|
+
tbl.print ", :id => false"
|
38
|
+
end
|
39
|
+
tbl.print ", :force => true"
|
40
|
+
tbl.puts " do |t|"
|
41
|
+
|
42
|
+
# then dump all non-primary key columns
|
43
|
+
column_specs = columns.map do |column|
|
44
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
|
45
|
+
next if column.name == pk
|
46
|
+
spec = {}
|
47
|
+
spec[:name] = column.name.inspect
|
48
|
+
|
49
|
+
# AR has an optimisation which handles zero-scale decimals as integers. This
|
50
|
+
# code ensures that the dumper still dumps the column as a decimal.
|
51
|
+
spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
|
52
|
+
'decimal'
|
53
|
+
else
|
54
|
+
column.type.to_s
|
55
|
+
end
|
56
|
+
spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
|
57
|
+
spec[:precision] = column.precision.inspect if column.precision
|
58
|
+
spec[:scale] = column.scale.inspect if column.scale
|
59
|
+
spec[:null] = 'false' unless column.null
|
60
|
+
spec[:default] = default_string(column.default) if column.has_default?
|
61
|
+
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
|
62
|
+
spec
|
63
|
+
end.compact
|
64
|
+
|
65
|
+
# find all migration keys used in this table
|
66
|
+
keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
|
67
|
+
|
68
|
+
# figure out the lengths for each column based on above keys
|
69
|
+
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
|
70
|
+
|
71
|
+
# the string we're going to sprintf our values against, with standardized column widths
|
72
|
+
format_string = lengths.map{ |len| "%-#{len}s" }
|
73
|
+
|
74
|
+
# find the max length for the 'type' column, which is special
|
75
|
+
type_length = column_specs.map{ |column| column[:type].length }.max
|
76
|
+
|
77
|
+
# add column type definition to our format string
|
78
|
+
format_string.unshift " t.%-#{type_length}s "
|
79
|
+
|
80
|
+
format_string *= ''
|
81
|
+
|
82
|
+
column_specs.each do |colspec|
|
83
|
+
values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
|
84
|
+
values.unshift colspec[:type]
|
85
|
+
tbl.print((format_string % values).gsub(/,\s*$/, ''))
|
86
|
+
tbl.puts
|
87
|
+
end
|
88
|
+
|
89
|
+
tbl.puts " end"
|
90
|
+
tbl.puts
|
91
|
+
|
92
|
+
indexes(table, tbl)
|
93
|
+
|
94
|
+
tbl.rewind
|
95
|
+
stream.print tbl.read
|
96
|
+
rescue => e
|
97
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
98
|
+
stream.puts "# #{e.message}"
|
99
|
+
stream.puts
|
100
|
+
end
|
101
|
+
|
102
|
+
stream
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord::Turntable::ActiveRecordExt
|
2
|
+
module Sequencer
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include DatabaseStatements
|
7
|
+
alias_method_chain :prefetch_primary_key?, :turntable
|
8
|
+
end
|
9
|
+
|
10
|
+
module DatabaseStatements
|
11
|
+
def default_sequence_name(table_name, pk = nil)
|
12
|
+
ActiveRecord::Turntable::Sequencer.sequence_name(table_name, pk)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def prefetch_primary_key_with_turntable?(table_name = nil)
|
17
|
+
ActiveRecord::Turntable::Sequencer.has_sequencer?(table_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_sequence_value(sequence_name)
|
21
|
+
ActiveRecord::Turntable::Sequencer.sequences[sequence_name].next_sequence_value(sequence_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_sequence_value(sequence_name)
|
25
|
+
ActiveRecord::Turntable::Sequencer.sequences[sequence_name].current_sequence_value(sequence_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ActiveRecord::Turntable
|
2
|
+
module ActiveRecordExt
|
3
|
+
module Transactions
|
4
|
+
def with_transaction_returning_status
|
5
|
+
if self.class.turntable_enabled?
|
6
|
+
status = nil
|
7
|
+
if self.new_record? and self.turntable_shard_key.to_s == self.class.primary_key and
|
8
|
+
self.id.nil? and connection.prefetch_primary_key?(self.class.table_name)
|
9
|
+
self.id = connection.next_sequence_value(self.class.sequence_name)
|
10
|
+
end
|
11
|
+
self.class.connection.shards_transaction([self.turntable_shard]) do
|
12
|
+
add_to_transaction
|
13
|
+
status = yield
|
14
|
+
raise ActiveRecord::Rollback unless status
|
15
|
+
end
|
16
|
+
status
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_to_transaction
|
23
|
+
if self.class.turntable_enabled?
|
24
|
+
if self.turntable_shard.connection.add_transaction_record(self)
|
25
|
+
remember_transaction_record_state
|
26
|
+
end
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module ActiveRecord::Turntable
|
2
|
+
module Algorithm
|
3
|
+
autoload :Base, "active_record/turntable/algorithm/base"
|
4
|
+
autoload :RangeAlgorithm, "active_record/turntable/algorithm/range_algorithm"
|
5
|
+
autoload :RangeBsearchAlgorithm, "active_record/turntable/algorithm/range_bsearch_algorithm"
|
6
|
+
end
|
7
|
+
end
|
File without changes
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module ActiveRecord::Turntable::Algorithm
|
3
|
+
class RangeAlgorithm < Base
|
4
|
+
def initialize(config)
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
def calculate(key)
|
9
|
+
idx = calculate_idx(key)
|
10
|
+
@config["shards"][idx]["connection"]
|
11
|
+
rescue
|
12
|
+
raise ActiveRecord::Turntable::CannotSpecifyShardError, "cannot specify shard for key:#{key}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def calculate_idx(key)
|
16
|
+
@config["shards"].find_index {|h| h["less_than"] > key }
|
17
|
+
end
|
18
|
+
|
19
|
+
# { connection_name => weight, ... }
|
20
|
+
def calculate_used_shards_with_weight(sequence_value)
|
21
|
+
idx = calculate_idx(sequence_value)
|
22
|
+
last_connection = calculate(sequence_value)
|
23
|
+
shards = @config["shards"][0..idx]
|
24
|
+
weighted_hash = Hash.new {|h,k| h[k]=0}
|
25
|
+
prev_max = 0
|
26
|
+
shards.each_with_index do |h,idx|
|
27
|
+
weighted_hash[h["connection"]] += if idx < shards.size - 1
|
28
|
+
h["less_than"] - prev_max - 1
|
29
|
+
else
|
30
|
+
sequence_value - prev_max
|
31
|
+
end
|
32
|
+
prev_max = h["less_than"] - 1
|
33
|
+
end
|
34
|
+
return weighted_hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'bsearch'
|
3
|
+
module ActiveRecord::Turntable::Algorithm
|
4
|
+
class RangeBsearchAlgorithm < Base
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
@config["shards"].sort_by! {|a| a["less_than"]}
|
8
|
+
end
|
9
|
+
|
10
|
+
def calculate(key)
|
11
|
+
idx = calculate_idx(key)
|
12
|
+
@config["shards"][idx]["connection"]
|
13
|
+
rescue
|
14
|
+
raise ActiveRecord::Turntable::CannotSpecifyShardError, "cannot specify shard for key:#{key}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def calculate_idx(key)
|
18
|
+
@config["shards"].bsearch_lower_boundary { |h|
|
19
|
+
h["less_than"] <=> key
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
# { connection_name => weight, ... }
|
24
|
+
def calculate_used_shards_with_weight(sequence_value)
|
25
|
+
idx = calculate_idx(sequence_value)
|
26
|
+
last_connection = calculate(sequence_value)
|
27
|
+
shards = @config["shards"][0..idx]
|
28
|
+
weighted_hash = Hash.new {|h,k| h[k]=0}
|
29
|
+
prev_max = 0
|
30
|
+
shards.each_with_index do |h,idx|
|
31
|
+
weighted_hash[h["connection"]] += if idx < shards.size - 1
|
32
|
+
h["less_than"] - prev_max - 1
|
33
|
+
else
|
34
|
+
sequence_value - prev_max
|
35
|
+
end
|
36
|
+
prev_max = h["less_than"] - 1
|
37
|
+
end
|
38
|
+
return weighted_hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module ActiveRecord::Turntable
|
2
|
+
module Base
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include Compatible
|
7
|
+
class_attribute :turntable_connections,
|
8
|
+
:turntable_enabled, :turntable_sequencer_enabled
|
9
|
+
|
10
|
+
self.turntable_connections = {}
|
11
|
+
self.turntable_enabled = false
|
12
|
+
self.turntable_sequencer_enabled = false
|
13
|
+
class << self
|
14
|
+
delegate :shards_transaction, :to => :connection
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def turntable(cluster_name, shard_key_name, options = {})
|
20
|
+
class_attribute :turntable_shard_key,
|
21
|
+
:turntable_cluster, :turntable_cluster_name
|
22
|
+
|
23
|
+
self.turntable_enabled = true
|
24
|
+
self.turntable_cluster_name = cluster_name
|
25
|
+
self.turntable_shard_key = shard_key_name
|
26
|
+
self.turntable_cluster = Cluster.new(
|
27
|
+
self,
|
28
|
+
turntable_config[:clusters][cluster_name],
|
29
|
+
options
|
30
|
+
)
|
31
|
+
turntable_replace_connection_pool
|
32
|
+
end
|
33
|
+
|
34
|
+
def force_transaction_all_shards!(options={}, &block)
|
35
|
+
force_connect_all_shards!
|
36
|
+
shards = turntable_connections.values
|
37
|
+
shards += [ActiveRecord::Base.connection_pool]
|
38
|
+
recursive_transaction(shards, options, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def recursive_transaction(pools, options, &block)
|
42
|
+
pool = pools.shift
|
43
|
+
if pools.present?
|
44
|
+
pool.connection.transaction(options) do
|
45
|
+
recursive_transaction(pools, options, &block)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
pool.connection.transaction(options, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def force_connect_all_shards!
|
53
|
+
conf = configurations[Rails.env]
|
54
|
+
shards = conf["shards"]
|
55
|
+
shards = shards.merge(conf["seq"]) if conf["seq"]
|
56
|
+
shards.each do |name, config|
|
57
|
+
turntable_connections[name] ||=
|
58
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec_for(config))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def turntable_replace_connection_pool
|
63
|
+
ch = connection_handler
|
64
|
+
cp = turntable_cluster.connection_proxy
|
65
|
+
if ActiveRecord::VERSION::STRING >= '3.2.0'
|
66
|
+
ch.connection_pools[cp.spec] = PoolProxy.new(cp)
|
67
|
+
ch.instance_variable_get(:@class_to_pool)[name] = ch.connection_pools[cp.spec]
|
68
|
+
else
|
69
|
+
ch.connection_pools[name] = PoolProxy.new(cp)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def spec_for(config)
|
74
|
+
begin
|
75
|
+
require "active_record/connection_adapters/#{config['adapter']}_adapter"
|
76
|
+
rescue LoadError => e
|
77
|
+
raise "Please install the #{config['adapter']} adapter: `gem install activerecord-#{config['adapter']}-adapter` (#{e})"
|
78
|
+
end
|
79
|
+
adapter_method = "#{config['adapter']}_connection"
|
80
|
+
ActiveRecord::Base::ConnectionSpecification.new(config, adapter_method)
|
81
|
+
end
|
82
|
+
|
83
|
+
def clear_all_connections!
|
84
|
+
turntable_connections.values.each do |pool|
|
85
|
+
pool.disconnect!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def sequencer
|
90
|
+
class_attribute :turntable_sequencer
|
91
|
+
self.turntable_sequencer_enabled = true
|
92
|
+
self.turntable_sequencer = ActiveRecord::Turntable::Sequencer.build(self)
|
93
|
+
end
|
94
|
+
|
95
|
+
def turntable_enabled?
|
96
|
+
turntable_enabled
|
97
|
+
end
|
98
|
+
|
99
|
+
def sequencer_enabled?
|
100
|
+
turntable_sequencer_enabled
|
101
|
+
end
|
102
|
+
|
103
|
+
def current_sequence
|
104
|
+
connection.current_sequence_value(self.sequence_name) if sequencer_enabled?
|
105
|
+
end
|
106
|
+
|
107
|
+
def current_last_shard
|
108
|
+
turntable_cluster.select_shard(current_sequence) if sequencer_enabled?
|
109
|
+
end
|
110
|
+
|
111
|
+
def weighted_random_shard_with(*klasses, &block)
|
112
|
+
shards_weight = self.turntable_cluster.weighted_shards
|
113
|
+
sum = shards_weight.values.inject(&:+)
|
114
|
+
idx = rand(sum)
|
115
|
+
shard, weight = shards_weight.find {|k,v|
|
116
|
+
(idx -= v) < 0
|
117
|
+
}
|
118
|
+
self.connection.with_recursive_shards(shard.name, *klasses, &block)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def shards_transaction(options = {}, &block)
|
123
|
+
self.class.shards_transaction(options, &block)
|
124
|
+
end
|
125
|
+
|
126
|
+
def turntable_shard
|
127
|
+
turntable_cluster.select_shard(self.send(turntable_shard_key))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|