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
# File 'lib/resilient_reads/lag_checker.rb', line 47

def self.lag_for_mysql(conn)
  result = conn.execute("SHOW SLAVE STATUS")
  # MySQL 8.0.22+ uses SHOW REPLICA STATUS
  result = conn.execute("SHOW REPLICA STATUS") if result.respond_to?(:count) && result.count == 0

  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_Master / Seconds_Behind_Source (MySQL 8.0.22+)
  lag = if row.is_a?(Hash)
          row["Seconds_Behind_Master"] || row["Seconds_Behind_Source"]
  elsif row.respond_to?(:[])
          row["Seconds_Behind_Master"] || row["Seconds_Behind_Source"]
  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