Module: ResilientReads::AdapterPatch

Defined in:
lib/resilient_reads/adapter_patch.rb

Overview

Prepended onto the database adapter (e.g. PostgreSQLAdapter, Mysql2Adapter, TrilogyAdapter). Intercepts raw_execute and routes SELECT queries to a healthy replica when inside a distribute_reads block. Writes always pass through to the primary connection.

Uses *args/**kwargs to stay compatible across Rails versions:

Rails 7.1/7.2: raw_execute(sql, name, async:, allow_retry:, materialize_transactions:)
Rails 8.0+:    raw_execute(sql, name, binds, prepare:, async:, allow_retry:, materialize_transactions:, batch:)

Constant Summary collapse

SKIP_NAMES =

Query names that should never be routed to a replica. These are schema introspection or internal bookkeeping queries that run during model loading and connection setup.

Set.new(%w[SCHEMA EXPLAIN]).freeze
LOCKING_CLAUSE_PATTERN =

SQL clauses that acquire locks and must execute on the primary, even though the statement starts with SELECT.

/\b(FOR\s+(UPDATE|NO\s+KEY\s+UPDATE|SHARE|KEY\s+SHARE)|LOCK\s+IN\s+SHARE\s+MODE)\b/i

Instance Method Summary collapse

Instance Method Details

#raw_execute(sql, *args, **kwargs) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/resilient_reads/adapter_patch.rb', line 20

def raw_execute(sql, *args, **kwargs)
  ctx = Thread.current[:resilient_reads_context]
  name = args.first

  if ctx &&
     ctx[:distributing] &&
     !ctx[:on_replica] &&
     !ctx[:routing] &&
     !ctx[:stick_to_primary] &&
     !skip_replica_routing?(sql, name) &&
     open_transactions.zero?

    execute_on_replica(sql, ctx, *args, **kwargs)
  else
    if ctx && ctx[:distributing]
      if write_query?(sql)
        # Sticky writes: after any write inside a distribute_reads
        # block, all subsequent reads stay on primary for the rest of
        # the block.  This prevents stale-read → conflicting-write
        # chains that cause MySQL/InnoDB deadlocks, especially with
        # transactionless writes like update_column that don't bump
        # open_transactions.
        if ResilientReads.config.sticky_writes
          ctx[:stick_to_primary] = true
        end
        ResilientReads.log_query("primary", sql, name, reason: "write query")
      elsif ctx[:stick_to_primary]
        ResilientReads.log_query("primary", sql, name, reason: "sticky write")
      end
    end
    super(sql, *args, **kwargs)
  end
end