switchman 3.0.5 → 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 +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 +6 -15
- data/lib/switchman/active_record/associations.rb +331 -0
- data/lib/switchman/active_record/attribute_methods.rb +182 -77
- data/lib/switchman/active_record/base.rb +249 -46
- data/lib/switchman/active_record/calculations.rb +98 -44
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +27 -28
- data/lib/switchman/active_record/database_configurations.rb +44 -6
- data/lib/switchman/active_record/finder_methods.rb +46 -16
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +52 -5
- 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 +12 -11
- 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 +202 -136
- data/lib/switchman/active_record/reflection.rb +1 -1
- data/lib/switchman/active_record/relation.rb +40 -28
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +11 -7
- 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 +53 -0
- data/lib/switchman/active_support/cache.rb +25 -4
- data/lib/switchman/arel.rb +45 -7
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +123 -79
- data/lib/switchman/default_shard.rb +14 -5
- data/lib/switchman/engine.rb +79 -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 +226 -241
- data/lib/switchman/sharded_instrumenter.rb +3 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +15 -12
- data/lib/switchman/test_helper.rb +2 -2
- data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +44 -12
- data/lib/tasks/switchman.rake +101 -54
- metadata +50 -58
- data/lib/switchman/active_record/association.rb +0 -206
- data/lib/switchman/open4.rb +0 -80
data/lib/switchman/engine.rb
CHANGED
|
@@ -5,104 +5,23 @@ 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
|
-
next if value.is_a?(String)
|
|
17
|
-
|
|
18
|
-
result[key] = ::ActiveSupport::Cache.lookup_store(value)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
cache_store_config.each do |key, value| # rubocop:disable Style/CombinableLoops
|
|
22
|
-
next unless value.is_a?(String)
|
|
23
|
-
|
|
24
|
-
result[key] = result[value]
|
|
25
|
-
end
|
|
26
|
-
result
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
initializer 'switchman.initialize_cache', before: 'initialize_cache' do
|
|
30
|
-
require 'switchman/active_support/cache'
|
|
31
|
-
::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
|
|
32
|
-
|
|
33
|
-
# if we haven't already setup our cache map out-of-band, set it up from
|
|
34
|
-
# config.cache_store now. behaves similarly to Rails' default
|
|
35
|
-
# initialize_cache initializer, but for each value in the map, rather
|
|
36
|
-
# than just Rails.cache. if config.cache_store is a flat value, uses it
|
|
37
|
-
# to fill just the Rails.env entry in the cache map.
|
|
38
|
-
unless Switchman.config[:cache_map].present?
|
|
39
|
-
cache_store_config = ::Rails.configuration.cache_store
|
|
40
|
-
cache_store_config = { ::Rails.env => cache_store_config } unless cache_store_config.is_a?(Hash)
|
|
41
|
-
|
|
42
|
-
Switchman.config[:cache_map] = Engine.lookup_stores(cache_store_config)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# if the configured cache map (either from before, or as populated from
|
|
46
|
-
# config.cache_store) didn't have an entry for Rails.env, add one using
|
|
47
|
-
# lookup_store(nil); matches the behavior of Rails' default
|
|
48
|
-
# initialize_cache initializer when config.cache_store is nil.
|
|
49
|
-
unless Switchman.config[:cache_map].key?(::Rails.env)
|
|
50
|
-
value = ::ActiveSupport::Cache.lookup_store(nil)
|
|
51
|
-
Switchman.config[:cache_map][::Rails.env] = value
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
middlewares = Switchman.config[:cache_map].values.map do |store|
|
|
55
|
-
store.middleware if store.respond_to?(:middleware)
|
|
56
|
-
end.compact.uniq
|
|
57
|
-
middlewares.each do |middleware|
|
|
58
|
-
config.middleware.insert_before('Rack::Runtime', middleware)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# prevent :initialize_cache from trying to (or needing to) set
|
|
62
|
-
# Rails.cache. once our switchman.extend_ar initializer (below) runs
|
|
63
|
-
# Rails.cache will be overridden to pull appropriate values from the
|
|
64
|
-
# cache map, but between now and then, Rails.cache should return the
|
|
65
|
-
# Rails.env entry in the cache map.
|
|
66
|
-
::Rails.cache = Switchman.config[:cache_map][::Rails.env]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
initializer 'switchman.extend_ar', before: 'active_record.initialize_database' do
|
|
13
|
+
initializer "switchman.active_record_patch",
|
|
14
|
+
before: "active_record.initialize_database",
|
|
15
|
+
after: :setup_once_autoloader do
|
|
70
16
|
::ActiveSupport.on_load(:active_record) do
|
|
71
|
-
|
|
72
|
-
require
|
|
73
|
-
require 'switchman/active_record/attribute_methods'
|
|
74
|
-
require 'switchman/active_record/base'
|
|
75
|
-
require 'switchman/active_record/calculations'
|
|
76
|
-
require 'switchman/active_record/connection_pool'
|
|
77
|
-
require 'switchman/active_record/database_configurations'
|
|
78
|
-
require 'switchman/active_record/database_configurations/database_config'
|
|
79
|
-
require 'switchman/active_record/finder_methods'
|
|
80
|
-
require 'switchman/active_record/log_subscriber'
|
|
81
|
-
require 'switchman/active_record/migration'
|
|
82
|
-
require 'switchman/active_record/model_schema'
|
|
83
|
-
require 'switchman/active_record/persistence'
|
|
84
|
-
require 'switchman/active_record/predicate_builder'
|
|
85
|
-
require 'switchman/active_record/query_cache'
|
|
86
|
-
require 'switchman/active_record/query_methods'
|
|
87
|
-
require 'switchman/active_record/reflection'
|
|
88
|
-
require 'switchman/active_record/relation'
|
|
89
|
-
require 'switchman/active_record/spawn_methods'
|
|
90
|
-
require 'switchman/active_record/statement_cache'
|
|
91
|
-
require 'switchman/active_record/tasks/database_tasks'
|
|
92
|
-
require 'switchman/active_record/type_caster'
|
|
93
|
-
require 'switchman/arel'
|
|
94
|
-
require 'switchman/call_super'
|
|
95
|
-
require 'switchman/rails'
|
|
96
|
-
require 'switchman/guard_rail/relation'
|
|
97
|
-
require 'switchman/standard_error'
|
|
98
|
-
|
|
99
|
-
::StandardError.include(StandardError)
|
|
17
|
+
# Switchman requires postgres, so just always load the pg adapter
|
|
18
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
|
100
19
|
|
|
101
20
|
self.default_shard = ::Rails.env.to_sym
|
|
102
21
|
self.default_role = :primary
|
|
103
22
|
|
|
104
|
-
|
|
105
|
-
|
|
23
|
+
prepend ActiveRecord::Base
|
|
24
|
+
prepend ActiveRecord::AttributeMethods
|
|
106
25
|
include ActiveRecord::Persistence
|
|
107
26
|
singleton_class.prepend ActiveRecord::ModelSchema::ClassMethods
|
|
108
27
|
|
|
@@ -111,25 +30,34 @@ module Switchman
|
|
|
111
30
|
::ActiveRecord::StatementCache::BindMap.prepend(ActiveRecord::StatementCache::BindMap)
|
|
112
31
|
::ActiveRecord::StatementCache::Substitute.send(:attr_accessor, :primary, :sharded)
|
|
113
32
|
|
|
114
|
-
::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
|
|
115
|
-
::ActiveRecord::Associations::HasOneAssociation.prepend(ActiveRecord::ForeignAssociation)
|
|
116
|
-
::ActiveRecord::Associations::HasManyAssociation.prepend(ActiveRecord::ForeignAssociation)
|
|
33
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::Associations::CollectionAssociation)
|
|
34
|
+
::ActiveRecord::Associations::HasOneAssociation.prepend(ActiveRecord::Associations::ForeignAssociation)
|
|
35
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(ActiveRecord::Associations::ForeignAssociation)
|
|
117
36
|
|
|
118
37
|
::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
|
|
119
38
|
|
|
120
|
-
prepend(ActiveRecord::AutosaveAssociation)
|
|
39
|
+
prepend(ActiveRecord::Associations::AutosaveAssociation)
|
|
121
40
|
|
|
122
|
-
::ActiveRecord::Associations::Association.prepend(ActiveRecord::Association)
|
|
123
|
-
::ActiveRecord::Associations::BelongsToAssociation.prepend(ActiveRecord::BelongsToAssociation)
|
|
124
|
-
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::CollectionProxy)
|
|
41
|
+
::ActiveRecord::Associations::Association.prepend(ActiveRecord::Associations::Association)
|
|
42
|
+
::ActiveRecord::Associations::BelongsToAssociation.prepend(ActiveRecord::Associations::BelongsToAssociation)
|
|
43
|
+
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::Associations::CollectionProxy)
|
|
125
44
|
|
|
126
|
-
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Preloader::Association)
|
|
45
|
+
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Associations::Preloader::Association)
|
|
46
|
+
::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
|
|
47
|
+
ActiveRecord::Associations::Preloader::Association::LoaderRecords
|
|
48
|
+
)
|
|
127
49
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
|
|
50
|
+
unless ::Rails.version < "7.1"
|
|
51
|
+
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
52
|
+
end
|
|
128
53
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
129
54
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
55
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
130
56
|
|
|
131
57
|
::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
|
|
132
|
-
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
58
|
+
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
59
|
+
ActiveRecord::DatabaseConfigurations::DatabaseConfig
|
|
60
|
+
)
|
|
133
61
|
|
|
134
62
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
135
63
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
@@ -137,6 +65,11 @@ module Switchman
|
|
|
137
65
|
::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext)
|
|
138
66
|
::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
|
|
139
67
|
|
|
68
|
+
if ::Rails.version > "7.1.3"
|
|
69
|
+
::ActiveRecord::PendingMigrationConnection.singleton_class
|
|
70
|
+
.include(ActiveRecord::PendingMigrationConnection::ClassMethods)
|
|
71
|
+
end
|
|
72
|
+
|
|
140
73
|
::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
|
|
141
74
|
::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
142
75
|
::ActiveRecord::Reflection::ThroughReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
@@ -149,62 +82,77 @@ module Switchman
|
|
|
149
82
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
|
150
83
|
::ActiveRecord::Relation.include(CallSuper)
|
|
151
84
|
|
|
152
|
-
::ActiveRecord::PredicateBuilder::
|
|
153
|
-
|
|
85
|
+
::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(
|
|
86
|
+
ActiveRecord::PredicateBuilder::PolymorphicArrayValue
|
|
87
|
+
)
|
|
154
88
|
|
|
155
89
|
::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
|
|
156
90
|
|
|
91
|
+
::ActiveRecord::TestFixtures.prepend(ActiveRecord::TestFixtures)
|
|
92
|
+
|
|
157
93
|
::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
|
|
158
94
|
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
|
159
95
|
|
|
160
|
-
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
161
|
-
|
|
162
96
|
::Arel::Table.prepend(Arel::Table)
|
|
163
97
|
::Arel::Visitors::ToSql.prepend(Arel::Visitors::ToSql)
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def self.foreign_key_check(name, type, limit: nil)
|
|
168
|
-
puts "WARNING: All foreign keys need to be 8-byte integers. #{name} looks like a foreign key. If so, please add the option: `:limit => 8`" if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
|
|
169
|
-
end
|
|
170
98
|
|
|
171
|
-
initializer 'switchman.extend_connection_adapters', after: 'active_record.initialize_database' do
|
|
172
|
-
::ActiveSupport.on_load(:active_record) do
|
|
173
99
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.descendants.each do |klass|
|
|
174
100
|
klass.prepend(ActiveRecord::AbstractAdapter::ForeignKeyCheck)
|
|
175
101
|
end
|
|
176
102
|
|
|
177
|
-
require 'switchman/active_record/table_definition'
|
|
178
103
|
::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
|
|
179
|
-
|
|
180
|
-
if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
|
181
|
-
require 'switchman/active_record/postgresql_adapter'
|
|
182
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
Shard.send(:initialize_sharding)
|
|
186
104
|
end
|
|
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
|
|
107
|
+
::ActiveRecord::Base
|
|
187
108
|
end
|
|
188
109
|
|
|
189
|
-
initializer
|
|
190
|
-
::ActiveSupport.on_load(:
|
|
191
|
-
|
|
192
|
-
require 'active_record/base'
|
|
110
|
+
initializer "switchman.error_patch", after: "active_record.initialize_database" do
|
|
111
|
+
::ActiveSupport.on_load(:active_record) do
|
|
112
|
+
::StandardError.include(StandardError)
|
|
193
113
|
end
|
|
194
114
|
end
|
|
195
115
|
|
|
196
|
-
initializer
|
|
197
|
-
::ActiveSupport.
|
|
198
|
-
|
|
116
|
+
initializer "switchman.initialize_cache", before: :initialize_cache, after: "active_record.initialize_database" do
|
|
117
|
+
::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
|
|
118
|
+
|
|
119
|
+
# if we haven't already setup our cache map out-of-band, set it up from
|
|
120
|
+
# config.cache_store now. behaves similarly to Rails' default
|
|
121
|
+
# initialize_cache initializer, but for each value in the map, rather
|
|
122
|
+
# than just Rails.cache. if config.cache_store is a flat value, uses it
|
|
123
|
+
# to fill just the Rails.env entry in the cache map.
|
|
124
|
+
unless Switchman.config[:cache_map].present?
|
|
125
|
+
cache_store_config = ::Rails.configuration.cache_store
|
|
126
|
+
cache_store_config = { ::Rails.env => cache_store_config } unless cache_store_config.is_a?(Hash)
|
|
199
127
|
|
|
200
|
-
::
|
|
128
|
+
Switchman.config[:cache_map] = ::ActiveSupport::Cache.lookup_stores(cache_store_config)
|
|
201
129
|
end
|
|
202
|
-
end
|
|
203
130
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
131
|
+
# if the configured cache map (either from before, or as populated from
|
|
132
|
+
# config.cache_store) didn't have an entry for Rails.env, add one using
|
|
133
|
+
# lookup_store(nil); matches the behavior of Rails' default
|
|
134
|
+
# initialize_cache initializer when config.cache_store is nil.
|
|
135
|
+
unless Switchman.config[:cache_map].key?(::Rails.env)
|
|
136
|
+
value = ::ActiveSupport::Cache.lookup_store(nil)
|
|
137
|
+
Switchman.config[:cache_map][::Rails.env] = value
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
middlewares = Switchman.config[:cache_map].values.filter_map do |store|
|
|
141
|
+
store.middleware if store.respond_to?(:middleware)
|
|
142
|
+
end.uniq
|
|
143
|
+
middlewares.each do |middleware|
|
|
144
|
+
config.middleware.insert_before("Rack::Runtime", middleware)
|
|
145
|
+
end
|
|
207
146
|
|
|
147
|
+
# prevent :initialize_cache from trying to (or needing to) set
|
|
148
|
+
# Rails.cache. once our switchman.extend_ar initializer (below) runs
|
|
149
|
+
# Rails.cache will be overridden to pull appropriate values from the
|
|
150
|
+
# cache map, but between now and then, Rails.cache should return the
|
|
151
|
+
# Rails.env entry in the cache map.
|
|
152
|
+
::Rails.cache = Switchman.config[:cache_map][::Rails.env]
|
|
153
|
+
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
154
|
+
|
|
155
|
+
::ActiveSupport.on_load(:action_controller) do
|
|
208
156
|
::ActionController::Base.include(ActionController::Caching)
|
|
209
157
|
end
|
|
210
158
|
end
|
|
@@ -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
|