Module: ResilientReads::LagChecker

Defined in:
lib/resilient_reads/lag_checker.rb

Class Method Summary collapse

Class Method Details

.lag_for(replica) ⇒ Object

Returns the replication lag in seconds for a replica. Supports PostgreSQL and MySQL/MariaDB. Returns 0 when the replica is fully caught up, nil on error.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/resilient_reads/lag_checker.rb', line 6

def self.lag_for(replica)
  conn = replica.connection
  adapter_name = conn.adapter_name.downcase

  if adapter_name.include?("postgresql")
    lag_for_postgresql(conn)
  elsif adapter_name.include?("mysql") || adapter_name.include?("trilogy")
    lag_for_mysql(conn)
  else
    ResilientReads.log(:debug, "Lag check not supported for adapter '#{adapter_name}'")
    nil
  end
rescue => e
  ResilientReads.log(:debug, "Lag check failed for '#{replica.name}': #{e.message}")
  nil
ensure
  replica.release_connection rescue nil
end

.lag_for_mysql(conn) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/resilient_reads/lag_checker.rb', line 47

def self.lag_for_mysql(conn)
  # Prefer SHOW REPLICA STATUS (MySQL 8.0.22+, MariaDB 10.5.1+)
  # and fall back to the deprecated SHOW SLAVE STATUS.
  result =
    begin
      conn.execute("SHOW REPLICA STATUS")
    rescue ActiveRecord::StatementInvalid
      conn.execute("SHOW SLAVE STATUS")
    end

  row = if result.respond_to?(:first)
          result.first
        elsif result.respond_to?(:to_a)
          result.to_a.first
        end

  return nil unless row

  # Seconds_Behind_Source (MySQL 8.0.22+) / Seconds_Behind_Master (legacy)
  lag = if row.is_a?(Hash)
          row["Seconds_Behind_Source"] || row["Seconds_Behind_Master"]
        elsif row.respond_to?(:[])
          row["Seconds_Behind_Source"] || row["Seconds_Behind_Master"]
        end

  lag&.to_f
rescue => e
  ResilientReads.log(:debug, "MySQL lag check failed: #{e.message}")
  nil
end

.lag_for_postgresql(conn) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/resilient_reads/lag_checker.rb', line 35

def self.lag_for_postgresql(conn)
  result = conn.execute(<<~SQL)
    SELECT CASE
      WHEN pg_last_wal_receive_lsn() IS NULL THEN NULL
      WHEN pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn() THEN 0
      ELSE EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp())::float
    END AS lag
  SQL
  lag = result.first&.fetch("lag", nil)
  lag&.to_f
end

.replication_lagObject

Convenience: check replication lag using the default reading connection.



26
27
28
29
30
31
# File 'lib/resilient_reads/lag_checker.rb', line 26

def self.replication_lag
  replica = ResilientReads.replica_pool.next_healthy
  return nil unless replica

  lag_for(replica)
end