resilient_reads 0.1.4 → 0.1.6
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/resilient_reads/adapter_patch.rb +31 -10
- data/lib/resilient_reads/replica.rb +9 -1
- data/lib/resilient_reads/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1fe8df35a3f61180a19739623b0aaff7bbbe294243ad427c009582ed1c8a318c
|
|
4
|
+
data.tar.gz: fb4714db90a83396646d513a9d017f38882ed908d063becd504144376d094c5d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5e1659a0280bba649717303fc25d0f5188a7c8660dda88fc4e4857fc6a102eaba2a65259ce07f641338164f6876cb53ddd438d9cf318191acb471998cc166d8
|
|
7
|
+
data.tar.gz: 05542c3105b4eccd0092e1242524fb88dd37804d0f7757c8b9255d36aec2cdb95181a7a80a835e3ec777745def2118fd61416757c06ac13370c4a11f2be85142
|
|
@@ -11,7 +11,7 @@ module ResilientReads
|
|
|
11
11
|
# Query names that should never be routed to a replica. These are
|
|
12
12
|
# schema introspection or internal bookkeeping queries that run during
|
|
13
13
|
# model loading and connection setup.
|
|
14
|
-
SKIP_NAMES = Set.new(%w[SCHEMA EXPLAIN]).freeze
|
|
14
|
+
SKIP_NAMES = Set.new(%w[SCHEMA EXPLAIN TRANSACTION]).freeze
|
|
15
15
|
|
|
16
16
|
# SQL clauses that acquire locks and must execute on the primary,
|
|
17
17
|
# even though the statement starts with SELECT.
|
|
@@ -31,15 +31,21 @@ module ResilientReads
|
|
|
31
31
|
|
|
32
32
|
execute_on_replica(sql, ctx, *args, **kwargs)
|
|
33
33
|
else
|
|
34
|
-
if ctx && ctx[:distributing]
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if ctx && ctx[:distributing] && !ctx[:on_replica]
|
|
35
|
+
adapter_write = write_query?(sql)
|
|
36
|
+
|
|
37
|
+
if adapter_write || ResilientReads.write_query?(sql)
|
|
38
|
+
# Sticky writes: after any DML write inside a distribute_reads
|
|
37
39
|
# block, all subsequent reads stay on primary for the rest of
|
|
38
40
|
# the block. This prevents stale-read → conflicting-write
|
|
39
41
|
# chains that cause MySQL/InnoDB deadlocks, especially with
|
|
40
42
|
# transactionless writes like update_column that don't bump
|
|
41
43
|
# open_transactions.
|
|
42
|
-
|
|
44
|
+
#
|
|
45
|
+
# Only adapter-detected writes (DML/DDL) trigger sticky —
|
|
46
|
+
# transaction commands (BEGIN/COMMIT) are handled by the
|
|
47
|
+
# open_transactions guard instead.
|
|
48
|
+
if ResilientReads.config.sticky_writes && adapter_write
|
|
43
49
|
ctx[:stick_to_primary] = true
|
|
44
50
|
end
|
|
45
51
|
ResilientReads.log_query("primary", sql, name, reason: "write query")
|
|
@@ -53,12 +59,17 @@ module ResilientReads
|
|
|
53
59
|
|
|
54
60
|
private
|
|
55
61
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
62
|
+
# Queries that must stay on the primary: writes, transaction
|
|
63
|
+
# commands, schema introspection, and locking reads.
|
|
64
|
+
#
|
|
65
|
+
# We check *both* the adapter's write_query? (which knows about
|
|
66
|
+
# adapter-specific DDL/DML) and ResilientReads' own WRITE_PATTERN
|
|
67
|
+
# (which catches transaction commands like BEGIN/COMMIT/SET that
|
|
68
|
+
# some adapters classify as reads).
|
|
58
69
|
def skip_replica_routing?(sql, name)
|
|
59
|
-
return true if name.
|
|
60
|
-
return true if SKIP_NAMES.include?(name)
|
|
70
|
+
return true if name && SKIP_NAMES.include?(name)
|
|
61
71
|
return true if write_query?(sql)
|
|
72
|
+
return true if ResilientReads.write_query?(sql)
|
|
62
73
|
return true if locking_query?(sql)
|
|
63
74
|
false
|
|
64
75
|
end
|
|
@@ -87,8 +98,18 @@ module ResilientReads
|
|
|
87
98
|
end
|
|
88
99
|
|
|
89
100
|
# Optional lag check — uses cached value to avoid querying on every request.
|
|
101
|
+
# The routing guard must be set while the lag query runs so that the
|
|
102
|
+
# replica adapter's patched raw_execute doesn't re-enter
|
|
103
|
+
# execute_on_replica (the lag SQL is a SELECT executed on the replica
|
|
104
|
+
# connection, which would otherwise pass skip_replica_routing? and
|
|
105
|
+
# recurse infinitely).
|
|
90
106
|
if ResilientReads.config.max_lag
|
|
91
|
-
|
|
107
|
+
ctx[:routing] = true
|
|
108
|
+
begin
|
|
109
|
+
lag = replica.cached_lag
|
|
110
|
+
ensure
|
|
111
|
+
ctx[:routing] = false
|
|
112
|
+
end
|
|
92
113
|
if lag && lag > ResilientReads.config.max_lag
|
|
93
114
|
if ResilientReads.config.lag_failover
|
|
94
115
|
ResilientReads.log_query("primary", sql, args.first, reason: "replica '#{replica.name}' lag #{lag.round(1)}s > max #{ResilientReads.config.max_lag}s")
|
|
@@ -85,8 +85,16 @@ module ResilientReads
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
# Verify the replica is reachable. Returns true/false and updates health.
|
|
88
|
+
# Uses the raw connection to avoid ActiveRecord SQL instrumentation/logging.
|
|
88
89
|
def check_health!
|
|
89
|
-
@connection_class.connection.
|
|
90
|
+
raw = @connection_class.connection.raw_connection
|
|
91
|
+
if raw.respond_to?(:exec) # PG::Connection
|
|
92
|
+
raw.exec("SELECT 1")
|
|
93
|
+
elsif raw.respond_to?(:ping) # Mysql2::Client / Trilogy
|
|
94
|
+
raw.ping
|
|
95
|
+
else
|
|
96
|
+
@connection_class.connection.execute("SELECT 1")
|
|
97
|
+
end
|
|
90
98
|
mark_healthy!
|
|
91
99
|
true
|
|
92
100
|
rescue => e
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resilient_reads
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jamie Puckett
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-06-20 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: Distribute database reads across multiple replicas in Rails with automatic
|
|
13
13
|
load balancing, health checking, and graceful failover to primary.
|
|
@@ -59,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
60
|
version: '0'
|
|
61
61
|
requirements: []
|
|
62
|
-
rubygems_version: 3.6.
|
|
62
|
+
rubygems_version: 3.6.2
|
|
63
63
|
specification_version: 4
|
|
64
64
|
summary: Distribute database reads across multiple replicas in Rails with automatic
|
|
65
65
|
load balancing, health checking, and graceful failover to primary.
|