rails-sharding 0.1.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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +1181 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +156 -0
- data/Rakefile +52 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/generators/scaffold_generator.rb +27 -0
- data/lib/generators/templates/rails-sharding_initializer.rb +4 -0
- data/lib/generators/templates/shards.yml.example +35 -0
- data/lib/rails/sharding.rb +18 -0
- data/lib/rails/sharding/active_record_extensions.rb +90 -0
- data/lib/rails/sharding/config.rb +32 -0
- data/lib/rails/sharding/connection_handler.rb +104 -0
- data/lib/rails/sharding/core.rb +67 -0
- data/lib/rails/sharding/errors.rb +13 -0
- data/lib/rails/sharding/railtie.rb +10 -0
- data/lib/rails/sharding/shard_thread_registry.rb +48 -0
- data/lib/rails/sharding/shardable_model.rb +109 -0
- data/lib/rails/sharding/version.rb +5 -0
- data/lib/tasks/rails-sharding.rake +309 -0
- data/rails-sharding.gemspec +34 -0
- metadata +180 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rails::Sharding
|
2
|
+
class Config
|
3
|
+
DEFAULT_CONFIGS = {
|
4
|
+
# If true one connection will be established per shard (in every shard group)
|
5
|
+
# on startup. This only establishes the connection with the database but
|
6
|
+
# it does not retrieve a connection yet. This will be done by the ConnectionPool
|
7
|
+
# when necessary.
|
8
|
+
# If false the user must call Shards::ConnectionHandler.establish_connection(shard_group, shard_name)
|
9
|
+
# manually at least once before using each shard.
|
10
|
+
establish_all_connections_on_setup: true,
|
11
|
+
|
12
|
+
# If true the method #using_shard will be mixed in ActiveRecord scopes. Put
|
13
|
+
# this to false if you don't want the gem to modify ActiveRecord
|
14
|
+
extend_active_record_scope: true,
|
15
|
+
|
16
|
+
# Specifies where to find the definition of the shards configurations
|
17
|
+
shards_config_file: 'config/shards.yml',
|
18
|
+
|
19
|
+
# Specifies where to find the migrations for each shard group
|
20
|
+
shards_migrations_dir: 'db/shards_migrations',
|
21
|
+
|
22
|
+
# Specifies where to find the schemas for each shard group
|
23
|
+
shards_schemas_dir: 'db/shards_schemas'
|
24
|
+
}
|
25
|
+
|
26
|
+
DEFAULT_CONFIGS.each do |config_name, default_value|
|
27
|
+
self.cattr_accessor config_name
|
28
|
+
self.send(config_name.to_s + '=', default_value)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Rails::Sharding
|
2
|
+
class ConnectionHandler
|
3
|
+
|
4
|
+
# Establishes connections to all shards in all shard groups.
|
5
|
+
# Despite the name, this actually only creates a connection pool with zero
|
6
|
+
# connections for each shard. The connections will be allocated for each
|
7
|
+
# thread when #retrieve_connection or #with_connection are called
|
8
|
+
def self.establish_all_connections
|
9
|
+
Core.shard_groups.each do |shard_group|
|
10
|
+
Core.shard_names(shard_group).each do |shard_name|
|
11
|
+
establish_connection(shard_group, shard_name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Establishes a connection to a single shard in a single shard group
|
17
|
+
def self.establish_connection(shard_group, shard_name, environment=nil)
|
18
|
+
self.setup unless defined? @@connection_handler
|
19
|
+
|
20
|
+
unless configurations = (environment.nil? ? Core.configurations : Core.configurations(environment))
|
21
|
+
raise Errors::ConfigNotFoundError, "Cannot find configuration for environment '#{environment}' in #{Config.shards_config_file}"
|
22
|
+
end
|
23
|
+
|
24
|
+
unless shard_group_configurations = configurations[shard_group.to_s]
|
25
|
+
raise Errors::ConfigNotFoundError, "Cannot find configuration for shard_group '#{shard_group}' in environment '#{environment}' in #{Config.shards_config_file}"
|
26
|
+
end
|
27
|
+
|
28
|
+
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(shard_group_configurations)
|
29
|
+
begin
|
30
|
+
connection_spec = resolver.spec(shard_name.to_sym)
|
31
|
+
rescue ActiveRecord::AdapterNotSpecified => e
|
32
|
+
raise Errors::ConfigNotFoundError, "Cannot find configuration for shard '#{shard_group}:#{shard_name}' in environment '#{environment}' in #{Config.shards_config_file}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# since rails requires a class to be the connection owner, we trick rails passing
|
36
|
+
# an instance of the ConnectionPoolOwner class, that responds to the #name method
|
37
|
+
connection_handler.establish_connection(connection_pool_owner(shard_group, shard_name), connection_spec)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.connection_pool(shard_group, shard_name)
|
41
|
+
connection_handler.retrieve_connection_pool(connection_pool_owner(shard_group, shard_name))
|
42
|
+
rescue Errors::ConnectionPoolRetrievalError
|
43
|
+
# mimicking behavior of rails at:
|
44
|
+
# https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#507
|
45
|
+
raise ActiveRecord::ConnectionNotEstablished, "No connection pool for shard #{connection_name(shard_group, shard_name)}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.retrieve_connection(shard_group, shard_name)
|
49
|
+
connection_handler.retrieve_connection(connection_pool_owner(shard_group, shard_name))
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.connected?(shard_group, shard_name)
|
53
|
+
connection_handler.connected?(connection_pool_owner(shard_group, shard_name))
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.with_connection(shard_group, shard_name, &block)
|
57
|
+
connection_pool(shard_group, shard_name).with_connection(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.remove_connection(shard_group, shard_name)
|
61
|
+
connection_handler.remove_connection(connection_pool_owner(shard_group, shard_name))
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def self.connection_handler
|
67
|
+
raise Errors::UninitializedError, 'Shards::ConnectionHandler was not setup' unless defined? @@connection_handler
|
68
|
+
@@connection_handler
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.setup
|
72
|
+
@@connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
73
|
+
@@connection_pool_owners = {}
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.connection_pool_owner(shard_group, shard_name)
|
77
|
+
connection_name = self.connection_name(shard_group, shard_name)
|
78
|
+
@@connection_pool_owners[connection_name] ||= ConnectionPoolOwner.new(connection_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Assembles connection name in the format "shard_group:shard_name"
|
82
|
+
def self.connection_name(shard_group, shard_name)
|
83
|
+
shard_group.to_s + ':' + shard_name.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
class ConnectionPoolOwner
|
87
|
+
attr_reader :name
|
88
|
+
|
89
|
+
def initialize(name)
|
90
|
+
@name = name
|
91
|
+
end
|
92
|
+
|
93
|
+
# Safeguard in case pool cannot be retrieved for owner. This makes the error clear
|
94
|
+
def superclass
|
95
|
+
raise Errors::ConnectionPoolRetrievalError, "ConnectionPool could not be retrieved for #{self}. See https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#607"
|
96
|
+
end
|
97
|
+
|
98
|
+
# in case owner ends up printed by rails in an error message when retrieving connection
|
99
|
+
def to_s
|
100
|
+
"ConnectionPoolOwner with name #{self.name}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rails/sharding/active_record_extensions'
|
2
|
+
require 'rails/sharding/config'
|
3
|
+
require 'rails/sharding/connection_handler'
|
4
|
+
require 'rails/sharding/errors'
|
5
|
+
require 'rails/sharding/shard_thread_registry'
|
6
|
+
require 'rails/sharding/shardable_model'
|
7
|
+
|
8
|
+
module Rails::Sharding
|
9
|
+
class Core
|
10
|
+
|
11
|
+
# Opens a block where all queries will be directed to the selected shard
|
12
|
+
def self.using_shard(shard_group, shard_name)
|
13
|
+
raise 'Cannot nest using_shard blocks' if ShardThreadRegistry.connecting_to_shard?
|
14
|
+
|
15
|
+
ShardThreadRegistry.current_shard_group = shard_group
|
16
|
+
ShardThreadRegistry.current_shard_name = shard_name
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
# Releases connections in case user left some connection in the reserved state
|
20
|
+
# (by calling retrieve_connection instead of with_connection). Also, using
|
21
|
+
# normal activerecord queries leaves a connection in the reserved state
|
22
|
+
ConnectionHandler.connection_pool(*ShardThreadRegistry.current_shard_group_and_name).release_connection
|
23
|
+
ShardThreadRegistry.connect_back_to_master!
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.configurations(environment=Rails.env)
|
27
|
+
@@db_configs ||= YAML.load_file(Config.shards_config_file)
|
28
|
+
@@db_configs[environment]
|
29
|
+
rescue Errno::ENOENT => e
|
30
|
+
raise Errors::ConfigNotFoundError, Config.shards_config_file.to_s + ' file was not found'
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.reset_configurations_cache
|
34
|
+
@@db_configs = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.test_configurations
|
38
|
+
self.configurations('test')
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.shard_groups
|
42
|
+
self.configurations.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.shard_names(shard_group)
|
46
|
+
self.configurations[shard_group.to_s].keys
|
47
|
+
end
|
48
|
+
|
49
|
+
# Method that should be called on a rails initializer
|
50
|
+
def self.setup
|
51
|
+
if block_given?
|
52
|
+
yield Config
|
53
|
+
end
|
54
|
+
|
55
|
+
if Config.establish_all_connections_on_setup
|
56
|
+
# Establishes connections with all shards specified in config/shards.yml
|
57
|
+
ConnectionHandler.establish_all_connections
|
58
|
+
end
|
59
|
+
|
60
|
+
if Config.extend_active_record_scope
|
61
|
+
# includes the #using_shard method to all AR scopes
|
62
|
+
ActiveRecordExtensions.extend_active_record_scope
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rails::Sharding
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
class UninitializedError < StandardError; end
|
5
|
+
|
6
|
+
class WrongUsageError < StandardError; end
|
7
|
+
|
8
|
+
class ConnectionPoolRetrievalError < StandardError; end
|
9
|
+
|
10
|
+
class ConfigNotFoundError < StandardError; end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'active_support/per_thread_registry'
|
2
|
+
|
3
|
+
module Rails::Sharding
|
4
|
+
class ShardThreadRegistry
|
5
|
+
# creates two thread-specific variables
|
6
|
+
extend ActiveSupport::PerThreadRegistry
|
7
|
+
attr_accessor :_current_shard_group
|
8
|
+
attr_accessor :_current_shard_name
|
9
|
+
|
10
|
+
def self.connecting_to_master?
|
11
|
+
current_shard_group.nil? || current_shard_name.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.connecting_to_shard?
|
15
|
+
!connecting_to_master?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.connect_back_to_master!
|
19
|
+
self.current_shard_group = nil
|
20
|
+
self.current_shard_name = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the current shard group (for the current Thread)
|
24
|
+
def self.current_shard_group
|
25
|
+
_current_shard_group
|
26
|
+
end
|
27
|
+
|
28
|
+
# Sets the current shard group (for the current Thread)
|
29
|
+
def self.current_shard_group=(group)
|
30
|
+
self._current_shard_group = group.blank? ? nil : group.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the current shard name (for the current Thread)
|
34
|
+
def self.current_shard_name
|
35
|
+
_current_shard_name
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sets the current shard name (for the current Thread)
|
39
|
+
def self.current_shard_name=(name)
|
40
|
+
self._current_shard_name = name.blank? ? nil : name.to_sym
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.current_shard_group_and_name
|
44
|
+
[current_shard_group, current_shard_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# The purpose of this class is to override ActiveRecord::ConnectionHandling
|
2
|
+
# methods that access the DB connection or connection pool in any way.
|
3
|
+
#
|
4
|
+
# If Rails::Sharding::ShardThreadRegistry.connecting_to_master? is true, we just
|
5
|
+
# delegate all calls to the original methods of ActiveRecord::ConnectionHandling,
|
6
|
+
# otherwise we access our own ConnectionHandler and retrieve the connection
|
7
|
+
# or connection pool to the selected shard
|
8
|
+
|
9
|
+
module Rails::Sharding
|
10
|
+
module ShardableModel
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
# base.extend ClassMethods
|
14
|
+
base.extend ClassMethodsOverrides
|
15
|
+
end
|
16
|
+
|
17
|
+
# Module that includes all class methods that will be overriden on the model
|
18
|
+
# class when Rails::Sharding::ShardableModel is included
|
19
|
+
module ClassMethodsOverrides
|
20
|
+
# dinamically saves original methods with prefix "original_" and overrides
|
21
|
+
# then with the methods with prefix "sharded_"
|
22
|
+
def self.extended(klass)
|
23
|
+
self.instance_methods.each do |sharded_method_name|
|
24
|
+
method_name = sharded_method_name.to_s.match(/sharded_(.+)/)[1].to_sym
|
25
|
+
|
26
|
+
klass.singleton_class.instance_eval do
|
27
|
+
alias_method "original_#{method_name}".to_sym, method_name
|
28
|
+
alias_method method_name, sharded_method_name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @overrides ActiveRecord::ConnectionHandling#connection_pool
|
34
|
+
def sharded_connection_pool
|
35
|
+
if ShardThreadRegistry.connecting_to_master?
|
36
|
+
return original_connection_pool
|
37
|
+
else
|
38
|
+
return ConnectionHandler.connection_pool(*ShardThreadRegistry.current_shard_group_and_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @overrides ActiveRecord::ConnectionHandling#retrieve_connection
|
43
|
+
def sharded_retrieve_connection
|
44
|
+
if ShardThreadRegistry.connecting_to_master?
|
45
|
+
return original_retrieve_connection
|
46
|
+
else
|
47
|
+
return ConnectionHandler.retrieve_connection(*ShardThreadRegistry.current_shard_group_and_name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @overrides ActiveRecord::ConnectionHandling#sharded_connected?
|
52
|
+
def sharded_connected?
|
53
|
+
if ShardThreadRegistry.connecting_to_master?
|
54
|
+
return original_connected?
|
55
|
+
else
|
56
|
+
return ConnectionHandler.connected?(*ShardThreadRegistry.current_shard_group_and_name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @overrides ActiveRecord::ConnectionHandling#remove_connection (only if no parameters are passed)
|
61
|
+
def sharded_remove_connection(klass=nil)
|
62
|
+
if ShardThreadRegistry.connecting_to_master? || klass
|
63
|
+
return klass ? original_remove_connection(klass) : original_remove_connection
|
64
|
+
else
|
65
|
+
return ConnectionHandler.remove_connection(*ShardThreadRegistry.current_shard_group_and_name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @overrides ActiveRecord::ConnectionHandling#establish_connection
|
70
|
+
# In the case we are connecting to a shard, ignore spec parameter and use
|
71
|
+
# what is in ShardThreadRegistry instead
|
72
|
+
def sharded_establish_connection(spec=nil)
|
73
|
+
if ShardThreadRegistry.connecting_to_master?
|
74
|
+
return original_establish_connection(spec)
|
75
|
+
else
|
76
|
+
return ConnectionHandler.establish_connection(*ShardThreadRegistry.current_shard_group_and_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @overrides ActiveRecord::ConnectionHandling#clear_active_connections!
|
81
|
+
def sharded_clear_active_connections!
|
82
|
+
if ShardThreadRegistry.connecting_to_master?
|
83
|
+
return original_clear_active_connections!
|
84
|
+
else
|
85
|
+
return ConnectionHandler.connection_handler.clear_active_connections!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @overrides ActiveRecord::ConnectionHandling#clear_reloadable_connections!
|
90
|
+
def sharded_clear_reloadable_connections!
|
91
|
+
if ShardThreadRegistry.connecting_to_master?
|
92
|
+
return original_clear_reloadable_connections!
|
93
|
+
else
|
94
|
+
return ConnectionHandler.connection_handler.clear_reloadable_connections!
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# @overrides ActiveRecord::ConnectionHandling#clear_all_connections!
|
99
|
+
def sharded_clear_all_connections!
|
100
|
+
if ShardThreadRegistry.connecting_to_master?
|
101
|
+
return original_clear_all_connections!
|
102
|
+
else
|
103
|
+
return ConnectionHandler.connection_handler.clear_all_connections!
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
shards_namespace = namespace :shards do
|
4
|
+
task _make_activerecord_base_shardable: [:environment] do
|
5
|
+
# Several resources used (like Migrator, SchemaDumper, schema methods)
|
6
|
+
# implicitly use ActiveRecord::Base.connection, so we have to make it
|
7
|
+
# shardable so we can call using_shard and switch the connection
|
8
|
+
ActiveRecord::Base.include(Rails::Sharding::ShardableModel) unless ActiveRecord::Base.ancestors.include? Rails::Sharding::ShardableModel
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Creates database shards (options: RAILS_ENV=x SHARD_GROUP=x SHARD=x)"
|
12
|
+
task create: [:environment] do
|
13
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
14
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
15
|
+
|
16
|
+
shards_configurations.each do |shard, configuration|
|
17
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
18
|
+
puts "== Creating shard #{shard_group}:#{shard}"
|
19
|
+
ActiveRecord::Tasks::DatabaseTasks.create(configuration)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Drops database shards (options: RAILS_ENV=x SHARD_GROUP=x SHARD=x)"
|
25
|
+
task drop: [:environment] do
|
26
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
27
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
28
|
+
|
29
|
+
shards_configurations.each do |shard, configuration|
|
30
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
31
|
+
puts "== Dropping shard #{shard_group}:#{shard}"
|
32
|
+
ActiveRecord::Tasks::DatabaseTasks.drop(configuration)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Migrate the database (options: RAILS_ENV=x, SHARD_GROUP=x, VERSION=x, VERBOSE=false, SCOPE=blog)."
|
38
|
+
task migrate: [:_make_activerecord_base_shardable] do
|
39
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
40
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
41
|
+
|
42
|
+
# configures path for migrations of this shard group and creates dir if necessary
|
43
|
+
shard_group_migrations_dir = File.join(Rails::Sharding::Config.shards_migrations_dir, shard_group.to_s)
|
44
|
+
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = shard_group_migrations_dir
|
45
|
+
FileUtils.mkdir_p(shard_group_migrations_dir)
|
46
|
+
|
47
|
+
shards_configurations.each do |shard, configuration|
|
48
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
49
|
+
puts "== Migrating shard #{shard_group}:#{shard}"
|
50
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
51
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
shards_namespace["_dump"].invoke
|
57
|
+
end
|
58
|
+
|
59
|
+
# IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false
|
60
|
+
task :_dump do
|
61
|
+
if ActiveRecord::Base.dump_schema_after_migration
|
62
|
+
case ActiveRecord::Base.schema_format
|
63
|
+
when :ruby
|
64
|
+
shards_namespace["schema:dump"].invoke
|
65
|
+
when :sql
|
66
|
+
raise "sql schema dump not supported by shards"
|
67
|
+
else
|
68
|
+
raise "unknown schema format #{ActiveRecord::Base.schema_format}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# Allow this task to be called as many times as required. An example is the
|
72
|
+
# migrate:redo task, which calls other two internally that depend on this one.
|
73
|
+
shards_namespace["_dump"].reenable
|
74
|
+
end
|
75
|
+
|
76
|
+
namespace :schema do
|
77
|
+
desc "Creates a schema.rb for each shard that is portable against any DB supported by Active Record (options: RAILS_ENV=x, SHARD_GROUP=x, SHARD=x)"
|
78
|
+
task dump: [:_make_activerecord_base_shardable] do
|
79
|
+
require "active_record/schema_dumper"
|
80
|
+
|
81
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
82
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
83
|
+
|
84
|
+
# configures path for schemas of this shard group and creates dir if necessary
|
85
|
+
shard_group_schemas_dir = File.join(Rails::Sharding::Config.shards_schemas_dir, shard_group.to_s)
|
86
|
+
FileUtils.mkdir_p(shard_group_schemas_dir)
|
87
|
+
|
88
|
+
shards_configurations.each do |shard, configuration|
|
89
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
90
|
+
puts "== Dumping schema of #{shard_group}:#{shard}"
|
91
|
+
|
92
|
+
schema_filename = File.join(shard_group_schemas_dir, shard + "_schema.rb")
|
93
|
+
File.open(schema_filename, "w:utf-8") do |file|
|
94
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
95
|
+
ActiveRecord::SchemaDumper.dump(Rails::Sharding::ConnectionHandler.retrieve_connection(shard_group, shard), file)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Allow this task to be called as many times as required. An example is the
|
102
|
+
# migrate:redo task, which calls other two internally that depend on this one.
|
103
|
+
shards_namespace["schema:dump"].reenable
|
104
|
+
end
|
105
|
+
|
106
|
+
desc "Loads schema.rb file into the shards (options: RAILS_ENV=x, SHARD_GROUP=x, SHARD=x)"
|
107
|
+
task load: [:_make_activerecord_base_shardable] do
|
108
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
109
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
110
|
+
|
111
|
+
# configures path for schemas of this shard group
|
112
|
+
shard_group_schemas_path = 'db/shards_schemas/' + shard_group.to_s
|
113
|
+
Rails.application.paths.add shard_group_schemas_path
|
114
|
+
shard_group_schemas_dir = Rails.application.paths[shard_group_schemas_path].to_a.first
|
115
|
+
|
116
|
+
shards_configurations.each do |shard, configuration|
|
117
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
118
|
+
puts "== Loading schema of #{shard_group}:#{shard}"
|
119
|
+
|
120
|
+
schema_filename = File.join(shard_group_schemas_dir, shard + "_schema.rb")
|
121
|
+
ActiveRecord::Tasks::DatabaseTasks.check_schema_file(schema_filename)
|
122
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
123
|
+
load(schema_filename)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
task load_if_ruby: ["shards:create", :environment] do
|
130
|
+
shards_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
namespace :migrate do
|
135
|
+
desc 'Rollbacks the shards one migration and re migrate up (options: RAILS_ENV=x, VERSION=x, STEP=x, SHARD_GROUP=x, SHARD=x).'
|
136
|
+
task redo: [:environment] do
|
137
|
+
if ENV["VERSION"]
|
138
|
+
shards_namespace["migrate:down"].invoke
|
139
|
+
shards_namespace["migrate:up"].invoke
|
140
|
+
else
|
141
|
+
shards_namespace["rollback"].invoke
|
142
|
+
shards_namespace["migrate"].invoke
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
desc 'Resets your shards using your migrations for the current environment'
|
147
|
+
task reset: ["shards:drop", "shards:create", "shards:migrate"]
|
148
|
+
|
149
|
+
desc 'Runs the "up" for a given migration VERSION.'
|
150
|
+
task up: [:_make_activerecord_base_shardable] do
|
151
|
+
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
152
|
+
raise "VERSION is required" unless version
|
153
|
+
|
154
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
155
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
156
|
+
|
157
|
+
# configures path for migrations of this shard group
|
158
|
+
shard_group_migrations_path = 'db/shards_migrations/' + shard_group.to_s
|
159
|
+
Rails.application.paths.add shard_group_migrations_path
|
160
|
+
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths[shard_group_migrations_path].to_a
|
161
|
+
|
162
|
+
shards_configurations.each do |shard, configuration|
|
163
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
164
|
+
puts "== Migrating up shard #{shard_group}:#{shard}"
|
165
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
166
|
+
ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
shards_namespace["_dump"].invoke
|
172
|
+
end
|
173
|
+
|
174
|
+
desc 'Runs the "down" for a given migration VERSION.'
|
175
|
+
task down: [:_make_activerecord_base_shardable] do
|
176
|
+
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
177
|
+
raise "VERSION is required - To go down one migration, run db:rollback" unless version
|
178
|
+
|
179
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
180
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
181
|
+
|
182
|
+
# configures path for migrations of this shard group
|
183
|
+
shard_group_migrations_path = 'db/shards_migrations/' + shard_group.to_s
|
184
|
+
Rails.application.paths.add shard_group_migrations_path
|
185
|
+
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths[shard_group_migrations_path].to_a
|
186
|
+
|
187
|
+
shards_configurations.each do |shard, configuration|
|
188
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
189
|
+
puts "== Migrating down shard #{shard_group}:#{shard}"
|
190
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
191
|
+
ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
shards_namespace["_dump"].invoke
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
desc "Rolls the schema back to the previous version (options: RAILS_ENV=x, STEP=x, SHARD_GROUP=x, SHARD=x)."
|
201
|
+
task rollback: [:_make_activerecord_base_shardable] do
|
202
|
+
step = ENV["STEP"] ? ENV["STEP"].to_i : 1
|
203
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
204
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
205
|
+
|
206
|
+
# configures path for migrations of this shard group
|
207
|
+
shard_group_migrations_path = 'db/shards_migrations/' + shard_group.to_s
|
208
|
+
Rails.application.paths.add shard_group_migrations_path
|
209
|
+
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths[shard_group_migrations_path].to_a
|
210
|
+
|
211
|
+
shards_configurations.each do |shard, configuration|
|
212
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
213
|
+
puts "== Rolling back shard #{shard_group}:#{shard}"
|
214
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
215
|
+
ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
shards_namespace["_dump"].invoke
|
221
|
+
end
|
222
|
+
|
223
|
+
desc "Retrieves the current schema version number"
|
224
|
+
task version: [:_make_activerecord_base_shardable] do
|
225
|
+
Rails::Sharding.configurations.each do |shard_group, shards_configurations|
|
226
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
227
|
+
|
228
|
+
shards_configurations.each do |shard, configuration|
|
229
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
230
|
+
|
231
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
232
|
+
puts "Shard #{shard_group}:#{shard} version: #{ActiveRecord::Migrator.current_version}"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
namespace :test do
|
240
|
+
desc "Recreate the test shards from existent schema files (options: SHARD_GROUP=x, SHARD=x)"
|
241
|
+
task load_schema: ['shards:test:purge'] do
|
242
|
+
Rails::Sharding.test_configurations.each do |shard_group, shards_configurations|
|
243
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
244
|
+
|
245
|
+
# configures path for schemas of this shard group
|
246
|
+
shard_group_schemas_path = 'db/shards_schemas/' + shard_group.to_s
|
247
|
+
Rails.application.paths.add shard_group_schemas_path
|
248
|
+
shard_group_schemas_dir = Rails.application.paths[shard_group_schemas_path].to_a.first
|
249
|
+
|
250
|
+
shards_configurations.each do |shard, configuration|
|
251
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
252
|
+
|
253
|
+
puts "== Loading test schema on shard #{shard_group}:#{shard}"
|
254
|
+
begin
|
255
|
+
# establishes connection with test shard, saving if it was connected before
|
256
|
+
should_reconnect = Rails::Sharding::ConnectionHandler.connection_pool(shard_group, shard).active_connection?
|
257
|
+
Rails::Sharding::ConnectionHandler.establish_connection(shard_group, shard, 'test')
|
258
|
+
|
259
|
+
schema_filename = File.join(shard_group_schemas_dir, shard + "_schema.rb")
|
260
|
+
ActiveRecord::Tasks::DatabaseTasks.check_schema_file(schema_filename)
|
261
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
262
|
+
ActiveRecord::Schema.verbose = false
|
263
|
+
load(schema_filename)
|
264
|
+
end
|
265
|
+
ensure
|
266
|
+
if should_reconnect
|
267
|
+
# reestablishes connection for RAILS_ENV environment (whatever that is)
|
268
|
+
Rails::Sharding::ConnectionHandler.establish_connection(shard_group, shard)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
desc 'Load the test schema into the shards (options: SHARD_GROUP=x, SHARD=x)'
|
276
|
+
task prepare: [:environment] do
|
277
|
+
unless Rails::Sharding.test_configurations.blank?
|
278
|
+
shards_namespace["test:load_schema"].invoke
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
desc "Empty the test shards (drops all tables) (options: SHARD_GROUP=x, SHARD=x)"
|
283
|
+
task :purge => [:_make_activerecord_base_shardable] do
|
284
|
+
Rails::Sharding.test_configurations.each do |shard_group, shards_configurations|
|
285
|
+
next if ENV["SHARD_GROUP"] && ENV["SHARD_GROUP"] != shard_group.to_s
|
286
|
+
|
287
|
+
shards_configurations.each do |shard, configuration|
|
288
|
+
next if ENV["SHARD"] && ENV["SHARD"] != shard.to_s
|
289
|
+
|
290
|
+
puts "== Purging test shard #{shard_group}:#{shard}"
|
291
|
+
begin
|
292
|
+
# establishes connection with test shard, saving if it was connected before (rails 4.2 doesn't do this, but should)
|
293
|
+
should_reconnect = Rails::Sharding::ConnectionHandler.connection_pool(shard_group, shard).active_connection?
|
294
|
+
Rails::Sharding::ConnectionHandler.establish_connection(shard_group, shard, 'test')
|
295
|
+
|
296
|
+
Rails::Sharding.using_shard(shard_group, shard) do
|
297
|
+
ActiveRecord::Tasks::DatabaseTasks.purge(configuration)
|
298
|
+
end
|
299
|
+
ensure
|
300
|
+
if should_reconnect
|
301
|
+
# reestablishes connection for RAILS_ENV environment (whatever that is)
|
302
|
+
Rails::Sharding::ConnectionHandler.establish_connection(shard_group, shard)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|