switchman 3.0.1 → 4.2.5
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 +16 -15
- data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +11 -18
- data/lib/switchman/active_record/associations.rb +315 -0
- data/lib/switchman/active_record/attribute_methods.rb +191 -79
- data/lib/switchman/active_record/base.rb +204 -50
- data/lib/switchman/active_record/calculations.rb +93 -50
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +47 -34
- data/lib/switchman/active_record/database_configurations.rb +32 -6
- data/lib/switchman/active_record/finder_methods.rb +22 -16
- data/lib/switchman/active_record/log_subscriber.rb +3 -6
- data/lib/switchman/active_record/migration.rb +42 -14
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +37 -2
- data/lib/switchman/active_record/postgresql_adapter.rb +39 -20
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +26 -17
- data/lib/switchman/active_record/query_methods.rb +252 -135
- data/lib/switchman/active_record/reflection.rb +10 -3
- data/lib/switchman/active_record/relation.rb +154 -32
- data/lib/switchman/active_record/spawn_methods.rb +3 -7
- data/lib/switchman/active_record/statement_cache.rb +13 -9
- data/lib/switchman/active_record/table_definition.rb +1 -1
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +89 -0
- data/lib/switchman/active_support/cache.rb +25 -4
- data/lib/switchman/arel.rb +20 -7
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +123 -83
- data/lib/switchman/default_shard.rb +14 -5
- data/lib/switchman/engine.rb +85 -131
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +17 -2
- data/lib/switchman/guard_rail/relation.rb +7 -10
- data/lib/switchman/guard_rail.rb +5 -0
- data/lib/switchman/parallel.rb +68 -0
- data/lib/switchman/r_spec_helper.rb +17 -28
- data/lib/switchman/rails.rb +1 -4
- data/{app/models → lib}/switchman/shard.rb +229 -246
- data/lib/switchman/sharded_instrumenter.rb +9 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +15 -12
- data/lib/switchman/test_helper.rb +3 -3
- data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +46 -12
- data/lib/tasks/switchman.rake +101 -54
- metadata +34 -176
- data/lib/switchman/active_record/association.rb +0 -206
- data/lib/switchman/open4.rb +0 -80
|
@@ -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
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
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."
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
def initialize(msg = DEFAULT_MSG)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class NonExistentShardError < RuntimeError; end
|
|
15
|
+
|
|
16
|
+
class ParallelShardExecError < RuntimeError; end
|
|
17
|
+
|
|
18
|
+
class ShadowRecordError < RuntimeError; end
|
|
19
|
+
|
|
20
|
+
class UnshardedTableError < RuntimeError; end
|
|
21
|
+
end
|
|
7
22
|
end
|
|
@@ -5,21 +5,18 @@ module Switchman
|
|
|
5
5
|
module Relation
|
|
6
6
|
def exec_queries(*args)
|
|
7
7
|
if lock_value
|
|
8
|
-
db = Shard.current(
|
|
9
|
-
|
|
8
|
+
db = Shard.current(connection_class_for_self).database_server
|
|
9
|
+
db.unguard { super }
|
|
10
|
+
else
|
|
11
|
+
super
|
|
10
12
|
end
|
|
11
|
-
super
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
%w[update_all delete_all].each do |method|
|
|
15
16
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
16
|
-
def #{method}(*args)
|
|
17
|
-
db = Shard.current(
|
|
18
|
-
|
|
19
|
-
db.unguard { super }
|
|
20
|
-
else
|
|
21
|
-
super
|
|
22
|
-
end
|
|
17
|
+
def #{method}(*args, **kwargs)
|
|
18
|
+
db = Shard.current(connection_class_for_self).database_server
|
|
19
|
+
db.unguard { super }
|
|
23
20
|
end
|
|
24
21
|
RUBY
|
|
25
22
|
end
|
data/lib/switchman/guard_rail.rb
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
module Switchman
|
|
4
4
|
module GuardRail
|
|
5
5
|
module ClassMethods
|
|
6
|
+
def environment
|
|
7
|
+
# no overrides so we get the global role, not the role for the default shard
|
|
8
|
+
::ActiveRecord::Base.current_role(without_overrides: true)
|
|
9
|
+
end
|
|
10
|
+
|
|
6
11
|
def activate(role)
|
|
7
12
|
DatabaseServer.send(:reference_role, role)
|
|
8
13
|
super
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parallel"
|
|
4
|
+
|
|
5
|
+
module Switchman
|
|
6
|
+
module Parallel
|
|
7
|
+
module UndumpableException
|
|
8
|
+
def initialize(original)
|
|
9
|
+
super
|
|
10
|
+
@active_shards = original.instance_variable_get(:@active_shards)
|
|
11
|
+
current_shard
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class QuietExceptionWrapper
|
|
16
|
+
attr_accessor :name
|
|
17
|
+
|
|
18
|
+
def initialize(name, wrapper)
|
|
19
|
+
@name = name
|
|
20
|
+
@wrapper = wrapper
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def exception
|
|
24
|
+
@wrapper.exception
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class UndumpableResult
|
|
29
|
+
attr_reader :name
|
|
30
|
+
|
|
31
|
+
def initialize(result)
|
|
32
|
+
@name = result.inspect
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
"#<UndumpableResult:#{name}>"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class ResultWrapper
|
|
41
|
+
attr_reader :result
|
|
42
|
+
|
|
43
|
+
def initialize(result)
|
|
44
|
+
@result =
|
|
45
|
+
begin
|
|
46
|
+
Marshal.dump(result) && result
|
|
47
|
+
rescue
|
|
48
|
+
UndumpableResult.new(result)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class TransformingIO
|
|
54
|
+
delegate_missing_to :@original_io
|
|
55
|
+
|
|
56
|
+
def initialize(transformer, original_io)
|
|
57
|
+
@transformer = transformer
|
|
58
|
+
@original_io = original_io
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def puts(*args)
|
|
62
|
+
args.flatten.each { |arg| @original_io.puts @transformer.call(arg) }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
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,14 +66,17 @@ 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
|
+
main_pid = Process.pid
|
|
73
74
|
at_exit do
|
|
75
|
+
next unless main_pid == Process.pid
|
|
76
|
+
|
|
74
77
|
# preserve rspec's exit status
|
|
75
78
|
status = $!.is_a?(::SystemExit) ? $!.status : nil
|
|
76
|
-
puts
|
|
79
|
+
puts "Tearing down sharding for all specs"
|
|
77
80
|
@@shard1.database_server.destroy unless @@shard1.database_server == Shard.default.database_server
|
|
78
81
|
unless @@keep_the_shards
|
|
79
82
|
@@shard1.drop_database
|
|
@@ -92,7 +95,7 @@ module Switchman
|
|
|
92
95
|
dup = @@default_shard.dup
|
|
93
96
|
dup.id = @@default_shard.id
|
|
94
97
|
dup.save!
|
|
95
|
-
Switchman.cache.delete(
|
|
98
|
+
Switchman.cache.delete("default_shard")
|
|
96
99
|
Shard.default(reload: true)
|
|
97
100
|
dup = @@shard1.dup
|
|
98
101
|
dup.id = @@shard1.id
|
|
@@ -104,47 +107,33 @@ module Switchman
|
|
|
104
107
|
end
|
|
105
108
|
|
|
106
109
|
klass.before do
|
|
107
|
-
raise
|
|
110
|
+
raise "Sharding did not set up correctly" if @@sharding_failed
|
|
108
111
|
|
|
109
112
|
Shard.clear_cache
|
|
110
113
|
if use_transactional_tests
|
|
111
114
|
Shard.default(reload: true)
|
|
112
115
|
@shard1 = Shard.find(@shard1.id)
|
|
113
116
|
@shard2 = Shard.find(@shard2.id)
|
|
114
|
-
shards = [@shard2]
|
|
115
|
-
shards << @shard1 unless @shard1.database_server == Shard.default.database_server
|
|
116
|
-
shards.each do |shard|
|
|
117
|
-
shard.activate do
|
|
118
|
-
::ActiveRecord::Base.connection.begin_transaction joinable: false
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
117
|
end
|
|
122
118
|
end
|
|
123
119
|
|
|
124
120
|
klass.after do
|
|
125
121
|
next if @@sharding_failed
|
|
126
122
|
|
|
127
|
-
if use_transactional_tests
|
|
128
|
-
shards = [@shard2]
|
|
129
|
-
shards << @shard1 unless @shard1.database_server == Shard.default.database_server
|
|
130
|
-
shards.each do |shard|
|
|
131
|
-
shard.activate do
|
|
132
|
-
::ActiveRecord::Base.connection.rollback_transaction if ::ActiveRecord::Base.connection.transaction_open?
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
123
|
# clean up after specs
|
|
137
|
-
DatabaseServer.
|
|
124
|
+
DatabaseServer.each do |ds|
|
|
138
125
|
if ds.fake? && ds != @shard2.database_server
|
|
139
126
|
ds.shards.delete_all unless use_transactional_tests
|
|
140
127
|
ds.destroy
|
|
141
128
|
end
|
|
129
|
+
ds.remove_instance_variable(:@primary_shard_id) if ds.instance_variable_defined?(:@primary_shard_id)
|
|
142
130
|
end
|
|
143
131
|
end
|
|
144
132
|
|
|
145
133
|
klass.after(:all) do
|
|
146
|
-
|
|
147
|
-
|
|
134
|
+
# Don't truncate because that can create some fun cross-connection lock contention
|
|
135
|
+
Shard.delete_all
|
|
136
|
+
Switchman.cache.delete("default_shard")
|
|
148
137
|
Shard.default(reload: true)
|
|
149
138
|
end
|
|
150
139
|
end
|
data/lib/switchman/rails.rb
CHANGED
|
@@ -4,10 +4,7 @@ module Switchman
|
|
|
4
4
|
module Rails
|
|
5
5
|
module ClassMethods
|
|
6
6
|
def self.prepended(klass)
|
|
7
|
-
#
|
|
8
|
-
# Rails.cache(_without_sharding) to the value from the config file. but now
|
|
9
|
-
# that that's done (the bootstrap happened before this module is included
|
|
10
|
-
# into Rails), we want to make sure no one tries to assign to Rails.cache,
|
|
7
|
+
# we want to make sure no one tries to assign to Rails.cache,
|
|
11
8
|
# because it would be wrong w.r.t. sharding.
|
|
12
9
|
klass.send(:remove_method, :cache=)
|
|
13
10
|
end
|