active_record_host_pool 0.12.0 → 1.0.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/Readme.md +9 -1
- data/lib/active_record_host_pool.rb +1 -0
- data/lib/active_record_host_pool/clear_query_cache_patch.rb +33 -0
- data/lib/active_record_host_pool/connection_adapter_mixin.rb +11 -11
- data/lib/active_record_host_pool/pool_proxy.rb +28 -0
- data/lib/active_record_host_pool/version.rb +1 -1
- data/test/database.yml +9 -0
- data/test/helper.rb +18 -0
- data/test/schema.rb +9 -1
- data/test/test_arhp.rb +68 -0
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5238d6f59f78811a9e9972e613562d674d1137604438b46d0ef3b9a3eb1fa437
|
4
|
+
data.tar.gz: 937e6d46772dc13b3b6faa62841053252aae17f92f8ef205b63927345408ed65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdbd8abf041ad7960996bbd0432fc19c90c0ae0c41328069798c401929cca9c3b9dd510f7160ab833895962b12e86937bf8eb592d851bea2197b94fcdf57b95a
|
7
|
+
data.tar.gz: '068fff2a8be21b0e1ca192a49967c352d90b5c81aae27c11f36128ec825b4c03a9087f56269b10ca2cf406ddea445dc48662110284f1a3870eb4d48df0ceba7b'
|
data/Readme.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[](https://github.com/zendesk/active_record_host_pool/actions?query=workflow%3ACI)
|
2
2
|
|
3
3
|
# ActiveRecord host pooling
|
4
4
|
|
@@ -24,6 +24,14 @@ You need a local user called 'john-doe'.
|
|
24
24
|
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,INDEX ON *.* TO 'john-doe'@'localhost';
|
25
25
|
FLUSH PRIVILEGES;
|
26
26
|
|
27
|
+
With mysql running locally, run
|
28
|
+
|
29
|
+
BUNDLE_GEMFILE=gemfiles/rails5.2.gemfile bundle exec rake test
|
30
|
+
|
31
|
+
Or
|
32
|
+
|
33
|
+
BUNDLE_GEMFILE=gemfiles/rails5.2.gemfile ruby test/test_arhp.rb --seed 19911 --verbose
|
34
|
+
|
27
35
|
## Copyright
|
28
36
|
|
29
37
|
Copyright (c) 2011 Zendesk. See MIT-LICENSE for details.
|
@@ -4,6 +4,7 @@ require 'active_record'
|
|
4
4
|
require 'active_record/base'
|
5
5
|
require 'active_record/connection_adapters/abstract_adapter'
|
6
6
|
|
7
|
+
require 'active_record_host_pool/clear_query_cache_patch'
|
7
8
|
require 'active_record_host_pool/connection_proxy'
|
8
9
|
require 'active_record_host_pool/pool_proxy'
|
9
10
|
require 'active_record_host_pool/connection_adapter_mixin'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if ActiveRecord.version >= Gem::Version.new('6.0')
|
4
|
+
module ActiveRecordHostPool
|
5
|
+
# ActiveRecord 6.0 introduced multiple database support. With that, an update
|
6
|
+
# has been made in https://github.com/rails/rails/pull/35089 to ensure that
|
7
|
+
# all query caches are cleared across connection handlers and pools. If you
|
8
|
+
# write on one connection, the other connection will have the update that
|
9
|
+
# occurred.
|
10
|
+
#
|
11
|
+
# This broke ARHP which implements its own pool, allowing you to access
|
12
|
+
# multiple databases with the same connection (e.g. 1 connection for 100
|
13
|
+
# shards on the same server).
|
14
|
+
#
|
15
|
+
# This patch maintains the reference to the database during the cache clearing
|
16
|
+
# to ensure that the database doesn't get swapped out mid-way into an
|
17
|
+
# operation.
|
18
|
+
#
|
19
|
+
# This is a private Rails API and may change in future releases as they're
|
20
|
+
# actively working on sharding in Rails 6 and above.
|
21
|
+
module ClearQueryCachePatch
|
22
|
+
def clear_query_caches_for_current_thread
|
23
|
+
host_pool_current_database_was = connection.unproxied._host_pool_current_database
|
24
|
+
super
|
25
|
+
ensure
|
26
|
+
# restore in case clearing the cache changed the database
|
27
|
+
connection.unproxied._host_pool_current_database = host_pool_current_database_was
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
ActiveRecord::Base.singleton_class.prepend ActiveRecordHostPool::ClearQueryCachePatch
|
33
|
+
end
|
@@ -85,22 +85,21 @@ module ActiveRecordHostPool
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
+
# rubocop:disable Lint/DuplicateMethods
|
88
89
|
module ActiveRecord
|
89
90
|
module ConnectionAdapters
|
90
91
|
class ConnectionHandler
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
owner_to_pool[spec.name] = ActiveRecordHostPool::PoolProxy.new(spec)
|
100
|
-
end
|
92
|
+
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
93
|
+
when '5.1', '5.2', '6.0'
|
94
|
+
|
95
|
+
def establish_connection(spec)
|
96
|
+
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
|
97
|
+
spec = resolver.spec(spec)
|
98
|
+
|
99
|
+
owner_to_pool[spec.name] = ActiveRecordHostPool::PoolProxy.new(spec)
|
101
100
|
end
|
102
101
|
|
103
|
-
|
102
|
+
when '4.2'
|
104
103
|
|
105
104
|
def establish_connection(owner, spec)
|
106
105
|
@class_to_pool.clear
|
@@ -116,5 +115,6 @@ module ActiveRecord
|
|
116
115
|
end
|
117
116
|
end
|
118
117
|
end
|
118
|
+
# rubocop:enable Lint/DuplicateMethods
|
119
119
|
|
120
120
|
ActiveRecord::ConnectionAdapters::Mysql2Adapter.include(ActiveRecordHostPool::DatabaseSwitch)
|
@@ -84,6 +84,34 @@ module ActiveRecordHostPool
|
|
84
84
|
_clear_connection_proxy_cache
|
85
85
|
end
|
86
86
|
|
87
|
+
def release_connection(*args)
|
88
|
+
p = _connection_pool(false)
|
89
|
+
return unless p
|
90
|
+
|
91
|
+
p.release_connection(*args)
|
92
|
+
end
|
93
|
+
|
94
|
+
def flush!
|
95
|
+
p = _connection_pool(false)
|
96
|
+
return unless p
|
97
|
+
|
98
|
+
p.flush!
|
99
|
+
end
|
100
|
+
|
101
|
+
def discard!
|
102
|
+
p = _connection_pool(false)
|
103
|
+
return unless p
|
104
|
+
|
105
|
+
p.discard!
|
106
|
+
|
107
|
+
# All connections in the pool (even if they're currently
|
108
|
+
# leased!) have just been discarded, along with the pool itself.
|
109
|
+
# Any further interaction with the pool (except #spec and #schema_cache)
|
110
|
+
# is undefined.
|
111
|
+
# Remove the connection for the given key so a new one can be created in its place
|
112
|
+
_connection_pools.delete(_pool_key)
|
113
|
+
end
|
114
|
+
|
87
115
|
private
|
88
116
|
|
89
117
|
def rescuable_errors
|
data/test/database.yml
CHANGED
@@ -66,3 +66,12 @@ test_host_1_db_not_there:
|
|
66
66
|
password: <%= mysql.password %>
|
67
67
|
host: <%= mysql.host %>
|
68
68
|
reconnect: true
|
69
|
+
|
70
|
+
test_host_1_db_shard:
|
71
|
+
adapter: mysql2
|
72
|
+
encoding: utf8
|
73
|
+
database: arhp_test_1_shard
|
74
|
+
username: <%= mysql.user %>
|
75
|
+
password: <%= mysql.password %>
|
76
|
+
host: <%= mysql.host %>
|
77
|
+
reconnect: true
|
data/test/helper.rb
CHANGED
@@ -25,6 +25,13 @@ module ARHPTestSetup
|
|
25
25
|
return if ARHPTestSetup.const_defined?('Test1')
|
26
26
|
|
27
27
|
eval <<-RUBY
|
28
|
+
# The placement of the Test1Shard class is important so that its
|
29
|
+
# connection will not be the most recent connection established
|
30
|
+
# for test_host_1.
|
31
|
+
class Test1Shard < ::ActiveRecord::Base
|
32
|
+
establish_connection(:test_host_1_db_shard)
|
33
|
+
end
|
34
|
+
|
28
35
|
class Test1 < ActiveRecord::Base
|
29
36
|
self.table_name = "tests"
|
30
37
|
establish_connection(:test_host_1_db_1)
|
@@ -60,4 +67,15 @@ module ARHPTestSetup
|
|
60
67
|
def current_database(klass)
|
61
68
|
klass.connection.select_value('select DATABASE()')
|
62
69
|
end
|
70
|
+
|
71
|
+
# Remove a method from a given module that fixes something.
|
72
|
+
# Execute the passed in block.
|
73
|
+
# Re-add the method back to the module.
|
74
|
+
def without_module_patch(mod, method_name)
|
75
|
+
method_body = mod.instance_method(method_name)
|
76
|
+
mod.remove_method(method_name)
|
77
|
+
yield if block_given?
|
78
|
+
ensure
|
79
|
+
mod.define_method(method_name, method_body)
|
80
|
+
end
|
63
81
|
end
|
data/test/schema.rb
CHANGED
@@ -2,7 +2,15 @@
|
|
2
2
|
|
3
3
|
require_relative 'helper'
|
4
4
|
ActiveRecord::Schema.define(version: 1) do
|
5
|
-
create_table 'tests' do |t|
|
5
|
+
create_table 'tests', force: true do |t|
|
6
6
|
t.string 'val'
|
7
7
|
end
|
8
|
+
|
9
|
+
# Add a table only the shard database will have. Conditional
|
10
|
+
# exists since Phenix loads the schema file for every database.
|
11
|
+
if ActiveRecord::Base.connection.current_database == 'arhp_test_1_shard'
|
12
|
+
create_table 'test1_shards' do |t|
|
13
|
+
t.string 'name'
|
14
|
+
end
|
15
|
+
end
|
8
16
|
end
|
data/test/test_arhp.rb
CHANGED
@@ -13,6 +13,23 @@ class ActiveRecordHostPoolTest < Minitest::Test
|
|
13
13
|
Phenix.burn!
|
14
14
|
end
|
15
15
|
|
16
|
+
def test_process_forking_with_connections
|
17
|
+
# Ensure we have a connection already
|
18
|
+
assert_equal(true, ActiveRecord::Base.connected?)
|
19
|
+
|
20
|
+
# Verify that when we fork, the process doesn't crash
|
21
|
+
pid = Process.fork do
|
22
|
+
if ActiveRecord.version >= Gem::Version.new('5.2')
|
23
|
+
assert_equal(false, ActiveRecord::Base.connected?) # New to Rails 5.2
|
24
|
+
else
|
25
|
+
assert_equal(true, ActiveRecord::Base.connected?)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
Process.wait(pid)
|
29
|
+
# Cleanup any connections we may have left around
|
30
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
31
|
+
end
|
32
|
+
|
16
33
|
def test_models_with_matching_hosts_should_share_a_connection
|
17
34
|
assert_equal(Test1.connection.raw_connection, Test2.connection.raw_connection)
|
18
35
|
assert_equal(Test3.connection.raw_connection, Test4.connection.raw_connection)
|
@@ -38,6 +55,38 @@ class ActiveRecordHostPoolTest < Minitest::Test
|
|
38
55
|
assert_action_uses_correct_database(:insert, "insert into tests values(NULL, 'foo')")
|
39
56
|
end
|
40
57
|
|
58
|
+
def test_models_with_matching_hosts_and_non_matching_databases_should_share_a_connection
|
59
|
+
simulate_rails_app_active_record_railties
|
60
|
+
assert_equal(Test1.connection.raw_connection, Test1Shard.connection.raw_connection)
|
61
|
+
end
|
62
|
+
|
63
|
+
if ActiveRecord.version >= Gem::Version.new('6.0')
|
64
|
+
def test_models_with_matching_hosts_and_non_matching_databases_issue_exists_without_arhp_patch
|
65
|
+
simulate_rails_app_active_record_railties
|
66
|
+
|
67
|
+
# Remove patch that fixes an issue in Rails 6+ to ensure it still
|
68
|
+
# exists. If this begins to fail then it may mean that Rails has fixed
|
69
|
+
# the issue so that it no longer occurs.
|
70
|
+
without_module_patch(ActiveRecordHostPool::ClearQueryCachePatch, :clear_query_caches_for_current_thread) do
|
71
|
+
exception = assert_raises(ActiveRecord::StatementInvalid) do
|
72
|
+
ActiveRecord::Base.cache { Test1Shard.create! }
|
73
|
+
end
|
74
|
+
|
75
|
+
assert_equal("Mysql2::Error: Table 'arhp_test_2.test1_shards' doesn't exist", exception.message)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_models_with_matching_hosts_and_non_matching_databases_do_not_mix_up_underlying_database
|
80
|
+
simulate_rails_app_active_record_railties
|
81
|
+
|
82
|
+
# ActiveRecord 6.0 introduced a change that surfaced a problematic code
|
83
|
+
# path in active_record_host_pool when clearing caches across connection
|
84
|
+
# handlers which can cause the database to change.
|
85
|
+
# See ActiveRecordHostPool::ClearQueryCachePatch
|
86
|
+
ActiveRecord::Base.cache { Test1Shard.create! }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
41
90
|
def test_connection_returns_a_proxy
|
42
91
|
assert_kind_of ActiveRecordHostPool::ConnectionProxy, Test1.connection
|
43
92
|
end
|
@@ -146,6 +195,13 @@ class ActiveRecordHostPoolTest < Minitest::Test
|
|
146
195
|
assert_equal expected_database, current_database(switch_to_klass)
|
147
196
|
end
|
148
197
|
|
198
|
+
def test_release_connection
|
199
|
+
pool = ActiveRecord::Base.connection_pool
|
200
|
+
conn = pool.connection
|
201
|
+
pool.expects(:checkin).with(conn)
|
202
|
+
pool.release_connection
|
203
|
+
end
|
204
|
+
|
149
205
|
private
|
150
206
|
|
151
207
|
def assert_action_uses_correct_database(action, sql)
|
@@ -156,4 +212,16 @@ class ActiveRecordHostPoolTest < Minitest::Test
|
|
156
212
|
assert_equal desired_db, current_database(klass)
|
157
213
|
end
|
158
214
|
end
|
215
|
+
|
216
|
+
def simulate_rails_app_active_record_railties
|
217
|
+
if ActiveRecord.version >= Gem::Version.new('6.0')
|
218
|
+
# Necessary for testing ActiveRecord 6.0 which uses the connection
|
219
|
+
# handlers when clearing query caches across all handlers when
|
220
|
+
# an operation that dirties the cache is involved (e.g. create/insert,
|
221
|
+
# update, delete/destroy, truncate, etc.)
|
222
|
+
ActiveRecord::Base.connection_handlers = {
|
223
|
+
ActiveRecord::Base.writing_role => ActiveRecord::Base.default_connection_handler
|
224
|
+
}
|
225
|
+
end
|
226
|
+
end
|
159
227
|
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_host_pool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin Quorning
|
8
8
|
- Gabe Martin-Dempesy
|
9
9
|
- Pierre Schambacher
|
10
10
|
- Ben Osheroff
|
11
|
-
autorequire:
|
11
|
+
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2021-02-09 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activerecord
|
@@ -22,7 +22,7 @@ dependencies:
|
|
22
22
|
version: 4.2.0
|
23
23
|
- - "<"
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '6.
|
25
|
+
version: '6.1'
|
26
26
|
type: :runtime
|
27
27
|
prerelease: false
|
28
28
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 4.2.0
|
33
33
|
- - "<"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '6.
|
35
|
+
version: '6.1'
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mysql2
|
38
38
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,14 +109,14 @@ dependencies:
|
|
109
109
|
requirements:
|
110
110
|
- - "~>"
|
111
111
|
- !ruby/object:Gem::Version
|
112
|
-
version: 0.
|
112
|
+
version: 0.80.0
|
113
113
|
type: :development
|
114
114
|
prerelease: false
|
115
115
|
version_requirements: !ruby/object:Gem::Requirement
|
116
116
|
requirements:
|
117
117
|
- - "~>"
|
118
118
|
- !ruby/object:Gem::Version
|
119
|
-
version: 0.
|
119
|
+
version: 0.80.0
|
120
120
|
- !ruby/object:Gem::Dependency
|
121
121
|
name: shoulda
|
122
122
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,6 +145,7 @@ files:
|
|
145
145
|
- MIT-LICENSE
|
146
146
|
- Readme.md
|
147
147
|
- lib/active_record_host_pool.rb
|
148
|
+
- lib/active_record_host_pool/clear_query_cache_patch.rb
|
148
149
|
- lib/active_record_host_pool/connection_adapter_mixin.rb
|
149
150
|
- lib/active_record_host_pool/connection_proxy.rb
|
150
151
|
- lib/active_record_host_pool/pool_proxy.rb
|
@@ -157,7 +158,7 @@ homepage: https://github.com/zendesk/active_record_host_pool
|
|
157
158
|
licenses:
|
158
159
|
- MIT
|
159
160
|
metadata: {}
|
160
|
-
post_install_message:
|
161
|
+
post_install_message:
|
161
162
|
rdoc_options: []
|
162
163
|
require_paths:
|
163
164
|
- lib
|
@@ -172,8 +173,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
173
|
- !ruby/object:Gem::Version
|
173
174
|
version: '0'
|
174
175
|
requirements: []
|
175
|
-
rubygems_version: 3.
|
176
|
-
signing_key:
|
176
|
+
rubygems_version: 3.2.2
|
177
|
+
signing_key:
|
177
178
|
specification_version: 4
|
178
179
|
summary: Allow ActiveRecord to share a connection to multiple databases on the same
|
179
180
|
host
|