Class: Pgtk::Retry

Inherits:
Object
  • Object
show all
Defined in:
lib/pgtk/retry.rb

Overview

Retry is a decorator for Pool that automatically retries failed SELECT queries. It provides fault tolerance for transient database errors by retrying read-only operations a configurable number of times before giving up.

This class implements the same interface as Pool but adds retry logic specifically for SELECT queries. Non-SELECT queries are executed without retry to maintain data integrity and avoid unintended side effects from duplicate writes.

Basic usage:

# Create and configure a regular pool
pool = Pgtk::Pool.new(wire, max: 4)
pool.start!

# Wrap the pool in a retry decorator with 3 attempts
retry_pool = Pgtk::Retry.new(pool, attempts: 3)

# SELECT queries are automatically retried on failure
begin
  retry_pool.exec('SELECT * FROM users WHERE id = $1', [42])
rescue PG::Error => e
  puts "Query failed after 3 attempts: #{e.message}"
end

# Non-SELECT queries are not retried
retry_pool.exec('UPDATE users SET active = true WHERE id = $1', [42])

# Transactions pass through without retry logic
retry_pool.transaction do |t|
  t.exec('SELECT * FROM accounts')  # No retry within transaction
  t.exec('UPDATE accounts SET balance = balance + 100')
end

# Combining with other decorators
impatient = Pgtk::Impatient.new(retry_pool, 5)
spy = Pgtk::Spy.new(impatient) do |sql, duration|
  puts "Query: #{sql} (#{duration}s)"
end
Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2026 Yegor Bugayenko

License

MIT

Defined Under Namespace

Classes: Exhausted

Instance Method Summary collapse

Constructor Details

#initialize(pool, attempts: 3) ⇒ Retry

Constructor.

Parameters:

  • pool (Pgtk::Pool)

    The pool to decorate

  • attempts (Integer) (defaults to: 3)

    Number of attempts to make (default: 3)

Raises:

  • (ArgumentError)


61
62
63
64
65
# File 'lib/pgtk/retry.rb', line 61

def initialize(pool, attempts: 3)
  raise(ArgumentError, "Attempts must be at least 2, while #{attempts} provided") if attempts < 2
  @pool = pool
  @attempts = attempts
end

Instance Method Details

#dumpObject

Convert internal state into text.



80
81
82
83
84
85
86
# File 'lib/pgtk/retry.rb', line 80

def dump
  [
    @pool.dump,
    '',
    "Pgtk::Retry (attempts=#{@attempts})"
  ].join("\n")
end

#exec(sql) ⇒ Array

Execute a SQL query with automatic retry for SELECT queries only. Non-SELECT queries fail on the first error, since a failure may occur after the server received the query but before the acknowledgement reached the client, and retrying a non-idempotent write could duplicate it.

Parameters:

  • sql (String)

    The SQL query with params inside (possibly)

Returns:

  • (Array)

    Result rows



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/pgtk/retry.rb', line 96

def exec(sql, *)
  query = sql.is_a?(Array) ? sql.join(' ') : sql
  attempt = 0
  begin
    @pool.exec(sql, *)
  rescue StandardError, Pgtk::Impatient::TooSlow => e
    raise(e) unless query.strip.upcase.start_with?('SELECT')
    attempt += 1
    raise(Exhausted, "Retry gave up after #{@attempts} attempts: #{e.message}") if attempt >= @attempts
    retry
  end
end

#start!Object

Start a new connection pool with the given arguments.



68
69
70
# File 'lib/pgtk/retry.rb', line 68

def start!
  @pool.start!
end

#transaction {|Object| ... } ⇒ Object

Run a transaction without retry logic.

Yields:

  • (Object)

    Yields the transaction object

Returns:

  • (Object)

    Result of the block



113
114
115
# File 'lib/pgtk/retry.rb', line 113

def transaction(&)
  @pool.transaction(&)
end

#versionString

Get the version of PostgreSQL server.

Returns:

  • (String)

    Version of PostgreSQL server



75
76
77
# File 'lib/pgtk/retry.rb', line 75

def version
  @pool.version
end