active_record_proxy_adapters 0.6.1 → 0.6.2
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/lib/active_record_proxy_adapters/active_record_context.rb +1 -1
- data/lib/active_record_proxy_adapters/configuration.rb +11 -0
- data/lib/active_record_proxy_adapters/context.rb +42 -0
- data/lib/active_record_proxy_adapters/contextualizer.rb +20 -0
- data/lib/active_record_proxy_adapters/mixin/configuration.rb +59 -0
- data/lib/active_record_proxy_adapters/primary_replica_proxy.rb +13 -29
- data/lib/active_record_proxy_adapters/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea3aa59337aa2ed793bfb2e51666d08ef6b40ad9ccd2008453807baeac862933
|
4
|
+
data.tar.gz: 1e899907a68ecf3c6ca5a51380e2b7be9859d40c0b033f23b872037c6d1fd5a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b9018059039d65f435a8d4571f5eea8a477e4388728a675b2176252959e5fb6afd0833cb21e65ea5bbd04e77c484f1d2ec3974d698b057c72440f7a9349150a
|
7
|
+
data.tar.gz: 7bf7662851cc2d6b1bcd7fcb59df5216fb87f28c368910da05476b6b734f692f2182de15bb731276f5952690b9ded4840ed2ddd2e35653fb2ff17c9c6e0a7bc4
|
@@ -40,7 +40,7 @@ module ActiveRecordProxyAdapters
|
|
40
40
|
def hijackable_methods
|
41
41
|
hijackable = %i[execute exec_query]
|
42
42
|
|
43
|
-
hijackable << :internal_exec_query if
|
43
|
+
hijackable << :internal_exec_query if active_record_v7_1_or_greater?
|
44
44
|
|
45
45
|
hijackable
|
46
46
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/integer/time"
|
4
4
|
require "active_record_proxy_adapters/synchronizable_configuration"
|
5
5
|
require "active_record_proxy_adapters/cache_configuration"
|
6
|
+
require "active_record_proxy_adapters/context"
|
6
7
|
|
7
8
|
module ActiveRecordProxyAdapters
|
8
9
|
# Provides a global configuration object to configure how the proxy should behave.
|
@@ -27,6 +28,9 @@ module ActiveRecordProxyAdapters
|
|
27
28
|
# @return [Proc] Prefix for the log subscriber when the replica database is used. Thread safe.
|
28
29
|
attr_reader :log_subscriber_replica_prefix
|
29
30
|
|
31
|
+
# @return [Class] The context that is used to store the current request's state.
|
32
|
+
attr_reader :context_store
|
33
|
+
|
30
34
|
def initialize
|
31
35
|
@lock = Monitor.new
|
32
36
|
|
@@ -35,6 +39,7 @@ module ActiveRecordProxyAdapters
|
|
35
39
|
self.log_subscriber_primary_prefix = LOG_SUBSCRIBER_PRIMARY_PREFIX
|
36
40
|
self.log_subscriber_replica_prefix = LOG_SUBSCRIBER_REPLICA_PREFIX
|
37
41
|
self.cache_configuration = CacheConfiguration.new(@lock)
|
42
|
+
self.context_store = ActiveRecordProxyAdapters::Context
|
38
43
|
end
|
39
44
|
|
40
45
|
def log_subscriber_primary_prefix=(prefix)
|
@@ -65,6 +70,12 @@ module ActiveRecordProxyAdapters
|
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
73
|
+
def context_store=(context_store)
|
74
|
+
synchronize_update(:context_store, from: @context_store, to: context_store) do
|
75
|
+
@context_store = context_store
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
68
79
|
def cache
|
69
80
|
block_given? ? yield(cache_configuration) : cache_configuration
|
70
81
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_proxy_adapters/mixin/configuration"
|
4
|
+
|
5
|
+
module ActiveRecordProxyAdapters
|
6
|
+
# Context is a simple class that holds a registry of connection names and their last write timestamps.
|
7
|
+
# It is used to track the last time a write operation was performed on each connection.
|
8
|
+
# This allows the proxy to determine whether to route read requests to the primary or replica database
|
9
|
+
class Context
|
10
|
+
include Mixin::Configuration
|
11
|
+
|
12
|
+
# @param hash [Hash] A hash containing the connection names as keys and the last write timestamps as values.
|
13
|
+
# Can be empty.
|
14
|
+
def initialize(hash)
|
15
|
+
@timestamp_registry = hash.transform_values(&:to_f)
|
16
|
+
end
|
17
|
+
|
18
|
+
def recent_write_to_primary?(connection_name)
|
19
|
+
now - self[connection_name] < proxy_delay
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_for(connection_name)
|
23
|
+
self[connection_name] = now
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](connection_name)
|
27
|
+
timestamp_registry[connection_name] || 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(connection_name, timestamp)
|
31
|
+
timestamp_registry[connection_name] = timestamp
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :timestamp_registry
|
37
|
+
|
38
|
+
def now
|
39
|
+
Time.now.utc.to_f
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordProxyAdapters
|
4
|
+
# A mixin for managing the context of current database connections.
|
5
|
+
module Contextualizer
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# @return [ActiveRecordProxyAdapters::Context]
|
9
|
+
# Retrieves the context for the current thread.
|
10
|
+
def current_context
|
11
|
+
Thread.current.thread_variable_get(:arpa_context)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param context [ActiveRecordProxyAdapters::Context]
|
15
|
+
# Sets the context for the current thread.
|
16
|
+
def current_context=(context)
|
17
|
+
Thread.current.thread_variable_set(:arpa_context, context)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/integer/time"
|
4
|
+
require "active_record_proxy_adapters/synchronizable_configuration"
|
5
|
+
require "active_record_proxy_adapters/cache_configuration"
|
6
|
+
require "active_record_proxy_adapters/context"
|
7
|
+
|
8
|
+
module ActiveRecordProxyAdapters
|
9
|
+
module Mixin
|
10
|
+
# Provides helpers to access to reduce boilerplate while retrieving configuration properties.
|
11
|
+
module Configuration
|
12
|
+
# Helper to retrieve the proxy delay from the configuration stored in
|
13
|
+
# {ActiveRecordProxyAdapters::Configuration#proxy_delay}.
|
14
|
+
# @return [ActiveSupport::Duration]
|
15
|
+
def proxy_delay
|
16
|
+
proxy_config.proxy_delay
|
17
|
+
end
|
18
|
+
|
19
|
+
# Helper to retrieve the checkout timeout from the configuration stored in
|
20
|
+
# {ActiveRecordProxyAdapters::Configuration#checkout_timeout}.
|
21
|
+
# @return [ActiveSupport::Duration]
|
22
|
+
def checkout_timeout
|
23
|
+
proxy_config.checkout_timeout
|
24
|
+
end
|
25
|
+
|
26
|
+
# Helper to retrieve the context store class from the configuration stored in
|
27
|
+
# {ActiveRecordProxyAdapters::Configuration#context_store}.
|
28
|
+
# @return [Class]
|
29
|
+
def context_store
|
30
|
+
proxy_config.context_store
|
31
|
+
end
|
32
|
+
|
33
|
+
# Helper to retrieve the cache store from the configuration stored in
|
34
|
+
# {ActiveRecordProxyAdapters::CacheConfiguration#store}.
|
35
|
+
# @return [ActiveSupport::Cache::Store]
|
36
|
+
def cache_store
|
37
|
+
cache_config.store
|
38
|
+
end
|
39
|
+
|
40
|
+
# Helper to retrieve the cache key prefix from the configuration stored in
|
41
|
+
# {ActiveRecordProxyAdapters::CacheConfiguration#key_prefix}.
|
42
|
+
# It uses the key builder to generate a cache key for the given SQL string, prepended with the key prefix.
|
43
|
+
# @return [String]
|
44
|
+
def cache_key_for(sql_string)
|
45
|
+
cache_config.key_builder.call(sql_string).prepend(cache_config.key_prefix)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @!visibility private
|
49
|
+
def cache_config
|
50
|
+
proxy_config.cache
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def proxy_config
|
55
|
+
ActiveRecordProxyAdapters.config
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record_proxy_adapters/active_record_context"
|
3
4
|
require "active_record_proxy_adapters/configuration"
|
5
|
+
require "active_record_proxy_adapters/contextualizer"
|
6
|
+
require "active_record_proxy_adapters/hijackable"
|
7
|
+
require "active_record_proxy_adapters/mixin/configuration"
|
4
8
|
require "active_support/core_ext/module/delegation"
|
5
9
|
require "active_support/core_ext/object/blank"
|
6
|
-
require "concurrent-ruby"
|
7
|
-
require "active_record_proxy_adapters/active_record_context"
|
8
|
-
require "active_record_proxy_adapters/hijackable"
|
9
10
|
|
10
11
|
module ActiveRecordProxyAdapters
|
11
12
|
# This is the base class for all proxies. It defines the methods that should be proxied
|
12
13
|
# and the logic to determine which database to use.
|
13
14
|
class PrimaryReplicaProxy # rubocop:disable Metrics/ClassLength
|
14
15
|
include Hijackable
|
16
|
+
include Contextualizer
|
17
|
+
include Mixin::Configuration
|
15
18
|
|
16
19
|
# All queries that match these patterns should be sent to the primary database
|
17
20
|
SQL_PRIMARY_MATCHERS = [
|
@@ -51,13 +54,12 @@ module ActiveRecordProxyAdapters
|
|
51
54
|
# @param primary_connection [ActiveRecord::ConnectionAdatpers::AbstractAdapter]
|
52
55
|
def initialize(primary_connection)
|
53
56
|
@primary_connection = primary_connection
|
54
|
-
@last_write_at = 0
|
55
57
|
@active_record_context = ActiveRecordContext.new
|
56
58
|
end
|
57
59
|
|
58
60
|
private
|
59
61
|
|
60
|
-
attr_reader :primary_connection, :
|
62
|
+
attr_reader :primary_connection, :active_record_context
|
61
63
|
|
62
64
|
delegate :connection_handler, to: :connection_class
|
63
65
|
delegate :reading_role, :writing_role, to: :active_record_context
|
@@ -140,10 +142,6 @@ module ActiveRecordProxyAdapters
|
|
140
142
|
[reading_role, writing_role].include?(role) ? role : nil
|
141
143
|
end
|
142
144
|
|
143
|
-
def cache_key_for(sql_string)
|
144
|
-
cache_config.key_builder.call(sql_string).prepend(cache_config.key_prefix)
|
145
|
-
end
|
146
|
-
|
147
145
|
def connected_to_stack
|
148
146
|
return connection_class.connected_to_stack if connection_class.respond_to?(:connected_to_stack)
|
149
147
|
|
@@ -221,11 +219,9 @@ module ActiveRecordProxyAdapters
|
|
221
219
|
proc { |matcher| matcher.match?(sql_string) }
|
222
220
|
end
|
223
221
|
|
224
|
-
# TODO: implement a context API to re-route requests to the primary database if a recent query was sent to it to
|
225
|
-
# avoid replication delay issues
|
226
222
|
# @return Boolean
|
227
223
|
def recent_write_to_primary?
|
228
|
-
|
224
|
+
proxy_context.recent_write_to_primary?(primary_connection_name)
|
229
225
|
end
|
230
226
|
|
231
227
|
def in_transaction?
|
@@ -233,27 +229,15 @@ module ActiveRecordProxyAdapters
|
|
233
229
|
end
|
234
230
|
|
235
231
|
def update_primary_latest_write_timestamp
|
236
|
-
|
237
|
-
end
|
238
|
-
|
239
|
-
def cache_store
|
240
|
-
cache_config.store
|
241
|
-
end
|
242
|
-
|
243
|
-
def cache_config
|
244
|
-
proxy_config.cache
|
245
|
-
end
|
246
|
-
|
247
|
-
def proxy_delay
|
248
|
-
proxy_config.proxy_delay
|
232
|
+
proxy_context.update_for(primary_connection_name)
|
249
233
|
end
|
250
234
|
|
251
|
-
def
|
252
|
-
|
235
|
+
def primary_connection_name
|
236
|
+
@primary_connection_name ||= primary_connection.pool.try(:db_config).try(:name).try(:to_sym)
|
253
237
|
end
|
254
238
|
|
255
|
-
def
|
256
|
-
|
239
|
+
def proxy_context
|
240
|
+
self.current_context ||= context_store.new({})
|
257
241
|
end
|
258
242
|
end
|
259
243
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_proxy_adapters
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Cruz
|
@@ -94,9 +94,12 @@ files:
|
|
94
94
|
- lib/active_record_proxy_adapters/connection_handling/postgresql.rb
|
95
95
|
- lib/active_record_proxy_adapters/connection_handling/sqlite3.rb
|
96
96
|
- lib/active_record_proxy_adapters/connection_handling/trilogy.rb
|
97
|
+
- lib/active_record_proxy_adapters/context.rb
|
98
|
+
- lib/active_record_proxy_adapters/contextualizer.rb
|
97
99
|
- lib/active_record_proxy_adapters/database_tasks.rb
|
98
100
|
- lib/active_record_proxy_adapters/hijackable.rb
|
99
101
|
- lib/active_record_proxy_adapters/log_subscriber.rb
|
102
|
+
- lib/active_record_proxy_adapters/mixin/configuration.rb
|
100
103
|
- lib/active_record_proxy_adapters/mysql2_proxy.rb
|
101
104
|
- lib/active_record_proxy_adapters/postgresql_proxy.rb
|
102
105
|
- lib/active_record_proxy_adapters/primary_replica_proxy.rb
|