active_record_proxy_adapters 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5312effcf68a3305eb5e7804440112433f5b6837d6b71e7d122145eac4741619
4
- data.tar.gz: 50586657daa02e76dfffebbe1faf8df5093198471faf847cec97cd16634c0c17
3
+ metadata.gz: efb046fb2816644ce048912c4c2666ae00f90fd728800f867cbe79bab99a59f3
4
+ data.tar.gz: 3d193415b9063dcfb5633c8e1493c20742f8cfbf06bc0baa7efccb291c727b49
5
5
  SHA512:
6
- metadata.gz: 707b80a3618328639126d13f3f76bb8123105019b3e30c75f15c66a66a88d49aa5de4301fe70b25ff5356ef37e54ebff896f4cab0160cadbb79d08ae9cf0dc4e
7
- data.tar.gz: 6b84984e33cf87abe9a12f5553bccffc32e2a9563a0c859c488cdae4be4c3a019ffce01242648620c67bb85a16dc61d982f0be8ecba83943f01c718e067743dc
6
+ metadata.gz: 8faf6efe63b55a9f00bbd4664f862abd3f0dd729f22b4e59306515a5dcd0fefbd42077cfaafc39f33f75d23eedd11a861e9284ba38c15868c69f72956d3acbbb
7
+ data.tar.gz: 91717a29aca4d6c41c71436527adb72cac844a4d19a2707eb5111d4f5f8b42b3c017990bdcc77972b48a7fa32082b02f74328a4c86b4f87cebc776b1d1641a17
data/CHANGELOG.md CHANGED
@@ -1,26 +1,37 @@
1
1
  ## [Unreleased]
2
2
 
3
+ - Handle PendingMigrationConnection introduced by Rails 7.2 and backported to Rails 7.1
4
+ - Stick to same connection throughout request span
5
+
6
+ ## [0.2.1] - 2025-01-02
7
+
8
+ - Fix replica connection pool getter when specific connection name is not found https://github.com/Nasdaq/active_record_proxy_adapters/commit/847e150dd21c5bc619745ee1d9d8fcaa9b8f2eea
9
+
3
10
  ## [0.2.0] - 2024-12-24
4
11
 
5
- - Add custom log subscriber to tag queries based on the adapter being used (68b8c1f4191388eb957bf12e0f84289da667e940)
12
+ - Add custom log subscriber to tag queries based on the adapter being used https://github.com/Nasdaq/active_record_proxy_adapters/commit/68b8c1f4191388eb957bf12e0f84289da667e940
13
+
14
+ ## [0.1.4] - 2025-01-02
15
+
16
+ - Fix replica connection pool getter when specific connection name is not found https://github.com/Nasdaq/active_record_proxy_adapters/commit/88b32a282b54d420e652f638656dbcf063ac8796
6
17
 
7
18
  ## [0.1.3] - 2024-12-24
8
19
 
9
- - Fix replica connection pool getter when database configurations have multiple replicas (ea5a33997da45ac073f166b3fbd2d12426053cd6)
10
- - Retrieve replica pool without checking out a connection (6470ef58e851082ae1f7a860ecdb5b451ef903c8)
20
+ - Fix replica connection pool getter when database configurations have multiple replicas https://github.com/Nasdaq/active_record_proxy_adapters/commit/ea5a33997da45ac073f166b3fbd2d12426053cd6
21
+ - Retrieve replica pool without checking out a connection https://github.com/Nasdaq/active_record_proxy_adapters/commit/6470ef58e851082ae1f7a860ecdb5b451ef903c8
11
22
 
12
23
  ## [0.1.2] - 2024-12-16
13
24
 
14
- - Fix CTE regex matcher (4b1d10bfd952fb1f5b102de8cc1a5bd05d25f5e9)
25
+ - Fix CTE regex matcher https://github.com/Nasdaq/active_record_proxy_adapters/commit/4b1d10bfd952fb1f5b102de8cc1a5bd05d25f5e9
15
26
 
16
27
  ## [0.1.1] - 2024-11-27
17
28
 
18
- - Enable RubyGems MFA (2a71b1f4354fb966cc0aa68231ca5837814e07ee)
29
+ - Enable RubyGems MFA https://github.com/Nasdaq/active_record_proxy_adapters/commit/2a71b1f4354fb966cc0aa68231ca5837814e07ee
19
30
 
20
31
  ## [0.1.0] - 2024-11-19
21
32
 
22
- - Add PostgreSQLProxyAdapter (2b3bb9f7359139519b32af3018ceb07fed8c6b33)
33
+ - Add PostgreSQLProxyAdapter https://github.com/Nasdaq/active_record_proxy_adapters/commit/2b3bb9f7359139519b32af3018ceb07fed8c6b33
23
34
 
24
35
  ## [0.1.0.rc2] - 2024-10-28
25
36
 
26
- - Add PostgreSQLProxyAdapter (2b3bb9f7359139519b32af3018ceb07fed8c6b33)
37
+ - Add PostgreSQLProxyAdapter https://github.com/Nasdaq/active_record_proxy_adapters/commit/2b3bb9f7359139519b32af3018ceb07fed8c6b33
data/Dockerfile CHANGED
@@ -8,7 +8,7 @@ RUN apk --update add \
8
8
  build-base \
9
9
  git \
10
10
  postgresql-dev \
11
- postgresql-client
11
+ postgresql17-client
12
12
  RUN gem install bundler -v 2.5.13
13
13
 
14
14
  COPY . /app
data/README.md CHANGED
@@ -1,7 +1,24 @@
1
1
  # ActiveRecordProxyAdapters
2
2
 
3
+ [![Run Test Suite](https://github.com/Nasdaq/active_record_proxy_adapters/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/Nasdaq/active_record_proxy_adapters/actions/workflows/test.yml)
4
+
3
5
  A set of ActiveRecord adapters that leverage Rails native multiple database setup to allow automatic connection switching from _one_ primary pool to _one_ replica pool at the database statement level.
4
6
 
7
+ ## Why do I need this?
8
+
9
+ Maybe you don't. Rails already provides, since version 6.0, a [Rack middleware](https://guides.rubyonrails.org/active_record_multiple_databases.html#activating-automatic-role-switching) that switches between primary and replica automatically based on the HTTP request (`GET` and `HEAD` requests go the primary, everything else goes to the replica).
10
+
11
+ The caveat is: you are not allowed do any writes in any `GET` or `HEAD` requests (including controller callbacks).
12
+ Which means, for example, your `devise` callbacks that save user metadata will now crash.
13
+ So will your `ahoy-matey` callbacks.
14
+
15
+ You will then start wrapping those callbacks in `ApplicationRecord.connected_to(role :reading) {}` blocks as a workaround and, many months later, you have dozens of those (we had nearly 40 when we decided to build this gem).
16
+
17
+ By the way, that middleware only works at HTTP request layer (well, duh! it's a Rack middleware).
18
+ So not good for background jobs, cron jobs or anything that happens outside the scope of an HTTP request. And, if your application needs a replica at this point, for sure you would benefit from automatic connection switching in background jobs too, wouldn't you?
19
+
20
+ This gem is heavily inspired by [Makara](https://github.com/instacart/makara), a fantastic gem built by the Instacart folks, which is [no longer maintained](https://github.com/instacart/makara/issues/393), but we took a slightly different, slimmer approach. We don't support load balancing replicas, and that is by design. We believe that should be done outside the scope of the application (using tools like `Pgpool-II`, `pgcat` or RDS Proxy).
21
+
5
22
  ## Installation
6
23
 
7
24
  Install the gem and add to the application's Gemfile by executing:
@@ -31,6 +48,15 @@ development:
31
48
  # your replica credentials here
32
49
  ```
33
50
 
51
+ ```ruby
52
+ # app/models/application_record.rb
53
+ class ApplicationRecord < ActiveRecord::Base
54
+ self.abstract_class = true
55
+
56
+ connects_to database: { writing: :primary, reading: :primary_replica }
57
+ end
58
+ ```
59
+
34
60
  ### Off Rails
35
61
 
36
62
  ```ruby
@@ -62,7 +88,7 @@ class ApplicationRecord << ActiveRecord::Base
62
88
  end
63
89
  ```
64
90
 
65
- ### Configuration
91
+ ## Configuration
66
92
 
67
93
  The gem comes preconfigured out of the box. However, if default configuration does not suit your needs, you can modify them by using a `.configure` block:
68
94
 
@@ -77,7 +103,40 @@ ActiveRecordProxyAdapters.configure do |config|
77
103
  end
78
104
  ```
79
105
 
80
- ### How it works
106
+ ## Logging
107
+
108
+ ```ruby
109
+ # config/initializers/active_record_proxy_adapters.rb
110
+ require "active_record_proxy_adapters/log_subscriber"
111
+
112
+ ActiveRecordProxyAdapters.configure do |config|
113
+ config.log_subscriber_primary_prefix = "My primary tag" # defaults to "#{adapter_name} Primary", i.e "PostgreSQL Primary"
114
+ config.log_subscriber_replica_prefix = "My replica tag" # defaults to "#{adapter_name} Replica", i.e "PostgreSQL Replica"
115
+ end
116
+
117
+ # You may want to remove duplicate logs
118
+ ActiveRecord::LogSubscriber.detach_from :active_record
119
+ ```
120
+
121
+ ### Example:
122
+
123
+ ```ruby
124
+ irb(main):001> User.count ; User.create(name: 'John Doe', email: 'john.doe@example.com') ; 3.times { User.count ; sleep(1) }
125
+ ```
126
+ yields
127
+
128
+ ```
129
+ D, [2024-12-24T17:18:49.151235 #328] DEBUG -- : [My replica tag] User Count (0.5ms) SELECT COUNT(*) FROM "users"
130
+ D, [2024-12-24T17:18:49.156633 #328] DEBUG -- : [My primary tag] TRANSACTION (0.1ms) BEGIN
131
+ D, [2024-12-24T17:18:49.157323 #328] DEBUG -- : [My primary tag] User Create (0.4ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "John Doe"], ["email", "john.doe@example.com"], ["created_at", "2024-12-24 17:18:49.156063"], ["updated_at", "2024-12-24 17:18:49.156063"]]
132
+ D, [2024-12-24T17:18:49.158305 #328] DEBUG -- : [My primary tag] TRANSACTION (0.7ms) COMMIT
133
+ D, [2024-12-24T17:18:49.159079 #328] DEBUG -- : [My primary tag] User Count (0.3ms) SELECT COUNT(*) FROM "users"
134
+ D, [2024-12-24T17:18:50.166105 #328] DEBUG -- : [My primary tag] User Count (1.9ms) SELECT COUNT(*) FROM "users"
135
+ D, [2024-12-24T17:18:51.169911 #328] DEBUG -- : [My replica tag] User Count (0.9ms) SELECT COUNT(*) FROM "users"
136
+ => 3
137
+ ```
138
+
139
+ ## How it works
81
140
 
82
141
  The proxy will analyze each SQL string, using pattern matching, to decide the appropriate connection for it (i.e. if it should go to the primary or replica).
83
142
 
@@ -89,10 +148,175 @@ The proxy will analyze each SQL string, using pattern matching, to decide the ap
89
148
  - All sequence methods (e.g `nextval`) go the primary
90
149
  - Everything else goes to the replica
91
150
 
92
- #### TL;DR
151
+ ### TL;DR
93
152
 
94
153
  All `SELECT` queries go to the _replica_, everything else goes to _primary_.
95
154
 
155
+ ## Stickiness context
156
+
157
+ Similar to Rails' built-in [automatic role switching](https://guides.rubyonrails.org/active_record_multiple_databases.html#activating-automatic-role-switching) Rack middleware, the proxy guarantes read-your-own-writes consistency by keeping a contextual timestamp for each Adapter Instance (a.k.a what you get when you call `Model.connection`).
158
+
159
+ Until `config.proxy_delay` time has been reached, all subsequent read requests _only for that connection_ will be rerouted to the primary. Once that has been reached, all following read requests will go the replica.
160
+
161
+ Although the gem comes configured out of the box with `config.proxy_delay = 2.seconds`, it is your responsibility to find the proper number to use here, as that is very particular to each application and may be affected by many different factors (i.e. hardware, workload, availability, fault-tolerance, etc.). **Do not use this gem** if you don't have any replication delay metrics avaiable in your production APM. And make sure you have the proper alerts setup in case there's a spike in replication delay.
162
+
163
+ One strategy you can use to quickly disable the proxy is set your adapter using an environment variable:
164
+
165
+ ```yaml
166
+ # config/database.yml
167
+ production:
168
+ primary:
169
+ adapter: <%= ENV.fetch("PRIMARY_DATABASE_ADAPTER", "postgresql") %>
170
+ primary_replica:
171
+ adapter: postgresql
172
+ replica: true
173
+ ```
174
+ Then set `PRIMARY_DATABASE_ADAPTER=postgresql_proxy` to enable the proxy.
175
+ That way you can redeploy your application disabling the proxy completely, without any code change.
176
+
177
+ ### Sticking to the primary database manually
178
+
179
+ The proxy respects ActiveRecord's `#connected_to_stack` and will use it if present.
180
+ You can use that to force connection to the primary or replica and bypass the proxy entirely.
181
+
182
+ ```ruby
183
+ User.create(name: 'John Doe', email: 'john.doe@example.com')
184
+ last_user = User.last # This would normally go to the primary to adhere to read-your-own-writes consistency
185
+ last_user = ApplicationRecord.connected_to(role: :reading) { User.last } # but I can override it with this block
186
+ ```
187
+
188
+ This is useful when picking up a background job that could be impacted by replication delay.
189
+
190
+ ```ruby
191
+ # app/models/application_record.rb
192
+ class ApplicationRecord < ActiveRecord::Base
193
+ self.abstract_class = true
194
+
195
+ connects_to database: { writing: :primary, reading: :primary_replica }
196
+ end
197
+
198
+ # app/models/user.rb
199
+ class User < ApplicationRecord
200
+ validates :name, :email, presence: true
201
+
202
+ after_commit :say_hello, on: :create
203
+
204
+ private
205
+
206
+ def say_hello
207
+ SayHelloJob.perform_later(id) # new row may not be replicated yet
208
+ end
209
+ end
210
+
211
+ # app/jobs/say_hello_job.rb
212
+ class SayHelloJob < ApplicationJob
213
+ def perform(user_id)
214
+ # so we manually reroute it to the primary
215
+ user = ApplicationRecord.connected_to(role: :writing) { User.find(user_id) }
216
+
217
+ UserMailer.welcome(user).deliver_now
218
+ end
219
+ end
220
+ ```
221
+
222
+ ### Thread safety
223
+
224
+ Since Rails already leases exactly one connection per thread from the pool and the adapter operates on that premise, it is safe to use it in multi-threaded servers such as Puma.
225
+
226
+ 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.
227
+
228
+ 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)).
229
+ Rails 7.0 works as expected.
230
+
231
+ Multi-threaded queries example:
232
+ ```ruby
233
+ # app/models/application_record.rb
234
+ class ApplicationRecord < ActiveRecord::Base
235
+ self.abstract_class = true
236
+
237
+ connects_to database: { writing: :primary, reading: :primary_replica }
238
+ end
239
+
240
+ # app/models/portal.rb
241
+ class Portal < ApplicationRecord
242
+ end
243
+
244
+ # in rails console -e test
245
+ ActiveRecord::Base.logger.formatter = proc do |_severity, _time, _progname, msg|
246
+ "[#{Time.current.iso8601} THREAD #{Thread.current[:name]}] #{msg}\n"
247
+ end
248
+
249
+ def read_your_own_writes
250
+ proc do
251
+ Portal.all.count # should go to the replica
252
+ FactoryBot.create(:portal)
253
+
254
+ 5.times do
255
+ Portal.all.count # first one goes the primary, last 3 should go to the replica
256
+ sleep(3)
257
+ end
258
+ end
259
+ end
260
+
261
+ def use_replica
262
+ proc do
263
+ 5.times do
264
+ Portal.all.count # should always go the replica
265
+ sleep(1.5)
266
+ end
267
+ end
268
+ end
269
+
270
+ def executor
271
+ Rails.application.executor
272
+ end
273
+
274
+ def test_multithread_queries
275
+ ActiveRecordProxyAdapters.configure do |config|
276
+ config.proxy_delay = 2.seconds
277
+ config.checkout_timeout = 2.seconds
278
+ end
279
+
280
+ t1 = Thread.new do
281
+ Thread.current[:name] = "USE REPLICA"
282
+ executor.wrap { ActiveRecord::Base.uncached { use_replica.call } }
283
+ end
284
+
285
+ t2 = Thread.new do
286
+ Thread.current[:name] = "READ YOUR OWN WRITES"
287
+ executor.wrap { ActiveRecord::Base.uncached { read_your_own_writes.call } }
288
+ end
289
+
290
+ [t1, t2].each(&:join)
291
+ end
292
+ ```
293
+
294
+ Yields:
295
+ ```bash
296
+ irb(main):051:0> test_multithread_queries
297
+ [2024-12-24T13:52:40-05:00 THREAD USE REPLICA] [PostgreSQL Replica] Portal Count (1.4ms) SELECT COUNT(*) FROM "portals"
298
+ [2024-12-24T13:52:40-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQL Replica] Portal Count (0.4ms) SELECT COUNT(*) FROM "portals"
299
+ [2024-12-24T13:52:40-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQLProxy Primary] TRANSACTION (0.5ms) BEGIN
300
+ [2024-12-24T13:52:40-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQLProxy Primary] Portal Exists? (1.2ms) SELECT 1 AS one FROM "portals" WHERE "portals"."id" IS NOT NULL AND "portals"."slug" = $1 LIMIT $2 [["slug", "portal-e065948fbbee73d3b2c576b48c2b37e021115158edc6a92390d613640460e1d4"], ["LIMIT", 1]]
301
+ [2024-12-24T13:52:40-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQLProxy Primary] Portal Exists? (0.4ms) SELECT 1 AS one FROM "portals" WHERE "portals"."name" = $1 LIMIT $2 [["name", "Portal-e065948fbbee73d3b2c576b48c2b37e021115158edc6a92390d613640460e1d4"], ["LIMIT", 1]]
302
+ [2024-12-24T13:52:40-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQLProxy Primary] Portal Create (0.8ms) INSERT INTO "portals" ("name", "slug", "logo", "created_at", "updated_at", "visible") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["name", "Portal-e065948fbbee73d3b2c576b48c2b37e021115158edc6a92390d613640460e1d4"], ["slug", "portal-e065948fbbee73d3b2c576b48c2b37e021115158edc6a92390d613640460e1d4"], ["logo", nil], ["created_at", "2024-12-24 18:52:40.428383"], ["updated_at", "2024-12-24 18:52:40.428383"], ["visible", true]]
303
+ [2024-12-24T13:52:40-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQLProxy Primary] TRANSACTION (0.7ms) COMMIT
304
+ [2024-12-24T13:52:40-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQLProxy Primary] Portal Count (0.6ms) SELECT COUNT(*) FROM "portals"
305
+ [2024-12-24T13:52:41-05:00 THREAD USE REPLICA] [PostgreSQL Replica] Portal Count (4.4ms) SELECT COUNT(*) FROM "portals"
306
+ [2024-12-24T13:52:43-05:00 THREAD USE REPLICA] [PostgreSQL Replica] Portal Count (3.3ms) SELECT COUNT(*) FROM "portals"
307
+ [2024-12-24T13:52:43-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQL Replica] Portal Count (2.8ms) SELECT COUNT(*) FROM "portals"
308
+ [2024-12-24T13:52:44-05:00 THREAD USE REPLICA] [PostgreSQL Replica] Portal Count (18.0ms) SELECT COUNT(*) FROM "portals"
309
+ [2024-12-24T13:52:46-05:00 THREAD USE REPLICA] [PostgreSQL Replica] Portal Count (0.9ms) SELECT COUNT(*) FROM "portals"
310
+ [2024-12-24T13:52:46-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQL Replica] Portal Count (2.3ms) SELECT COUNT(*) FROM "portals"
311
+ [2024-12-24T13:52:49-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQL Replica] Portal Count (7.2ms) SELECT COUNT(*) FROM "portals"
312
+ [2024-12-24T13:52:52-05:00 THREAD READ YOUR OWN WRITES] [PostgreSQL Replica] Portal Count (3.7ms) SELECT COUNT(*) FROM "portals"
313
+ => [#<Thread:0x00007fffdd6c9348 (irb):38 dead>, #<Thread:0x00007fffdd6c9230 (irb):43 dead>]
314
+ ```
315
+
316
+ ## Building your own proxy
317
+
318
+ TODO: update instructions
319
+
96
320
  ## Development
97
321
 
98
322
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,6 +1,7 @@
1
1
  SET statement_timeout = 0;
2
2
  SET lock_timeout = 0;
3
3
  SET idle_in_transaction_session_timeout = 0;
4
+ SET transaction_timeout = 0;
4
5
  SET client_encoding = 'UTF8';
5
6
  SET standard_conforming_strings = on;
6
7
  SELECT pg_catalog.set_config('search_path', '', false);
@@ -9,16 +10,18 @@ SET xmloption = content;
9
10
  SET client_min_messages = warning;
10
11
  SET row_security = off;
11
12
 
12
- --
13
- -- Name: public; Type: SCHEMA; Schema: -; Owner: -
14
- --
13
+ SET default_tablespace = '';
15
14
 
16
- -- *not* creating schema, since initdb creates it
15
+ SET default_table_access_method = heap;
17
16
 
17
+ --
18
+ -- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
19
+ --
18
20
 
19
- SET default_tablespace = '';
21
+ CREATE TABLE public.schema_migrations (
22
+ version character varying(255) NOT NULL
23
+ );
20
24
 
21
- SET default_table_access_method = heap;
22
25
 
23
26
  --
24
27
  -- Name: users; Type: TABLE; Schema: public; Owner: -
@@ -68,6 +71,13 @@ ALTER TABLE ONLY public.users
68
71
  ADD CONSTRAINT users_pkey PRIMARY KEY (id);
69
72
 
70
73
 
74
+ --
75
+ -- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -
76
+ --
77
+
78
+ CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version);
79
+
80
+
71
81
  --
72
82
  -- PostgreSQL database dump complete
73
83
  --
@@ -62,7 +62,7 @@ module ActiveRecordProxyAdapters
62
62
 
63
63
  attr_reader :primary_connection, :last_write_at, :active_record_context
64
64
 
65
- delegate :connection_handler, :connected_to_stack, to: :connection_class
65
+ delegate :connection_handler, to: :connection_class
66
66
  delegate :reading_role, :writing_role, to: :active_record_context
67
67
 
68
68
  def replica_pool_unavailable?
@@ -70,9 +70,18 @@ module ActiveRecordProxyAdapters
70
70
  end
71
71
 
72
72
  def replica_pool
73
+ # use default handler if the connection pool for specific class is not found
74
+ specific_replica_pool || default_replica_pool
75
+ end
76
+
77
+ def specific_replica_pool
73
78
  connection_handler.retrieve_connection_pool(connection_class.name, role: reading_role)
74
79
  end
75
80
 
81
+ def default_replica_pool
82
+ connection_handler.retrieve_connection_pool(ActiveRecord::Base.name, role: reading_role)
83
+ end
84
+
76
85
  def connection_class
77
86
  active_record_context.connection_class_for(primary_connection)
78
87
  end
@@ -111,11 +120,25 @@ module ActiveRecordProxyAdapters
111
120
  [reading_role, writing_role].include?(role) ? role : nil
112
121
  end
113
122
 
123
+ def connected_to_stack
124
+ return connection_class.connected_to_stack if connection_class.respond_to?(:connected_to_stack)
125
+
126
+ # handle Rails 7.2+ pending migrations Connection
127
+ return [{ role: writing_role }] if pending_migration_connection?
128
+
129
+ []
130
+ end
131
+
132
+ def pending_migration_connection?
133
+ active_record_context.active_record_v7_1_or_greater? &&
134
+ connection_class.name == "ActiveRecord::PendingMigrationConnection"
135
+ end
136
+
114
137
  def connection_for(role, sql_string)
115
138
  connection = primary_connection if role == writing_role || replica_pool_unavailable?
116
139
  connection ||= checkout_replica_connection
117
140
 
118
- result = yield(connection)
141
+ result = connected_to(role:) { yield connection }
119
142
 
120
143
  update_primary_latest_write_timestamp if !replica_connection?(connection) && write_statement?(sql_string)
121
144
 
@@ -124,6 +147,12 @@ module ActiveRecordProxyAdapters
124
147
  replica_connection?(connection) && replica_pool.checkin(connection)
125
148
  end
126
149
 
150
+ def connected_to(role:, &block)
151
+ return block.call unless connection_class.respond_to?(:connected_to)
152
+
153
+ connection_class.connected_to(role:, &block)
154
+ end
155
+
127
156
  def replica_connection?(connection)
128
157
  connection && connection != primary_connection
129
158
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordProxyAdapters
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  end
@@ -1,4 +1,4 @@
1
- FROM docker.io/postgres:14-alpine
1
+ FROM docker.io/postgres:17-alpine
2
2
 
3
3
  ARG REPLICA_USER=replicator
4
4
  ARG REPLICA_PASSWORD=replicator
@@ -1,4 +1,4 @@
1
- FROM docker.io/postgres:14-alpine
1
+ FROM docker.io/postgres:17-alpine
2
2
 
3
3
  ENV PRIMARY_DATABASE_HOST=localhost
4
4
  ENV PRIMARY_DATABASE_PORT=5432
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_proxy_adapters
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Cruz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-24 00:00:00.000000000 Z
11
+ date: 2025-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord