switchman 3.4.2 → 3.5.3
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 +4 -4
- data/Rakefile +15 -14
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/active_record/associations.rb +16 -5
- data/lib/switchman/active_record/attribute_methods.rb +67 -22
- data/lib/switchman/active_record/base.rb +32 -12
- data/lib/switchman/active_record/calculations.rb +54 -37
- data/lib/switchman/active_record/connection_pool.rb +4 -2
- data/lib/switchman/active_record/database_configurations.rb +20 -10
- data/lib/switchman/active_record/finder_methods.rb +1 -1
- data/lib/switchman/active_record/log_subscriber.rb +2 -2
- data/lib/switchman/active_record/migration.rb +4 -2
- data/lib/switchman/active_record/persistence.rb +18 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
- data/lib/switchman/active_record/query_cache.rb +1 -1
- data/lib/switchman/active_record/query_methods.rb +72 -26
- data/lib/switchman/active_record/relation.rb +13 -7
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/tasks/database_tasks.rb +1 -1
- data/lib/switchman/active_record/test_fixtures.rb +19 -16
- data/lib/switchman/active_support/cache.rb +4 -1
- data/lib/switchman/arel.rb +6 -6
- data/lib/switchman/call_super.rb +1 -1
- data/lib/switchman/database_server.rb +22 -16
- data/lib/switchman/default_shard.rb +3 -3
- data/lib/switchman/engine.rb +33 -18
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +4 -1
- data/lib/switchman/guard_rail/relation.rb +1 -1
- data/lib/switchman/parallel.rb +1 -1
- data/lib/switchman/r_spec_helper.rb +10 -10
- data/lib/switchman/shard.rb +85 -54
- data/lib/switchman/sharded_instrumenter.rb +5 -1
- data/lib/switchman/test_helper.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +12 -4
- data/lib/tasks/switchman.rake +40 -37
- metadata +9 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "securerandom"
|
|
4
4
|
|
|
5
5
|
module Switchman
|
|
6
6
|
class DatabaseServer
|
|
@@ -16,13 +16,13 @@ module Switchman
|
|
|
16
16
|
|
|
17
17
|
def find(id_or_all)
|
|
18
18
|
return all if id_or_all == :all
|
|
19
|
-
return id_or_all.
|
|
19
|
+
return id_or_all.filter_map { |id| database_servers[id || ::Rails.env] }.uniq if id_or_all.is_a?(Array)
|
|
20
20
|
|
|
21
21
|
database_servers[id_or_all || ::Rails.env]
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def create(settings = {})
|
|
25
|
-
raise
|
|
25
|
+
raise "database servers should be set up in database.yml" unless ::Rails.env.test?
|
|
26
26
|
|
|
27
27
|
id = settings[:id]
|
|
28
28
|
unless id
|
|
@@ -64,8 +64,8 @@ module Switchman
|
|
|
64
64
|
@database_servers = {}.with_indifferent_access
|
|
65
65
|
roles = []
|
|
66
66
|
::ActiveRecord::Base.configurations.configurations.each do |config|
|
|
67
|
-
if config.name.include?(
|
|
68
|
-
name, role = config.name.split(
|
|
67
|
+
if config.name.include?("/")
|
|
68
|
+
name, role = config.name.split("/")
|
|
69
69
|
else
|
|
70
70
|
name, role = config.env_name, config.name
|
|
71
71
|
end
|
|
@@ -81,6 +81,8 @@ module Switchman
|
|
|
81
81
|
# Do this after so that all database servers for all roles are established and we won't prematurely
|
|
82
82
|
# configure a connection for the wrong role
|
|
83
83
|
@all_roles = roles.uniq
|
|
84
|
+
return @database_servers if @database_servers.empty?
|
|
85
|
+
|
|
84
86
|
Shard.send(:configure_connects_to)
|
|
85
87
|
end
|
|
86
88
|
@database_servers
|
|
@@ -110,8 +112,9 @@ module Switchman
|
|
|
110
112
|
self.class.send(:database_servers).delete(id) if id
|
|
111
113
|
Shard.sharded_models.each do |klass|
|
|
112
114
|
self.class.all_roles.each do |role|
|
|
113
|
-
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
|
114
|
-
|
|
115
|
+
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
|
116
|
+
role: role,
|
|
117
|
+
shard: id.to_sym)
|
|
115
118
|
end
|
|
116
119
|
end
|
|
117
120
|
end
|
|
@@ -142,11 +145,13 @@ module Switchman
|
|
|
142
145
|
# value of GuardRail.environment)
|
|
143
146
|
def guard!(environment = :secondary)
|
|
144
147
|
DatabaseServer.send(:reference_role, environment)
|
|
145
|
-
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
|
|
148
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
|
|
149
|
+
klasses: [::ActiveRecord::Base] }
|
|
146
150
|
end
|
|
147
151
|
|
|
148
152
|
def unguard!
|
|
149
|
-
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
|
|
153
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
|
|
154
|
+
klasses: [::ActiveRecord::Base] }
|
|
150
155
|
end
|
|
151
156
|
|
|
152
157
|
def unguard
|
|
@@ -162,7 +167,7 @@ module Switchman
|
|
|
162
167
|
|
|
163
168
|
def shards
|
|
164
169
|
if id == ::Rails.env
|
|
165
|
-
Shard.where(
|
|
170
|
+
Shard.where("database_server_id IS NULL OR database_server_id=?", id)
|
|
166
171
|
else
|
|
167
172
|
Shard.where(database_server_id: id)
|
|
168
173
|
end
|
|
@@ -187,7 +192,7 @@ module Switchman
|
|
|
187
192
|
end
|
|
188
193
|
|
|
189
194
|
id ||= begin
|
|
190
|
-
id_seq = Shard.connection.quote(Shard.connection.quote_table_name(
|
|
195
|
+
id_seq = Shard.connection.quote(Shard.connection.quote_table_name("switchman_shards_id_seq"))
|
|
191
196
|
next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
|
|
192
197
|
next_id.to_i
|
|
193
198
|
end
|
|
@@ -204,17 +209,18 @@ module Switchman
|
|
|
204
209
|
name: name,
|
|
205
210
|
database_server_id: self.id)
|
|
206
211
|
if create_statement
|
|
207
|
-
if ::ActiveRecord::Base.connection.select_value(
|
|
212
|
+
if ::ActiveRecord::Base.connection.select_value(
|
|
213
|
+
"SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"
|
|
214
|
+
)
|
|
208
215
|
schema_already_existed = true
|
|
209
|
-
raise
|
|
216
|
+
raise "This schema already exists; cannot overwrite"
|
|
210
217
|
end
|
|
211
218
|
Array(create_statement.call).each do |stmt|
|
|
212
219
|
::ActiveRecord::Base.connection.execute(stmt)
|
|
213
220
|
end
|
|
214
221
|
end
|
|
215
|
-
if config[:adapter] ==
|
|
216
|
-
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor
|
|
217
|
-
end
|
|
222
|
+
if config[:adapter] == "postgresql"
|
|
223
|
+
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {}
|
|
218
224
|
end
|
|
219
225
|
old_verbose = ::ActiveRecord::Migration.verbose
|
|
220
226
|
::ActiveRecord::Migration.verbose = false
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Switchman
|
|
4
4
|
class DefaultShard
|
|
5
5
|
def id
|
|
6
|
-
|
|
6
|
+
"default"
|
|
7
7
|
end
|
|
8
|
-
|
|
8
|
+
alias_method :cache_key, :id
|
|
9
9
|
def activate(*_classes)
|
|
10
10
|
yield
|
|
11
11
|
end
|
|
@@ -58,7 +58,7 @@ module Switchman
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def _dump(_depth)
|
|
61
|
-
|
|
61
|
+
""
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def self._load(_str)
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -10,19 +10,23 @@ module Switchman
|
|
|
10
10
|
|
|
11
11
|
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
|
12
12
|
|
|
13
|
-
# after :initialize_dependency_mechanism to ensure autoloading is
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
# after :initialize_dependency_mechanism to ensure autoloading is
|
|
14
|
+
# configured for any downstream initializers that care. In rails 7.0 we
|
|
15
|
+
# should be able to just use an explicit after on configuring the once
|
|
16
|
+
# autoloaders and not need to go monkey around with initializer order
|
|
17
|
+
if ::Rails.version < "7.0"
|
|
18
|
+
initialize_dependency_mechanism = ::Rails::Application::Bootstrap.initializers.find do |i|
|
|
19
|
+
i.name == :initialize_dependency_mechanism
|
|
20
|
+
end
|
|
17
21
|
initialize_dependency_mechanism.instance_variable_get(:@options)[:after] = :set_autoload_paths
|
|
18
22
|
end
|
|
19
23
|
|
|
20
|
-
initializer
|
|
21
|
-
before:
|
|
22
|
-
after: (::Rails.version <
|
|
24
|
+
initializer "switchman.active_record_patch",
|
|
25
|
+
before: "active_record.initialize_database",
|
|
26
|
+
after: ((::Rails.version < "7.0") ? :initialize_dependency_mechanism : :setup_once_autoloader) do
|
|
23
27
|
::ActiveSupport.on_load(:active_record) do
|
|
24
28
|
# Switchman requires postgres, so just always load the pg adapter
|
|
25
|
-
require
|
|
29
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
|
26
30
|
|
|
27
31
|
self.default_shard = ::Rails.env.to_sym
|
|
28
32
|
self.default_role = :primary
|
|
@@ -50,14 +54,20 @@ module Switchman
|
|
|
50
54
|
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::Associations::CollectionProxy)
|
|
51
55
|
|
|
52
56
|
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Associations::Preloader::Association)
|
|
53
|
-
|
|
57
|
+
unless ::Rails.version < "7.0"
|
|
58
|
+
::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
|
|
59
|
+
ActiveRecord::Associations::Preloader::Association::LoaderRecords
|
|
60
|
+
)
|
|
61
|
+
end
|
|
54
62
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
|
|
55
63
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
56
64
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
57
65
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
58
66
|
|
|
59
67
|
::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
|
|
60
|
-
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
68
|
+
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
69
|
+
ActiveRecord::DatabaseConfigurations::DatabaseConfig
|
|
70
|
+
)
|
|
61
71
|
|
|
62
72
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
63
73
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
@@ -77,8 +87,12 @@ module Switchman
|
|
|
77
87
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
|
78
88
|
::ActiveRecord::Relation.include(CallSuper)
|
|
79
89
|
|
|
80
|
-
::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(
|
|
81
|
-
|
|
90
|
+
::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(
|
|
91
|
+
ActiveRecord::PredicateBuilder::AssociationQueryValue
|
|
92
|
+
)
|
|
93
|
+
::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(
|
|
94
|
+
ActiveRecord::PredicateBuilder::AssociationQueryValue
|
|
95
|
+
)
|
|
82
96
|
|
|
83
97
|
::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
|
|
84
98
|
|
|
@@ -96,17 +110,18 @@ module Switchman
|
|
|
96
110
|
|
|
97
111
|
::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
|
|
98
112
|
end
|
|
99
|
-
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
|
113
|
+
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
|
114
|
+
# initializers can go try to load Switchman::Shard or we get a loop
|
|
100
115
|
::ActiveRecord::Base
|
|
101
116
|
end
|
|
102
117
|
|
|
103
|
-
initializer
|
|
118
|
+
initializer "switchman.error_patch", after: "active_record.initialize_database" do
|
|
104
119
|
::ActiveSupport.on_load(:active_record) do
|
|
105
120
|
::StandardError.include(StandardError)
|
|
106
121
|
end
|
|
107
122
|
end
|
|
108
123
|
|
|
109
|
-
initializer
|
|
124
|
+
initializer "switchman.initialize_cache", before: :initialize_cache, after: "active_record.initialize_database" do
|
|
110
125
|
::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
|
|
111
126
|
|
|
112
127
|
# if we haven't already setup our cache map out-of-band, set it up from
|
|
@@ -130,11 +145,11 @@ module Switchman
|
|
|
130
145
|
Switchman.config[:cache_map][::Rails.env] = value
|
|
131
146
|
end
|
|
132
147
|
|
|
133
|
-
middlewares = Switchman.config[:cache_map].values.
|
|
148
|
+
middlewares = Switchman.config[:cache_map].values.filter_map do |store|
|
|
134
149
|
store.middleware if store.respond_to?(:middleware)
|
|
135
|
-
end.
|
|
150
|
+
end.uniq
|
|
136
151
|
middlewares.each do |middleware|
|
|
137
|
-
config.middleware.insert_before(
|
|
152
|
+
config.middleware.insert_before("Rack::Runtime", middleware)
|
|
138
153
|
end
|
|
139
154
|
|
|
140
155
|
# prevent :initialize_cache from trying to (or needing to) set
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "etc"
|
|
4
4
|
|
|
5
5
|
module Switchman
|
|
6
6
|
class Environment
|
|
7
|
-
def self.cpu_count(nproc_bin =
|
|
7
|
+
def self.cpu_count(nproc_bin = "nproc")
|
|
8
8
|
return Etc.nprocessors if Etc.respond_to?(:nprocessors)
|
|
9
9
|
|
|
10
10
|
`#{nproc_bin}`.to_i
|
data/lib/switchman/errors.rb
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
module Switchman
|
|
4
4
|
module Errors
|
|
5
5
|
class ManuallyCreatedShadowRecordError < RuntimeError
|
|
6
|
-
|
|
6
|
+
DEFAULT_MSG = "It looks like you're trying to manually create a shadow record. " \
|
|
7
|
+
"Please use Switchman::ActiveRecord::Base#save_shadow_record instead."
|
|
8
|
+
|
|
9
|
+
def initialize(msg = DEFAULT_MSG)
|
|
7
10
|
super
|
|
8
11
|
end
|
|
9
12
|
end
|
|
@@ -13,7 +13,7 @@ module Switchman
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
%w[update_all delete_all].each do |method|
|
|
16
|
-
arg_params = RUBY_VERSION <=
|
|
16
|
+
arg_params = (RUBY_VERSION <= "2.8") ? "*args" : "*args, **kwargs"
|
|
17
17
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
18
18
|
def #{method}(#{arg_params})
|
|
19
19
|
db = Shard.current(connection_class_for_self).database_server
|
data/lib/switchman/parallel.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "switchman/test_helper"
|
|
4
4
|
|
|
5
5
|
module Switchman
|
|
6
6
|
# including this module in your specs will give you several shards to
|
|
@@ -34,9 +34,9 @@ module Switchman
|
|
|
34
34
|
groups = group.class.descendant_filtered_examples.map(&:example_group).uniq
|
|
35
35
|
next unless groups.any? { |descendant_group| RSpecHelper.included_in?(descendant_group) }
|
|
36
36
|
|
|
37
|
-
puts
|
|
37
|
+
puts "Setting up sharding for all specs..."
|
|
38
38
|
Shard.delete_all
|
|
39
|
-
Switchman.cache.delete(
|
|
39
|
+
Switchman.cache.delete("default_shard")
|
|
40
40
|
|
|
41
41
|
@@shard1, @@shard2 = TestHelper.recreate_persistent_test_shards
|
|
42
42
|
@@default_shard = Shard.default
|
|
@@ -48,7 +48,7 @@ module Switchman
|
|
|
48
48
|
@@shard1 = @@shard1.create_new_shard
|
|
49
49
|
@@shard2 = @@shard2.create_new_shard
|
|
50
50
|
rescue => e
|
|
51
|
-
warn
|
|
51
|
+
warn "Sharding setup FAILED!:"
|
|
52
52
|
while e
|
|
53
53
|
warn "\n#{e}\n"
|
|
54
54
|
warn e.backtrace
|
|
@@ -66,9 +66,9 @@ module Switchman
|
|
|
66
66
|
# we'll re-persist in the group's `before :all`; we don't want them to exist
|
|
67
67
|
# in the db before then
|
|
68
68
|
Shard.delete_all
|
|
69
|
-
Switchman.cache.delete(
|
|
69
|
+
Switchman.cache.delete("default_shard")
|
|
70
70
|
Shard.default(reload: true)
|
|
71
|
-
puts
|
|
71
|
+
puts "Done!"
|
|
72
72
|
|
|
73
73
|
main_pid = Process.pid
|
|
74
74
|
at_exit do
|
|
@@ -76,7 +76,7 @@ module Switchman
|
|
|
76
76
|
|
|
77
77
|
# preserve rspec's exit status
|
|
78
78
|
status = $!.is_a?(::SystemExit) ? $!.status : nil
|
|
79
|
-
puts
|
|
79
|
+
puts "Tearing down sharding for all specs"
|
|
80
80
|
@@shard1.database_server.destroy unless @@shard1.database_server == Shard.default.database_server
|
|
81
81
|
unless @@keep_the_shards
|
|
82
82
|
@@shard1.drop_database
|
|
@@ -95,7 +95,7 @@ module Switchman
|
|
|
95
95
|
dup = @@default_shard.dup
|
|
96
96
|
dup.id = @@default_shard.id
|
|
97
97
|
dup.save!
|
|
98
|
-
Switchman.cache.delete(
|
|
98
|
+
Switchman.cache.delete("default_shard")
|
|
99
99
|
Shard.default(reload: true)
|
|
100
100
|
dup = @@shard1.dup
|
|
101
101
|
dup.id = @@shard1.id
|
|
@@ -107,7 +107,7 @@ module Switchman
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
klass.before do
|
|
110
|
-
raise
|
|
110
|
+
raise "Sharding did not set up correctly" if @@sharding_failed
|
|
111
111
|
|
|
112
112
|
Shard.clear_cache
|
|
113
113
|
if use_transactional_tests
|
|
@@ -132,7 +132,7 @@ module Switchman
|
|
|
132
132
|
klass.after(:all) do
|
|
133
133
|
# Don't truncate because that can create some fun cross-connection lock contention
|
|
134
134
|
Shard.delete_all
|
|
135
|
-
Switchman.cache.delete(
|
|
135
|
+
Switchman.cache.delete("default_shard")
|
|
136
136
|
Shard.default(reload: true)
|
|
137
137
|
end
|
|
138
138
|
end
|
data/lib/switchman/shard.rb
CHANGED
|
@@ -36,13 +36,15 @@ module Switchman
|
|
|
36
36
|
|
|
37
37
|
# Now find the actual record, if it exists
|
|
38
38
|
@default = begin
|
|
39
|
-
find_cached(
|
|
39
|
+
find_cached("default_shard") { Shard.where(default: true).take } || default
|
|
40
40
|
rescue
|
|
41
41
|
default
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# make sure this is not erroneously cached
|
|
45
|
-
|
|
45
|
+
if @default.database_server.instance_variable_defined?(:@primary_shard)
|
|
46
|
+
@default.database_server.remove_instance_variable(:@primary_shard)
|
|
47
|
+
end
|
|
46
48
|
|
|
47
49
|
# and finally, check for cached references to the default shard on the existing connection
|
|
48
50
|
sharded_models.each do |klass|
|
|
@@ -75,28 +77,30 @@ module Switchman
|
|
|
75
77
|
klass.current_switchman_shard != shard
|
|
76
78
|
|
|
77
79
|
(activated_classes ||= []) << klass
|
|
78
|
-
klass.connected_to_stack << { shard: shard.database_server.id.to_sym,
|
|
80
|
+
klass.connected_to_stack << { shard: shard.database_server.id.to_sym,
|
|
81
|
+
klasses: [klass],
|
|
82
|
+
switchman_shard: shard }
|
|
79
83
|
end
|
|
80
84
|
activated_classes
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
def active_shards
|
|
84
|
-
sharded_models.
|
|
88
|
+
sharded_models.filter_map do |klass|
|
|
85
89
|
[klass, current(klass)]
|
|
86
|
-
end.
|
|
90
|
+
end.to_h
|
|
87
91
|
end
|
|
88
92
|
|
|
89
93
|
def lookup(id)
|
|
90
94
|
id_i = id.to_i
|
|
91
|
-
return current if id_i == current.id || id ==
|
|
92
|
-
return default if id_i == default.id || id.nil? || id ==
|
|
95
|
+
return current if id_i == current.id || id == "self"
|
|
96
|
+
return default if id_i == default.id || id.nil? || id == "default"
|
|
93
97
|
|
|
94
98
|
id = id_i
|
|
95
99
|
raise ArgumentError if id.zero?
|
|
96
100
|
|
|
97
101
|
unless cached_shards.key?(id)
|
|
98
102
|
cached_shards[id] = Shard.default.activate do
|
|
99
|
-
find_cached([
|
|
103
|
+
find_cached(["shard", id]) { find_by(id: id) }
|
|
100
104
|
end
|
|
101
105
|
end
|
|
102
106
|
cached_shards[id]
|
|
@@ -140,13 +144,15 @@ module Switchman
|
|
|
140
144
|
parallel = 0 if parallel == false || parallel.nil?
|
|
141
145
|
|
|
142
146
|
scope ||= Shard.all
|
|
143
|
-
|
|
147
|
+
if ::ActiveRecord::Relation === scope && scope.order_values.empty?
|
|
148
|
+
scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
|
|
149
|
+
end
|
|
144
150
|
|
|
145
151
|
if parallel > 1
|
|
146
152
|
if ::ActiveRecord::Relation === scope
|
|
147
153
|
# still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
|
|
148
|
-
database_servers = scope.reorder(
|
|
149
|
-
|
|
154
|
+
database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
|
|
155
|
+
.filter_map(&:database_server).uniq
|
|
150
156
|
# nothing to do
|
|
151
157
|
return if database_servers.count.zero?
|
|
152
158
|
|
|
@@ -162,15 +168,15 @@ module Switchman
|
|
|
162
168
|
# silly like dealloc'ing prepared statements)
|
|
163
169
|
::ActiveRecord::Base.clear_all_connections!
|
|
164
170
|
|
|
165
|
-
parent_process_name =
|
|
166
|
-
ret = ::Parallel.map(scopes, in_processes: scopes.length > 1 ? parallel : 0) do |server, subscope|
|
|
171
|
+
parent_process_name = sanitized_process_title
|
|
172
|
+
ret = ::Parallel.map(scopes, in_processes: (scopes.length > 1) ? parallel : 0) do |server, subscope|
|
|
167
173
|
name = server.id
|
|
168
174
|
last_description = name
|
|
169
175
|
|
|
170
176
|
begin
|
|
171
177
|
max_length = 128 - name.length - 3
|
|
172
178
|
short_parent_name = parent_process_name[0..max_length] if max_length >= 0
|
|
173
|
-
new_title = [short_parent_name, name].join(
|
|
179
|
+
new_title = [short_parent_name, name].join(" ")
|
|
174
180
|
Process.setproctitle(new_title)
|
|
175
181
|
Switchman.config[:on_fork_proc]&.call
|
|
176
182
|
with_each_shard(subscope, classes, exception: exception, output: :decorated) do
|
|
@@ -187,8 +193,9 @@ module Switchman
|
|
|
187
193
|
unless errors.empty?
|
|
188
194
|
raise errors.first.exception if errors.length == 1
|
|
189
195
|
|
|
196
|
+
errors_desc = errors.map(&:name).sort.join(", ")
|
|
190
197
|
raise Errors::ParallelShardExecError,
|
|
191
|
-
"The following database server(s) did not finish processing cleanly: #{
|
|
198
|
+
"The following database server(s) did not finish processing cleanly: #{errors_desc}",
|
|
192
199
|
cause: errors.first.exception
|
|
193
200
|
end
|
|
194
201
|
|
|
@@ -437,6 +444,39 @@ module Switchman
|
|
|
437
444
|
shard = nil unless shard.database_server
|
|
438
445
|
shard
|
|
439
446
|
end
|
|
447
|
+
|
|
448
|
+
# Determines the name of the current process, including arguments, but stripping
|
|
449
|
+
# any shebang from the invoked script, and any additional path info from the
|
|
450
|
+
# executable.
|
|
451
|
+
#
|
|
452
|
+
# @return [String]
|
|
453
|
+
def sanitized_process_title
|
|
454
|
+
# get the effective process name from `ps`; this will include any changes
|
|
455
|
+
# from Process.setproctitle _or_ assigning to $0.
|
|
456
|
+
parent_process_name = `ps -ocommand= -p#{Process.pid}`.strip
|
|
457
|
+
# Effective process titles may be shorter than the actual
|
|
458
|
+
# command; truncate our ARGV[0] so that they are comparable
|
|
459
|
+
# for the next step
|
|
460
|
+
argv0 = if parent_process_name.length < Process.argv0.length
|
|
461
|
+
Process.argv0[0..parent_process_name.length]
|
|
462
|
+
else
|
|
463
|
+
Process.argv0
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# when running via a shebang, the `ps` output will include the shebang
|
|
467
|
+
# (i.e. it will be "ruby bin/rails c"); attempt to strip it off.
|
|
468
|
+
# Note that argv0 in this case will _only_ be `bin/rails` (no shebang,
|
|
469
|
+
# no arguments). We want to preserve the arguments we got from `ps`
|
|
470
|
+
if (index = parent_process_name.index(argv0))
|
|
471
|
+
parent_process_name.slice!(0...index)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# remove directories from the main executable to make more room
|
|
475
|
+
# for additional info
|
|
476
|
+
argv = parent_process_name.shellsplit
|
|
477
|
+
argv[0] = File.basename(argv[0])
|
|
478
|
+
argv.shelljoin
|
|
479
|
+
end
|
|
440
480
|
end
|
|
441
481
|
|
|
442
482
|
def name
|
|
@@ -467,7 +507,7 @@ module Switchman
|
|
|
467
507
|
end
|
|
468
508
|
|
|
469
509
|
def description
|
|
470
|
-
[database_server.id, name].compact.join(
|
|
510
|
+
[database_server.id, name].compact.join(":")
|
|
471
511
|
end
|
|
472
512
|
|
|
473
513
|
# Shards are always on the default shard
|
|
@@ -501,48 +541,39 @@ module Switchman
|
|
|
501
541
|
end
|
|
502
542
|
|
|
503
543
|
def drop_database
|
|
504
|
-
raise
|
|
544
|
+
raise "Cannot drop the database of the default shard" if default?
|
|
505
545
|
return unless read_attribute(:name)
|
|
506
546
|
|
|
507
547
|
begin
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
drop_statement = sharding_config[adapter]&.[](:drop_statement)
|
|
511
|
-
drop_statement ||= sharding_config[:drop_statement]
|
|
512
|
-
if drop_statement
|
|
513
|
-
drop_statement = Array(drop_statement).dup.
|
|
514
|
-
map { |statement| statement.gsub('%{name}', name) }
|
|
548
|
+
activate do
|
|
549
|
+
self.class.drop_database(name)
|
|
515
550
|
end
|
|
551
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
|
552
|
+
logger.error "Drop failed: #{e}"
|
|
553
|
+
end
|
|
554
|
+
end
|
|
516
555
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
drop_statement ||= "DROP SCHEMA #{name} CASCADE"
|
|
535
|
-
Array(drop_statement).each do |stmt|
|
|
536
|
-
::ActiveRecord::Base.connection.execute(stmt)
|
|
537
|
-
end
|
|
538
|
-
ensure
|
|
539
|
-
conn.raw_connection.set_notice_processor(&old_proc) if old_proc
|
|
540
|
-
end
|
|
541
|
-
end
|
|
556
|
+
#
|
|
557
|
+
# Drops a specific database/schema from the currently active connection
|
|
558
|
+
#
|
|
559
|
+
def self.drop_database(name)
|
|
560
|
+
sharding_config = Switchman.config || {}
|
|
561
|
+
drop_statement = sharding_config["postgresql"]&.[](:drop_statement)
|
|
562
|
+
drop_statement ||= sharding_config[:drop_statement]
|
|
563
|
+
drop_statement = Array(drop_statement).map { |statement| statement.gsub("%{name}", name) } if drop_statement
|
|
564
|
+
|
|
565
|
+
::GuardRail.activate(:deploy) do
|
|
566
|
+
# Shut up, Postgres!
|
|
567
|
+
conn = ::ActiveRecord::Base.connection
|
|
568
|
+
old_proc = conn.raw_connection.set_notice_processor {}
|
|
569
|
+
begin
|
|
570
|
+
drop_statement ||= "DROP SCHEMA #{name} CASCADE"
|
|
571
|
+
Array(drop_statement).each do |stmt|
|
|
572
|
+
::ActiveRecord::Base.connection.execute(stmt)
|
|
542
573
|
end
|
|
574
|
+
ensure
|
|
575
|
+
conn.raw_connection.set_notice_processor(&old_proc) if old_proc
|
|
543
576
|
end
|
|
544
|
-
rescue
|
|
545
|
-
logger.info "Drop failed: #{$!}"
|
|
546
577
|
end
|
|
547
578
|
end
|
|
548
579
|
|
|
@@ -561,7 +592,7 @@ module Switchman
|
|
|
561
592
|
end
|
|
562
593
|
|
|
563
594
|
def destroy
|
|
564
|
-
raise(
|
|
595
|
+
raise("Cannot destroy the default shard") if default?
|
|
565
596
|
|
|
566
597
|
super
|
|
567
598
|
end
|
|
@@ -570,8 +601,8 @@ module Switchman
|
|
|
570
601
|
|
|
571
602
|
def clear_cache
|
|
572
603
|
Shard.default.activate do
|
|
573
|
-
Switchman.cache.delete([
|
|
574
|
-
Switchman.cache.delete(
|
|
604
|
+
Switchman.cache.delete(["shard", id].join("/"))
|
|
605
|
+
Switchman.cache.delete("default_shard") if default?
|
|
575
606
|
end
|
|
576
607
|
self.class.clear_cache
|
|
577
608
|
end
|
|
@@ -16,7 +16,11 @@ module Switchman
|
|
|
16
16
|
payload[:shard] = {
|
|
17
17
|
database_server_id: shard.database_server.id,
|
|
18
18
|
id: shard.id,
|
|
19
|
-
env: ::Rails.version <
|
|
19
|
+
env: if ::Rails.version < "7.0"
|
|
20
|
+
@shard_host.pool.connection_klass&.current_role
|
|
21
|
+
else
|
|
22
|
+
@shard_host.pool.connection_class&.current_role
|
|
23
|
+
end
|
|
20
24
|
}
|
|
21
25
|
end
|
|
22
26
|
super name, payload
|
|
@@ -65,7 +65,7 @@ module Switchman
|
|
|
65
65
|
if server == Shard.default.database_server
|
|
66
66
|
server.shards.where(name: name).first
|
|
67
67
|
else
|
|
68
|
-
shard = Shard.where(
|
|
68
|
+
shard = Shard.where("database_server_id IS NOT NULL AND name=?", name).first
|
|
69
69
|
# if somehow databases got created in a different order, change the shard to match
|
|
70
70
|
shard.database_server = server if shard
|
|
71
71
|
shard
|
data/lib/switchman/version.rb
CHANGED