active_record_proxy_adapters 0.4.2 → 0.4.4
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 +0 -3
- data/lib/active_record/connection_adapters/mysql2_proxy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_proxy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/trilogy_proxy_adapter.rb +1 -1
- data/lib/active_record_proxy_adapters/active_record_context.rb +18 -0
- data/lib/active_record_proxy_adapters/configuration.rb +44 -8
- data/lib/active_record_proxy_adapters/primary_replica_proxy.rb +19 -2
- data/lib/active_record_proxy_adapters/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3d41a4a59d8c5917bdbc07a02c626f6e0f028b1fac534a436ec114816902c4a
|
4
|
+
data.tar.gz: c0a14e90a707f6ffdaf2e0f67443a880de96c6bb3278795677dd47258c07bf26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a89e0589e582bc2ed1da956640bf3c1455e14ae4d712684adad898c7c2eb00c6b297cf5f0dd0184a1fc70c9642814d130749a3c2352480790fba4f0574c89ffe
|
7
|
+
data.tar.gz: 2f9dc79dad11c1469ff3fb04e1a201f6f746206714a49a03d9e15e501cade1878b0959ddeb80f02b6538d340a8af911f20588a93d251e87ee77ca120e9f97186
|
data/README.md
CHANGED
@@ -246,9 +246,6 @@ Since Rails already leases exactly one connection per thread from the pool and t
|
|
246
246
|
|
247
247
|
As long as you're not writing thread unsafe code that handles connections from the pool directly, or you don't have any other gem depenencies that write thread unsafe pool operations, you're all set.
|
248
248
|
|
249
|
-
There is, however, an open bug in `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter` for Rails versions 7.1 and greater that can cause random race conditions, but it's not caused by this gem (More info [here](https://github.com/rails/rails/issues/51780)).
|
250
|
-
Rails 7.0 works as expected.
|
251
|
-
|
252
249
|
Multi-threaded queries example:
|
253
250
|
```ruby
|
254
251
|
# app/models/application_record.rb
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
|
16
16
|
ADAPTER_NAME = "Mysql2Proxy"
|
17
17
|
|
18
|
-
delegate_to_proxy
|
18
|
+
delegate_to_proxy(*ActiveRecordProxyAdapters::ActiveRecordContext.hijackable_methods)
|
19
19
|
|
20
20
|
def initialize(...)
|
21
21
|
@proxy = ActiveRecordProxyAdapters::Mysql2Proxy.new(self)
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
|
16
16
|
ADAPTER_NAME = "PostgreSQLProxy"
|
17
17
|
|
18
|
-
delegate_to_proxy
|
18
|
+
delegate_to_proxy(*ActiveRecordProxyAdapters::ActiveRecordContext.hijackable_methods)
|
19
19
|
|
20
20
|
unless ActiveRecordProxyAdapters::ActiveRecordContext.active_record_v8_0_or_greater?
|
21
21
|
delegate_to_proxy :exec_no_cache, :exec_cache
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
|
16
16
|
ADAPTER_NAME = "TrilogyProxy"
|
17
17
|
|
18
|
-
delegate_to_proxy
|
18
|
+
delegate_to_proxy(*ActiveRecordProxyAdapters::ActiveRecordContext.hijackable_methods)
|
19
19
|
|
20
20
|
def initialize(...)
|
21
21
|
@proxy = ActiveRecordProxyAdapters::TrilogyProxy.new(self)
|
@@ -23,6 +23,8 @@ module ActiveRecordProxyAdapters
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def connection_class_for(connection)
|
26
|
+
return connection.connection_descriptor.name.constantize if active_record_v8_0_2_or_greater?
|
27
|
+
|
26
28
|
connection.connection_class || ActiveRecord::Base
|
27
29
|
end
|
28
30
|
|
@@ -33,6 +35,18 @@ module ActiveRecordProxyAdapters
|
|
33
35
|
ActiveRecord
|
34
36
|
end
|
35
37
|
|
38
|
+
def hijackable_methods
|
39
|
+
hijackable = %i[execute exec_query]
|
40
|
+
|
41
|
+
hijackable << :internal_exec_query if active_record_v8_0_or_greater?
|
42
|
+
|
43
|
+
hijackable
|
44
|
+
end
|
45
|
+
|
46
|
+
def active_record_v7?
|
47
|
+
active_record_version >= Gem::Version.new("7.0") && active_record_version < Gem::Version.new("8.0")
|
48
|
+
end
|
49
|
+
|
36
50
|
def active_record_v7_1_or_greater?
|
37
51
|
active_record_version >= Gem::Version.new("7.1")
|
38
52
|
end
|
@@ -44,5 +58,9 @@ module ActiveRecordProxyAdapters
|
|
44
58
|
def active_record_v8_0_or_greater?
|
45
59
|
active_record_version >= Gem::Version.new("8.0")
|
46
60
|
end
|
61
|
+
|
62
|
+
def active_record_v8_0_2_or_greater?
|
63
|
+
active_record_version >= Gem::Version.new("8.0.2")
|
64
|
+
end
|
47
65
|
end
|
48
66
|
end
|
@@ -11,19 +11,21 @@ module ActiveRecordProxyAdapters
|
|
11
11
|
LOG_SUBSCRIBER_REPLICA_PREFIX = proc { |event| "#{event.payload[:connection].class::ADAPTER_NAME} Replica" }.freeze
|
12
12
|
|
13
13
|
# @return [ActiveSupport::Duration] How long the proxy should reroute all read requests to the primary database
|
14
|
-
# since the latest write. Defaults to PROXY_DELAY.
|
15
|
-
|
14
|
+
# since the latest write. Defaults to PROXY_DELAY. Thread safe.
|
15
|
+
attr_reader :proxy_delay
|
16
16
|
# @return [ActiveSupport::Duration] How long the proxy should wait for a connection from the replica pool.
|
17
|
-
# Defaults to CHECKOUT_TIMEOUT.
|
18
|
-
|
17
|
+
# Defaults to CHECKOUT_TIMEOUT. Thread safe.
|
18
|
+
attr_reader :checkout_timeout
|
19
19
|
|
20
|
-
# @return [Proc] Prefix for the log subscriber when the primary database is used.
|
20
|
+
# @return [Proc] Prefix for the log subscriber when the primary database is used. Thread safe.
|
21
21
|
attr_reader :log_subscriber_primary_prefix
|
22
22
|
|
23
|
-
# @return [Proc] Prefix for the log subscriber when the replica database is used.
|
23
|
+
# @return [Proc] Prefix for the log subscriber when the replica database is used. Thread safe.
|
24
24
|
attr_reader :log_subscriber_replica_prefix
|
25
25
|
|
26
26
|
def initialize
|
27
|
+
@lock = Monitor.new
|
28
|
+
|
27
29
|
self.proxy_delay = PROXY_DELAY
|
28
30
|
self.checkout_timeout = CHECKOUT_TIMEOUT
|
29
31
|
self.log_subscriber_primary_prefix = LOG_SUBSCRIBER_PRIMARY_PREFIX
|
@@ -31,11 +33,45 @@ module ActiveRecordProxyAdapters
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def log_subscriber_primary_prefix=(prefix)
|
34
|
-
|
36
|
+
prefix_proc = prefix.is_a?(Proc) ? prefix : proc { prefix.to_s }
|
37
|
+
|
38
|
+
synchronize_update(:log_subscriber_primary_prefix, from: @log_subscriber_primary_prefix, to: prefix_proc) do
|
39
|
+
@log_subscriber_primary_prefix = prefix_proc
|
40
|
+
end
|
35
41
|
end
|
36
42
|
|
37
43
|
def log_subscriber_replica_prefix=(prefix)
|
38
|
-
|
44
|
+
prefix_proc = prefix.is_a?(Proc) ? prefix : proc { prefix.to_s }
|
45
|
+
|
46
|
+
synchronize_update(:log_subscriber_replica_prefix, from: @log_subscriber_replica_prefix, to: prefix_proc) do
|
47
|
+
@log_subscriber_replica_prefix = prefix_proc
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def proxy_delay=(proxy_delay)
|
52
|
+
synchronize_update(:proxy_delay, from: @proxy_delay, to: proxy_delay) do
|
53
|
+
@proxy_delay = proxy_delay
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def checkout_timeout=(checkout_timeout)
|
58
|
+
synchronize_update(:checkout_timeout, from: @checkout_timeout, to: checkout_timeout) do
|
59
|
+
@checkout_timeout = checkout_timeout
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def synchronize_update(attribute, from:, to:, &block)
|
66
|
+
ActiveSupport::Notifications.instrument(
|
67
|
+
"active_record_proxy_adapters.configuration_update",
|
68
|
+
attribute:,
|
69
|
+
who: Thread.current,
|
70
|
+
from:,
|
71
|
+
to:
|
72
|
+
) do
|
73
|
+
@lock.synchronize(&block)
|
74
|
+
end
|
39
75
|
end
|
40
76
|
end
|
41
77
|
end
|
@@ -30,7 +30,7 @@ module ActiveRecordProxyAdapters
|
|
30
30
|
/DROP\s/i].map(&:freeze).freeze
|
31
31
|
|
32
32
|
# Abstract adapter methods that should be proxied.
|
33
|
-
hijack_method
|
33
|
+
hijack_method(*ActiveRecordContext.hijackable_methods)
|
34
34
|
|
35
35
|
def self.hijacked_methods
|
36
36
|
@hijacked_methods.to_a
|
@@ -50,6 +50,22 @@ module ActiveRecordProxyAdapters
|
|
50
50
|
delegate :connection_handler, to: :connection_class
|
51
51
|
delegate :reading_role, :writing_role, to: :active_record_context
|
52
52
|
|
53
|
+
# We need to call .verify! to ensure `configure_connection` is called on the instance before attempting to use it.
|
54
|
+
# This is necessary because the connection may have been lazily initialized and is an unintended side effect from a
|
55
|
+
# change in Rails to defer connection verification: https://github.com/rails/rails/pull/44576
|
56
|
+
# verify! cannot be called before the object is initialized and because of how the proxy hooks into the connection
|
57
|
+
# instance, it has to be done lazily (hence the memoization). Ideally, we shouldn't have to worry about this at all
|
58
|
+
# But there is tight coupling between methods in ActiveRecord::ConnectionAdapters::AbstractAdapter and
|
59
|
+
# its descendants which will require significant refactoring to be decoupled.
|
60
|
+
# See https://github.com/rails/rails/issues/51780
|
61
|
+
def verified_primary_connection
|
62
|
+
@verified_primary_connection ||= begin
|
63
|
+
connected_to(role: writing_role) { primary_connection.verify! }
|
64
|
+
|
65
|
+
primary_connection
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
53
69
|
def replica_pool_unavailable?
|
54
70
|
!replica_pool
|
55
71
|
end
|
@@ -120,7 +136,8 @@ module ActiveRecordProxyAdapters
|
|
120
136
|
end
|
121
137
|
|
122
138
|
def connection_for(role, sql_string)
|
123
|
-
connection =
|
139
|
+
connection = verified_primary_connection if role == writing_role || replica_pool_unavailable?
|
140
|
+
|
124
141
|
connection ||= checkout_replica_connection
|
125
142
|
|
126
143
|
result = connected_to(role:) { yield connection }
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_proxy_adapters
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Cruz
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-19 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activerecord
|