active_record_proxy_adapters 0.3.6 → 0.4.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/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/Appraisals +25 -0
- data/CHANGELOG.md +49 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Dockerfile +21 -0
- data/db/mysql_structure.sql +41 -0
- data/db/postgresql_structure.sql +86 -0
- data/docker/postgres_replica/cmd.sh +9 -0
- data/docker-compose.yml +98 -0
- data/gemfiles/rails_7.0.gemfile +25 -0
- data/gemfiles/rails_7.0.gemfile.lock +122 -0
- data/gemfiles/rails_7.1.gemfile +21 -0
- data/gemfiles/rails_7.1.gemfile.lock +131 -0
- data/gemfiles/rails_7.2.gemfile +21 -0
- data/gemfiles/rails_7.2.gemfile.lock +129 -0
- data/gemfiles/rails_8.0.gemfile +21 -0
- data/gemfiles/rails_8.0.gemfile.lock +131 -0
- data/lib/active_record/connection_adapters/mysql2_proxy_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql_proxy_adapter.rb +4 -1
- data/lib/active_record/connection_adapters/trilogy_proxy_adapter.rb +41 -0
- data/lib/active_record/tasks/trilogy_proxy_database_tasks.rb +19 -0
- data/lib/active_record_proxy_adapters/active_record_context.rb +0 -18
- data/lib/active_record_proxy_adapters/connection_handling/mysql2.rb +29 -23
- data/lib/active_record_proxy_adapters/connection_handling/postgresql.rb +27 -21
- data/lib/active_record_proxy_adapters/connection_handling/trilogy.rb +44 -0
- data/lib/active_record_proxy_adapters/connection_handling.rb +1 -7
- data/lib/active_record_proxy_adapters/hijackable.rb +3 -0
- data/lib/active_record_proxy_adapters/primary_replica_proxy.rb +8 -41
- data/lib/active_record_proxy_adapters/railtie.rb +1 -4
- data/lib/active_record_proxy_adapters/trilogy_proxy.rb +9 -0
- data/lib/active_record_proxy_adapters/version.rb +1 -1
- data/postgres_primary.dockerfile +34 -0
- data/postgres_replica.dockerfile +15 -0
- data/sig/active_record_proxy_adapters.rbs +4 -0
- metadata +28 -5
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "active_record/connection_adapters/trilogy_proxy_adapter"
|
5
|
+
rescue LoadError
|
6
|
+
# trilogy not available
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
module ActiveRecordProxyAdapters
|
11
|
+
module Trilogy
|
12
|
+
# Module to extend ActiveRecord::Base with the connection handling methods.
|
13
|
+
# Required to make adapter work in ActiveRecord versions <= 7.2.x
|
14
|
+
module ConnectionHandling
|
15
|
+
def trilogy_proxy_adapter_class
|
16
|
+
ActiveRecord::ConnectionAdapters::TrilogyProxyAdapter
|
17
|
+
end
|
18
|
+
|
19
|
+
def trilogy_proxy_connection(config) # rubocop:disable Metrics/MethodLength
|
20
|
+
configuration = config.dup
|
21
|
+
|
22
|
+
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
|
23
|
+
# matched rather than number of rows updated.
|
24
|
+
configuration[:found_rows] = true
|
25
|
+
|
26
|
+
options = [
|
27
|
+
configuration[:host],
|
28
|
+
configuration[:port],
|
29
|
+
configuration[:database],
|
30
|
+
configuration[:username],
|
31
|
+
configuration[:password],
|
32
|
+
configuration[:socket],
|
33
|
+
0
|
34
|
+
]
|
35
|
+
|
36
|
+
trilogy_proxy_adapter_class.new nil, logger, options, configuration
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ActiveSupport.on_load(:active_record) do
|
43
|
+
ActiveRecord::Base.extend(ActiveRecordProxyAdapters::Trilogy::ConnectionHandling)
|
44
|
+
end
|
@@ -2,10 +2,4 @@
|
|
2
2
|
|
3
3
|
require "active_record_proxy_adapters/connection_handling/postgresql"
|
4
4
|
require "active_record_proxy_adapters/connection_handling/mysql2"
|
5
|
-
|
6
|
-
module ActiveRecordProxyAdapters
|
7
|
-
# Module to extend ActiveRecord::Base with the connection handling methods.
|
8
|
-
# Required to make adapter work in ActiveRecord versions <= 7.2.x
|
9
|
-
module ConnectionHandling
|
10
|
-
end
|
11
|
-
end
|
5
|
+
require "active_record_proxy_adapters/connection_handling/trilogy"
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record/tasks/postgresql_proxy_database_tasks"
|
4
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
5
|
+
|
3
6
|
module ActiveRecordProxyAdapters
|
4
7
|
# Defines mixins to delegate specific methods from the proxy to the adapter.
|
5
8
|
module Hijackable
|
@@ -18,30 +18,19 @@ module ActiveRecordProxyAdapters
|
|
18
18
|
/\A\s*select.+for update\Z/i, /select.+lock in share mode\Z/i,
|
19
19
|
/\A\s*select.+(nextval|currval|lastval|get_lock|release_lock|pg_advisory_lock|pg_advisory_unlock)\(/i
|
20
20
|
].map(&:freeze).freeze
|
21
|
-
|
22
|
-
CTE_MATCHER = /\A\s*WITH\s+(?<CTE>\S+\s+AS\s+\(\s?[\s\S]*\))/i
|
23
21
|
# All queries that match these patterns should be sent to the replica database
|
24
|
-
SQL_REPLICA_MATCHERS
|
25
|
-
/\A\s*(select)\s/i,
|
26
|
-
/#{CTE_MATCHER.source}\s*select/i
|
27
|
-
].map(&:freeze).freeze
|
22
|
+
SQL_REPLICA_MATCHERS = [/\A\s*(select|with\s[\s\S]*\)\s*select)\s/i].map(&:freeze).freeze
|
28
23
|
# All queries that match these patterns should be sent to all databases
|
29
24
|
SQL_ALL_MATCHERS = [/\A\s*set\s/i].map(&:freeze).freeze
|
30
25
|
# Local sets queries should not be sent to all datbases
|
31
26
|
SQL_SKIP_ALL_MATCHERS = [/\A\s*set\s+local\s/i].map(&:freeze).freeze
|
32
27
|
# These patterns define which database statments are considered write statments, so we can shortly re-route all
|
33
28
|
# requests to the primary database so the replica has time to replicate
|
34
|
-
WRITE_STATEMENT_MATCHERS = [
|
35
|
-
|
36
|
-
/\ACOMMIT/i,
|
37
|
-
/INSERT\s[\s\S]*INTO\s[\s\S]*/i,
|
38
|
-
/UPDATE\s[\s\S]*/i,
|
39
|
-
/DELETE\s[\s\S]*FROM\s[\s\S]*/i,
|
40
|
-
/DROP\s/i
|
41
|
-
].map(&:freeze).freeze
|
29
|
+
WRITE_STATEMENT_MATCHERS = [/\ABEGIN/i, /\ACOMMIT/i, /INSERT\sINTO\s/i, /UPDATE\s/i, /DELETE\sFROM\s/i,
|
30
|
+
/DROP\s/i].map(&:freeze).freeze
|
42
31
|
|
43
32
|
# Abstract adapter methods that should be proxied.
|
44
|
-
hijack_method
|
33
|
+
hijack_method :execute, :exec_query
|
45
34
|
|
46
35
|
def self.hijacked_methods
|
47
36
|
@hijacked_methods.to_a
|
@@ -61,22 +50,6 @@ module ActiveRecordProxyAdapters
|
|
61
50
|
delegate :connection_handler, to: :connection_class
|
62
51
|
delegate :reading_role, :writing_role, to: :active_record_context
|
63
52
|
|
64
|
-
# We need to call .verify! to ensure `configure_connection` is called on the instance before attempting to use it.
|
65
|
-
# This is necessary because the connection may have been lazily initialized and is an unintended side effect from a
|
66
|
-
# change in Rails to defer connection verification: https://github.com/rails/rails/pull/44576
|
67
|
-
# verify! cannot be called before the object is initialized and because of how the proxy hooks into the connection
|
68
|
-
# instance, it has to be done lazily (hence the memoization). Ideally, we shouldn't have to worry about this at all
|
69
|
-
# But there is tight coupling between methods in ActiveRecord::ConnectionAdapters::AbstractAdapter and
|
70
|
-
# its descendants which will require significant refactoring to be decoupled.
|
71
|
-
# See https://github.com/rails/rails/issues/51780
|
72
|
-
def verified_primary_connection
|
73
|
-
@verified_primary_connection ||= begin
|
74
|
-
connected_to(role: writing_role) { primary_connection.verify! }
|
75
|
-
|
76
|
-
primary_connection
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
53
|
def replica_pool_unavailable?
|
81
54
|
!replica_pool
|
82
55
|
end
|
@@ -147,8 +120,7 @@ module ActiveRecordProxyAdapters
|
|
147
120
|
end
|
148
121
|
|
149
122
|
def connection_for(role, sql_string)
|
150
|
-
connection =
|
151
|
-
|
123
|
+
connection = primary_connection if role == writing_role || replica_pool_unavailable?
|
152
124
|
connection ||= checkout_replica_connection
|
153
125
|
|
154
126
|
result = connected_to(role:) { yield connection }
|
@@ -182,21 +154,16 @@ module ActiveRecordProxyAdapters
|
|
182
154
|
# @return [TrueClass] if there has been a write within the last {#proxy_delay} seconds
|
183
155
|
# @return [TrueClass] if sql_string matches a write statement (i.e. INSERT, UPDATE, DELETE, SELECT FOR UPDATE)
|
184
156
|
# @return [FalseClass] if sql_string matches a read statement (i.e. SELECT)
|
185
|
-
def need_primary?(sql_string)
|
186
|
-
return true
|
157
|
+
def need_primary?(sql_string)
|
158
|
+
return true if recent_write_to_primary?
|
159
|
+
|
187
160
|
return true if in_transaction?
|
188
|
-
return true if cte_for_write?(sql_string)
|
189
161
|
return true if SQL_PRIMARY_MATCHERS.any?(&match_sql?(sql_string))
|
190
162
|
return false if SQL_REPLICA_MATCHERS.any?(&match_sql?(sql_string))
|
191
163
|
|
192
164
|
true
|
193
165
|
end
|
194
166
|
|
195
|
-
def cte_for_write?(sql_string)
|
196
|
-
CTE_MATCHER.match?(sql_string) &&
|
197
|
-
WRITE_STATEMENT_MATCHERS.any?(&match_sql?(sql_string))
|
198
|
-
end
|
199
|
-
|
200
167
|
def need_all?(sql_string)
|
201
168
|
return false if SQL_SKIP_ALL_MATCHERS.any?(&match_sql?(sql_string))
|
202
169
|
|
@@ -5,10 +5,7 @@ require "active_support"
|
|
5
5
|
module ActiveRecordProxyAdapters
|
6
6
|
# Hooks into rails boot process to extend ActiveRecord with the proxy adapter.
|
7
7
|
class Railtie < Rails::Railtie
|
8
|
-
|
9
|
-
require "active_record_proxy_adapters/connection_handling"
|
10
|
-
ActiveRecord::Base.extend(ActiveRecordProxyAdapters::ConnectionHandling)
|
11
|
-
end
|
8
|
+
require "active_record_proxy_adapters/connection_handling"
|
12
9
|
|
13
10
|
config.to_prepare do
|
14
11
|
Rails.autoloaders.each do |autoloader|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_proxy_adapters/mysql2_proxy"
|
4
|
+
|
5
|
+
module ActiveRecordProxyAdapters
|
6
|
+
# Proxy to the Mysql2Proxy, allowing the use of the ActiveRecordProxyAdapters::PrimaryReplicaProxy.
|
7
|
+
class TrilogyProxy < Mysql2Proxy
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
FROM docker.io/postgres:17-alpine
|
2
|
+
|
3
|
+
ARG REPLICA_USER=replicator
|
4
|
+
ARG REPLICA_PASSWORD=replicator
|
5
|
+
ARG REPLICATION_SLOT_NAME=replication_slot
|
6
|
+
ARG INIT_SQL=00_init.sql
|
7
|
+
ARG POSTGRES_LOGGING_COLLECTOR=
|
8
|
+
ARG POSTGRES_LOG_DESTINATION=
|
9
|
+
ARG POSTGRES_LOG_STATEMENT=
|
10
|
+
ENV CONF_SAMPLE="/usr/local/share/postgresql/postgresql.conf.sample"
|
11
|
+
|
12
|
+
WORKDIR /docker-entrypoint-initdb.d
|
13
|
+
|
14
|
+
USER root
|
15
|
+
|
16
|
+
RUN touch $INIT_SQL
|
17
|
+
RUN chown -R postgres:postgres $INIT_SQL
|
18
|
+
RUN echo "CREATE USER ${REPLICA_USER} WITH REPLICATION ENCRYPTED PASSWORD '${REPLICA_PASSWORD}';" > $INIT_SQL
|
19
|
+
RUN echo "SELECT pg_create_physical_replication_slot('${REPLICATION_SLOT_NAME}');" >> $INIT_SQL
|
20
|
+
|
21
|
+
# Enable logging collector if given
|
22
|
+
RUN if [[ ! -z "${POSTGRES_LOGGING_COLLECTOR}" ]]; then sed -i "s/#\(logging_collector = \)off\(.*\)/\1${POSTGRES_LOGGING_COLLECTOR}\2/" ${CONF_SAMPLE}; fi
|
23
|
+
|
24
|
+
# Override default log destination if given
|
25
|
+
RUN if [[ ! -z "${POSTGRES_LOG_DESTINATION}" ]]; then sed -i "s/#\(log_destination = \)'stderr'\(.*\)/\1'${POSTGRES_LOG_DESTINATION}'\2/" ${CONF_SAMPLE}; fi
|
26
|
+
|
27
|
+
# Override log statement if given
|
28
|
+
RUN if [[ ! -z "${POSTGRES_LOG_STATEMENT}" ]]; then sed -i "s/#\(log_statement = \)'none'\(.*\)/\1'${POSTGRES_LOG_STATEMENT}'\2/" ${CONF_SAMPLE}; fi
|
29
|
+
|
30
|
+
WORKDIR /
|
31
|
+
|
32
|
+
USER postgres
|
33
|
+
|
34
|
+
CMD ["postgres", "-c", "wal_level=replica", "-c", "hot_standby=on", "-c", "max_wal_senders=10", "-c", "max_replication_slots=10", "-c", "hot_standby_feedback=on" ]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
FROM docker.io/postgres:17-alpine
|
2
|
+
|
3
|
+
ENV PRIMARY_DATABASE_HOST=localhost
|
4
|
+
ENV PRIMARY_DATABASE_PORT=5432
|
5
|
+
ENV PRIMARY_REPLICATION_SLOT=replication_slot
|
6
|
+
|
7
|
+
|
8
|
+
COPY docker/postgres_replica/cmd.sh cmd.sh
|
9
|
+
|
10
|
+
USER root
|
11
|
+
RUN chown -R postgres:postgres cmd.sh
|
12
|
+
USER postgres
|
13
|
+
RUN chmod u+x cmd.sh
|
14
|
+
|
15
|
+
CMD [ "./cmd.sh" ]
|
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
|
+
version: 0.4.0
|
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-02-25 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activerecord
|
@@ -57,22 +57,41 @@ email:
|
|
57
57
|
- matt.cruz@nasdaq.com
|
58
58
|
executables: []
|
59
59
|
extensions: []
|
60
|
-
extra_rdoc_files:
|
61
|
-
- README.md
|
62
|
-
- LICENSE.txt
|
60
|
+
extra_rdoc_files: []
|
63
61
|
files:
|
62
|
+
- ".rspec"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- Appraisals
|
65
|
+
- CHANGELOG.md
|
66
|
+
- CODE_OF_CONDUCT.md
|
67
|
+
- Dockerfile
|
64
68
|
- LICENSE.txt
|
65
69
|
- README.md
|
70
|
+
- db/mysql_structure.sql
|
71
|
+
- db/postgresql_structure.sql
|
72
|
+
- docker-compose.yml
|
73
|
+
- docker/postgres_replica/cmd.sh
|
74
|
+
- gemfiles/rails_7.0.gemfile
|
75
|
+
- gemfiles/rails_7.0.gemfile.lock
|
76
|
+
- gemfiles/rails_7.1.gemfile
|
77
|
+
- gemfiles/rails_7.1.gemfile.lock
|
78
|
+
- gemfiles/rails_7.2.gemfile
|
79
|
+
- gemfiles/rails_7.2.gemfile.lock
|
80
|
+
- gemfiles/rails_8.0.gemfile
|
81
|
+
- gemfiles/rails_8.0.gemfile.lock
|
66
82
|
- lib/active_record/connection_adapters/mysql2_proxy_adapter.rb
|
67
83
|
- lib/active_record/connection_adapters/postgresql_proxy_adapter.rb
|
84
|
+
- lib/active_record/connection_adapters/trilogy_proxy_adapter.rb
|
68
85
|
- lib/active_record/tasks/mysql2_proxy_database_tasks.rb
|
69
86
|
- lib/active_record/tasks/postgresql_proxy_database_tasks.rb
|
87
|
+
- lib/active_record/tasks/trilogy_proxy_database_tasks.rb
|
70
88
|
- lib/active_record_proxy_adapters.rb
|
71
89
|
- lib/active_record_proxy_adapters/active_record_context.rb
|
72
90
|
- lib/active_record_proxy_adapters/configuration.rb
|
73
91
|
- lib/active_record_proxy_adapters/connection_handling.rb
|
74
92
|
- lib/active_record_proxy_adapters/connection_handling/mysql2.rb
|
75
93
|
- lib/active_record_proxy_adapters/connection_handling/postgresql.rb
|
94
|
+
- lib/active_record_proxy_adapters/connection_handling/trilogy.rb
|
76
95
|
- lib/active_record_proxy_adapters/database_tasks.rb
|
77
96
|
- lib/active_record_proxy_adapters/hijackable.rb
|
78
97
|
- lib/active_record_proxy_adapters/log_subscriber.rb
|
@@ -80,7 +99,11 @@ files:
|
|
80
99
|
- lib/active_record_proxy_adapters/postgresql_proxy.rb
|
81
100
|
- lib/active_record_proxy_adapters/primary_replica_proxy.rb
|
82
101
|
- lib/active_record_proxy_adapters/railtie.rb
|
102
|
+
- lib/active_record_proxy_adapters/trilogy_proxy.rb
|
83
103
|
- lib/active_record_proxy_adapters/version.rb
|
104
|
+
- postgres_primary.dockerfile
|
105
|
+
- postgres_replica.dockerfile
|
106
|
+
- sig/active_record_proxy_adapters.rbs
|
84
107
|
homepage: https://github.com/Nasdaq/active_record_proxy_adapters
|
85
108
|
licenses:
|
86
109
|
- MIT
|