switchman 3.2.1 → 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.
- 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/abstract_adapter.rb +4 -2
- data/lib/switchman/active_record/associations.rb +89 -49
- data/lib/switchman/active_record/attribute_methods.rb +72 -34
- data/lib/switchman/active_record/base.rb +145 -27
- data/lib/switchman/active_record/calculations.rb +96 -49
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +24 -3
- data/lib/switchman/active_record/database_configurations.rb +37 -15
- data/lib/switchman/active_record/finder_methods.rb +44 -14
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +45 -3
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +30 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +49 -20
- data/lib/switchman/active_record/query_methods.rb +192 -135
- data/lib/switchman/active_record/relation.rb +28 -13
- 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 +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +26 -16
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +34 -18
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +69 -31
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +29 -22
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +13 -0
- data/lib/switchman/guard_rail/relation.rb +1 -1
- data/lib/switchman/parallel.rb +6 -6
- data/lib/switchman/r_spec_helper.rb +12 -11
- data/lib/switchman/shard.rb +180 -68
- data/lib/switchman/sharded_instrumenter.rb +3 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +4 -0
- data/lib/switchman/test_helper.rb +2 -2
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +27 -15
- data/lib/tasks/switchman.rake +96 -60
- metadata +35 -45
data/lib/switchman/arel.rb
CHANGED
|
@@ -13,38 +13,54 @@ module Switchman
|
|
|
13
13
|
# rubocop:disable Naming/MethodName
|
|
14
14
|
# rubocop:disable Naming/MethodParameterName
|
|
15
15
|
|
|
16
|
+
def visit_Arel_Nodes_Cte(o, collector)
|
|
17
|
+
collector << quote_local_table_name(o.name)
|
|
18
|
+
collector << " AS "
|
|
19
|
+
|
|
20
|
+
case o.materialized
|
|
21
|
+
when true
|
|
22
|
+
collector << "MATERIALIZED "
|
|
23
|
+
when false
|
|
24
|
+
collector << "NOT MATERIALIZED "
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
visit o.relation, collector
|
|
28
|
+
end
|
|
29
|
+
|
|
16
30
|
def visit_Arel_Nodes_TableAlias(o, collector)
|
|
17
31
|
collector = visit o.relation, collector
|
|
18
|
-
collector <<
|
|
32
|
+
collector << " "
|
|
19
33
|
collector << quote_local_table_name(o.name)
|
|
20
34
|
end
|
|
21
35
|
|
|
22
36
|
def visit_Arel_Attributes_Attribute(o, collector)
|
|
23
37
|
join_name = o.relation.table_alias || o.relation.name
|
|
24
|
-
collector << quote_local_table_name(join_name) <<
|
|
38
|
+
collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
|
|
25
39
|
end
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
if ::Rails.version < "7.1"
|
|
42
|
+
def visit_Arel_Nodes_HomogeneousIn(o, collector)
|
|
43
|
+
collector.preparable = false
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
collector << quote_local_table_name(o.table_name) << "." << quote_column_name(o.column_name)
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
collector << if o.type == :in
|
|
48
|
+
" IN ("
|
|
49
|
+
else
|
|
50
|
+
" NOT IN ("
|
|
51
|
+
end
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
values = o.casted_values
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
if values.empty?
|
|
56
|
+
collector << @connection.quote(nil)
|
|
57
|
+
else
|
|
58
|
+
collector.add_binds(values, o.proc_for_binds, &bind_block)
|
|
59
|
+
end
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
collector << ")"
|
|
62
|
+
collector
|
|
63
|
+
end
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
# rubocop:enable Naming/MethodName
|
data/lib/switchman/call_super.rb
CHANGED
|
@@ -12,8 +12,8 @@ module Switchman
|
|
|
12
12
|
method.super_method
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def call_super(method, above_module,
|
|
16
|
-
super_method_above(method, above_module).call(
|
|
15
|
+
def call_super(method, above_module, ...)
|
|
16
|
+
super_method_above(method, above_module).call(...)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -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
|
|
@@ -10,19 +10,23 @@ module Switchman
|
|
|
10
10
|
attr_accessor :creating_new_shard
|
|
11
11
|
attr_reader :all_roles
|
|
12
12
|
|
|
13
|
+
include Enumerable
|
|
14
|
+
|
|
15
|
+
delegate :each, to: :all
|
|
16
|
+
|
|
13
17
|
def all
|
|
14
18
|
database_servers.values
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def find(id_or_all)
|
|
18
22
|
return all if id_or_all == :all
|
|
19
|
-
return id_or_all.
|
|
23
|
+
return id_or_all.filter_map { |id| database_servers[id || ::Rails.env] }.uniq if id_or_all.is_a?(Array)
|
|
20
24
|
|
|
21
25
|
database_servers[id_or_all || ::Rails.env]
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def create(settings = {})
|
|
25
|
-
raise
|
|
29
|
+
raise "database servers should be set up in database.yml" unless ::Rails.env.test?
|
|
26
30
|
|
|
27
31
|
id = settings[:id]
|
|
28
32
|
unless id
|
|
@@ -50,6 +54,10 @@ module Switchman
|
|
|
50
54
|
all.each { |db| db.guard! if db.config[:prefer_secondary] }
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
def regions
|
|
58
|
+
@regions ||= all.filter_map(&:region).uniq.sort
|
|
59
|
+
end
|
|
60
|
+
|
|
53
61
|
private
|
|
54
62
|
|
|
55
63
|
def reference_role(role)
|
|
@@ -64,8 +72,8 @@ module Switchman
|
|
|
64
72
|
@database_servers = {}.with_indifferent_access
|
|
65
73
|
roles = []
|
|
66
74
|
::ActiveRecord::Base.configurations.configurations.each do |config|
|
|
67
|
-
if config.name.include?(
|
|
68
|
-
name, role = config.name.split(
|
|
75
|
+
if config.name.include?("/")
|
|
76
|
+
name, role = config.name.split("/")
|
|
69
77
|
else
|
|
70
78
|
name, role = config.env_name, config.name
|
|
71
79
|
end
|
|
@@ -81,6 +89,8 @@ module Switchman
|
|
|
81
89
|
# Do this after so that all database servers for all roles are established and we won't prematurely
|
|
82
90
|
# configure a connection for the wrong role
|
|
83
91
|
@all_roles = roles.uniq
|
|
92
|
+
return @database_servers if @database_servers.empty?
|
|
93
|
+
|
|
84
94
|
Shard.send(:configure_connects_to)
|
|
85
95
|
end
|
|
86
96
|
@database_servers
|
|
@@ -110,8 +120,9 @@ module Switchman
|
|
|
110
120
|
self.class.send(:database_servers).delete(id) if id
|
|
111
121
|
Shard.sharded_models.each do |klass|
|
|
112
122
|
self.class.all_roles.each do |role|
|
|
113
|
-
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
|
114
|
-
|
|
123
|
+
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
|
124
|
+
role: role,
|
|
125
|
+
shard: id.to_sym)
|
|
115
126
|
end
|
|
116
127
|
end
|
|
117
128
|
end
|
|
@@ -137,16 +148,41 @@ module Switchman
|
|
|
137
148
|
end
|
|
138
149
|
end
|
|
139
150
|
|
|
151
|
+
def region
|
|
152
|
+
config[:region]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# @param region [String, Array<String>] the region(s) to check against
|
|
156
|
+
# @return true if the database server doesn't have a region, or it
|
|
157
|
+
# matches the specified region
|
|
158
|
+
def in_region?(region)
|
|
159
|
+
!self.region || (region.is_a?(Array) ? region.include?(self.region) : self.region == region)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @return true if the database server doesn't have a region, Switchman is
|
|
163
|
+
# not configured with a region, or the database server's region matches
|
|
164
|
+
# Switchman's current region
|
|
165
|
+
def in_current_region?
|
|
166
|
+
unless instance_variable_defined?(:@in_current_region)
|
|
167
|
+
@in_current_region = !region ||
|
|
168
|
+
!Switchman.region ||
|
|
169
|
+
region == Switchman.region
|
|
170
|
+
end
|
|
171
|
+
@in_current_region
|
|
172
|
+
end
|
|
173
|
+
|
|
140
174
|
# locks this db to a specific environment, except for
|
|
141
175
|
# when doing writes (then it falls back to the current
|
|
142
176
|
# value of GuardRail.environment)
|
|
143
177
|
def guard!(environment = :secondary)
|
|
144
178
|
DatabaseServer.send(:reference_role, environment)
|
|
145
|
-
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
|
|
179
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
|
|
180
|
+
klasses: [::ActiveRecord::Base] }
|
|
146
181
|
end
|
|
147
182
|
|
|
148
183
|
def unguard!
|
|
149
|
-
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
|
|
184
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
|
|
185
|
+
klasses: [::ActiveRecord::Base] }
|
|
150
186
|
end
|
|
151
187
|
|
|
152
188
|
def unguard
|
|
@@ -162,7 +198,7 @@ module Switchman
|
|
|
162
198
|
|
|
163
199
|
def shards
|
|
164
200
|
if id == ::Rails.env
|
|
165
|
-
Shard.where(
|
|
201
|
+
Shard.where("database_server_id IS NULL OR database_server_id=?", id)
|
|
166
202
|
else
|
|
167
203
|
Shard.where(database_server_id: id)
|
|
168
204
|
end
|
|
@@ -187,7 +223,7 @@ module Switchman
|
|
|
187
223
|
end
|
|
188
224
|
|
|
189
225
|
id ||= begin
|
|
190
|
-
id_seq = Shard.connection.quote(Shard.connection.quote_table_name(
|
|
226
|
+
id_seq = Shard.connection.quote(Shard.connection.quote_table_name("switchman_shards_id_seq"))
|
|
191
227
|
next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
|
|
192
228
|
next_id.to_i
|
|
193
229
|
end
|
|
@@ -204,29 +240,32 @@ module Switchman
|
|
|
204
240
|
name: name,
|
|
205
241
|
database_server_id: self.id)
|
|
206
242
|
if create_statement
|
|
207
|
-
if ::ActiveRecord::Base.connection.select_value(
|
|
243
|
+
if ::ActiveRecord::Base.connection.select_value(
|
|
244
|
+
"SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"
|
|
245
|
+
)
|
|
208
246
|
schema_already_existed = true
|
|
209
|
-
raise
|
|
247
|
+
raise "This schema already exists; cannot overwrite"
|
|
210
248
|
end
|
|
211
249
|
Array(create_statement.call).each do |stmt|
|
|
212
250
|
::ActiveRecord::Base.connection.execute(stmt)
|
|
213
251
|
end
|
|
214
252
|
end
|
|
215
|
-
if config[:adapter] ==
|
|
216
|
-
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor
|
|
217
|
-
end
|
|
253
|
+
if config[:adapter] == "postgresql"
|
|
254
|
+
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {}
|
|
218
255
|
end
|
|
219
256
|
old_verbose = ::ActiveRecord::Migration.verbose
|
|
220
257
|
::ActiveRecord::Migration.verbose = false
|
|
221
258
|
|
|
222
259
|
unless schema == false
|
|
223
260
|
shard.activate do
|
|
224
|
-
reset_column_information
|
|
225
|
-
|
|
226
261
|
::ActiveRecord::Base.connection.transaction(requires_new: true) do
|
|
227
|
-
::
|
|
262
|
+
if ::Rails.version < "7.1"
|
|
263
|
+
::ActiveRecord::Base.connection.migration_context.migrate
|
|
264
|
+
else
|
|
265
|
+
::ActiveRecord::MigrationContext.new(::ActiveRecord::Migrator.migrations_paths).migrate
|
|
266
|
+
end
|
|
228
267
|
end
|
|
229
|
-
|
|
268
|
+
|
|
230
269
|
::ActiveRecord::Base.descendants.reject do |m|
|
|
231
270
|
m <= UnshardedRecord || !m.table_exists?
|
|
232
271
|
end.each(&:define_attribute_methods)
|
|
@@ -240,7 +279,6 @@ module Switchman
|
|
|
240
279
|
rescue
|
|
241
280
|
shard&.destroy
|
|
242
281
|
shard&.drop_database rescue nil unless schema_already_existed
|
|
243
|
-
reset_column_information unless schema == false rescue nil
|
|
244
282
|
raise
|
|
245
283
|
ensure
|
|
246
284
|
self.class.creating_new_shard = false
|
|
@@ -265,18 +303,18 @@ module Switchman
|
|
|
265
303
|
end
|
|
266
304
|
|
|
267
305
|
def primary_shard
|
|
268
|
-
unless
|
|
269
|
-
# if sharding isn't fully set up yet, we may not be able to query the shards table
|
|
270
|
-
@primary_shard = Shard.default if Shard.default.database_server == self
|
|
271
|
-
@primary_shard ||= shards.where(name: nil).first
|
|
272
|
-
end
|
|
273
|
-
@primary_shard
|
|
274
|
-
end
|
|
306
|
+
return nil unless primary_shard_id
|
|
275
307
|
|
|
276
|
-
|
|
308
|
+
Shard.lookup(primary_shard_id)
|
|
309
|
+
end
|
|
277
310
|
|
|
278
|
-
def
|
|
279
|
-
|
|
311
|
+
def primary_shard_id
|
|
312
|
+
unless instance_variable_defined?(:@primary_shard_id)
|
|
313
|
+
# if sharding isn't fully set up yet, we may not be able to query the shards table
|
|
314
|
+
@primary_shard_id = Shard.default.id if Shard.default.database_server == self
|
|
315
|
+
@primary_shard_id ||= shards.where(name: nil).first&.id
|
|
316
|
+
end
|
|
317
|
+
@primary_shard_id
|
|
280
318
|
end
|
|
281
319
|
end
|
|
282
320
|
end
|
|
@@ -3,9 +3,10 @@
|
|
|
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
10
|
def activate(*_classes)
|
|
10
11
|
yield
|
|
11
12
|
end
|
|
@@ -57,8 +58,18 @@ module Switchman
|
|
|
57
58
|
self
|
|
58
59
|
end
|
|
59
60
|
|
|
61
|
+
def region; end
|
|
62
|
+
|
|
63
|
+
def in_region?(_region)
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def in_current_region?
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
60
71
|
def _dump(_depth)
|
|
61
|
-
|
|
72
|
+
""
|
|
62
73
|
end
|
|
63
74
|
|
|
64
75
|
def self._load(_str)
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -5,24 +5,17 @@ module Switchman
|
|
|
5
5
|
isolate_namespace Switchman
|
|
6
6
|
|
|
7
7
|
# enable Rails 6.1 style connection handling
|
|
8
|
-
config.active_record.legacy_connection_handling = false
|
|
8
|
+
config.active_record.legacy_connection_handling = false if ::Rails.version < "7.1"
|
|
9
9
|
config.active_record.writing_role = :primary
|
|
10
10
|
|
|
11
11
|
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
initialize_dependency_mechanism = ::Rails::Application::Bootstrap.initializers.find { |i| i.name == :initialize_dependency_mechanism }
|
|
17
|
-
initialize_dependency_mechanism.instance_variable_get(:@options)[:after] = :set_autoload_paths
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
initializer 'switchman.active_record_patch',
|
|
21
|
-
before: 'active_record.initialize_database',
|
|
22
|
-
after: (::Rails.version < '7.0' ? :initialize_dependency_mechanism : :setup_once_autoloader) do
|
|
13
|
+
initializer "switchman.active_record_patch",
|
|
14
|
+
before: "active_record.initialize_database",
|
|
15
|
+
after: :setup_once_autoloader do
|
|
23
16
|
::ActiveSupport.on_load(:active_record) do
|
|
24
17
|
# Switchman requires postgres, so just always load the pg adapter
|
|
25
|
-
require
|
|
18
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
|
26
19
|
|
|
27
20
|
self.default_shard = ::Rails.env.to_sym
|
|
28
21
|
self.default_role = :primary
|
|
@@ -50,14 +43,21 @@ module Switchman
|
|
|
50
43
|
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::Associations::CollectionProxy)
|
|
51
44
|
|
|
52
45
|
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Associations::Preloader::Association)
|
|
53
|
-
::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
|
|
46
|
+
::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
|
|
47
|
+
ActiveRecord::Associations::Preloader::Association::LoaderRecords
|
|
48
|
+
)
|
|
54
49
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
|
|
50
|
+
unless ::Rails.version < "7.1"
|
|
51
|
+
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
52
|
+
end
|
|
55
53
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
56
54
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
57
55
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
58
56
|
|
|
59
57
|
::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
|
|
60
|
-
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
58
|
+
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
59
|
+
ActiveRecord::DatabaseConfigurations::DatabaseConfig
|
|
60
|
+
)
|
|
61
61
|
|
|
62
62
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
63
63
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
@@ -65,6 +65,11 @@ module Switchman
|
|
|
65
65
|
::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext)
|
|
66
66
|
::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
|
|
67
67
|
|
|
68
|
+
if ::Rails.version > "7.1.3"
|
|
69
|
+
::ActiveRecord::PendingMigrationConnection.singleton_class
|
|
70
|
+
.include(ActiveRecord::PendingMigrationConnection::ClassMethods)
|
|
71
|
+
end
|
|
72
|
+
|
|
68
73
|
::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
|
|
69
74
|
::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
70
75
|
::ActiveRecord::Reflection::ThroughReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
@@ -77,8 +82,9 @@ module Switchman
|
|
|
77
82
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
|
78
83
|
::ActiveRecord::Relation.include(CallSuper)
|
|
79
84
|
|
|
80
|
-
::ActiveRecord::PredicateBuilder::
|
|
81
|
-
|
|
85
|
+
::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(
|
|
86
|
+
ActiveRecord::PredicateBuilder::PolymorphicArrayValue
|
|
87
|
+
)
|
|
82
88
|
|
|
83
89
|
::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
|
|
84
90
|
|
|
@@ -96,17 +102,18 @@ module Switchman
|
|
|
96
102
|
|
|
97
103
|
::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
|
|
98
104
|
end
|
|
99
|
-
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
|
105
|
+
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
|
106
|
+
# initializers can go try to load Switchman::Shard or we get a loop
|
|
100
107
|
::ActiveRecord::Base
|
|
101
108
|
end
|
|
102
109
|
|
|
103
|
-
initializer
|
|
110
|
+
initializer "switchman.error_patch", after: "active_record.initialize_database" do
|
|
104
111
|
::ActiveSupport.on_load(:active_record) do
|
|
105
112
|
::StandardError.include(StandardError)
|
|
106
113
|
end
|
|
107
114
|
end
|
|
108
115
|
|
|
109
|
-
initializer
|
|
116
|
+
initializer "switchman.initialize_cache", before: :initialize_cache, after: "active_record.initialize_database" do
|
|
110
117
|
::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
|
|
111
118
|
|
|
112
119
|
# if we haven't already setup our cache map out-of-band, set it up from
|
|
@@ -130,11 +137,11 @@ module Switchman
|
|
|
130
137
|
Switchman.config[:cache_map][::Rails.env] = value
|
|
131
138
|
end
|
|
132
139
|
|
|
133
|
-
middlewares = Switchman.config[:cache_map].values.
|
|
140
|
+
middlewares = Switchman.config[:cache_map].values.filter_map do |store|
|
|
134
141
|
store.middleware if store.respond_to?(:middleware)
|
|
135
|
-
end.
|
|
142
|
+
end.uniq
|
|
136
143
|
middlewares.each do |middleware|
|
|
137
|
-
config.middleware.insert_before(
|
|
144
|
+
config.middleware.insert_before("Rack::Runtime", middleware)
|
|
138
145
|
end
|
|
139
146
|
|
|
140
147
|
# 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
|
@@ -2,8 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module Switchman
|
|
4
4
|
module Errors
|
|
5
|
+
class ManuallyCreatedShadowRecordError < RuntimeError
|
|
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)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
5
14
|
class NonExistentShardError < RuntimeError; end
|
|
6
15
|
|
|
7
16
|
class ParallelShardExecError < RuntimeError; end
|
|
17
|
+
|
|
18
|
+
class ShadowRecordError < RuntimeError; end
|
|
19
|
+
|
|
20
|
+
class UnshardedTableError < RuntimeError; end
|
|
8
21
|
end
|
|
9
22
|
end
|
|
@@ -14,7 +14,7 @@ module Switchman
|
|
|
14
14
|
|
|
15
15
|
%w[update_all delete_all].each do |method|
|
|
16
16
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
17
|
-
def #{method}(*args)
|
|
17
|
+
def #{method}(*args, **kwargs)
|
|
18
18
|
db = Shard.current(connection_class_for_self).database_server
|
|
19
19
|
db.unguard { super }
|
|
20
20
|
end
|
data/lib/switchman/parallel.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "parallel"
|
|
4
4
|
|
|
5
5
|
module Switchman
|
|
6
6
|
module Parallel
|
|
@@ -50,19 +50,19 @@ module Switchman
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
class
|
|
53
|
+
class TransformingIO
|
|
54
54
|
delegate_missing_to :@original_io
|
|
55
55
|
|
|
56
|
-
def initialize(
|
|
57
|
-
@
|
|
56
|
+
def initialize(transformer, original_io)
|
|
57
|
+
@transformer = transformer
|
|
58
58
|
@original_io = original_io
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def puts(*args)
|
|
62
|
-
args.flatten.each { |arg| @original_io.puts
|
|
62
|
+
args.flatten.each { |arg| @original_io.puts @transformer.call(arg) }
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Parallel::UndumpableException.prepend(Switchman::Parallel::UndumpableException)
|
|
@@ -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
|
|
@@ -121,18 +121,19 @@ module Switchman
|
|
|
121
121
|
next if @@sharding_failed
|
|
122
122
|
|
|
123
123
|
# clean up after specs
|
|
124
|
-
DatabaseServer.
|
|
124
|
+
DatabaseServer.each do |ds|
|
|
125
125
|
if ds.fake? && ds != @shard2.database_server
|
|
126
126
|
ds.shards.delete_all unless use_transactional_tests
|
|
127
127
|
ds.destroy
|
|
128
128
|
end
|
|
129
|
+
ds.remove_instance_variable(:@primary_shard_id) if ds.instance_variable_defined?(:@primary_shard_id)
|
|
129
130
|
end
|
|
130
131
|
end
|
|
131
132
|
|
|
132
133
|
klass.after(:all) do
|
|
133
134
|
# Don't truncate because that can create some fun cross-connection lock contention
|
|
134
135
|
Shard.delete_all
|
|
135
|
-
Switchman.cache.delete(
|
|
136
|
+
Switchman.cache.delete("default_shard")
|
|
136
137
|
Shard.default(reload: true)
|
|
137
138
|
end
|
|
138
139
|
end
|