Class: ClickhouseRuby::RetryHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/clickhouse_ruby/retry_handler.rb

Overview

Implements retry logic with exponential backoff and jitter

This class handles transient failures in ClickHouse connections by retrying with an exponential backoff strategy and optional jitter.

Examples:

Basic usage

handler = RetryHandler.new(max_attempts: 3)
result = handler.with_retry do
  client.execute('SELECT * FROM users')
end

With idempotency flag

handler.with_retry(idempotent: false) do |query_id|
  client.insert('events', data, settings: { query_id: query_id })
end

Constant Summary collapse

RETRIABLE_ERRORS =

Errors that should trigger a retry

[
  ConnectionError,
  ConnectionTimeout,
  ConnectionNotEstablished,
  PoolTimeout,
].freeze
RETRIABLE_HTTP_CODES =

HTTP status codes that should trigger a retry

%w[500 502 503 504 429].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_attempts: 3, initial_backoff: 1.0, max_backoff: 120.0, multiplier: 1.6, jitter: :equal) ⇒ RetryHandler

Creates a new RetryHandler

Parameters:

  • max_attempts (Integer) (defaults to: 3)

    maximum retry attempts (default: 3)

  • initial_backoff (Float) (defaults to: 1.0)

    initial backoff in seconds (default: 1.0)

  • max_backoff (Float) (defaults to: 120.0)

    maximum backoff in seconds (default: 120.0)

  • multiplier (Float) (defaults to: 1.6)

    backoff multiplier (default: 1.6)

  • jitter (Symbol) (defaults to: :equal)

    jitter strategy (default: :equal)



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/clickhouse_ruby/retry_handler.rb', line 55

def initialize(
  max_attempts: 3,
  initial_backoff: 1.0,
  max_backoff: 120.0,
  multiplier: 1.6,
  jitter: :equal
)
  @max_attempts = max_attempts
  @initial_backoff = initial_backoff
  @max_backoff = max_backoff
  @multiplier = multiplier
  @jitter = jitter
end

Instance Attribute Details

#initial_backoffFloat (readonly)

Returns initial backoff delay in seconds.

Returns:

  • (Float)

    initial backoff delay in seconds



37
38
39
# File 'lib/clickhouse_ruby/retry_handler.rb', line 37

def initial_backoff
  @initial_backoff
end

#jitterSymbol (readonly)

Returns jitter strategy (:full, :equal, or :none).

Returns:

  • (Symbol)

    jitter strategy (:full, :equal, or :none)



46
47
48
# File 'lib/clickhouse_ruby/retry_handler.rb', line 46

def jitter
  @jitter
end

#max_attemptsInteger (readonly)

Returns maximum number of attempts.

Returns:

  • (Integer)

    maximum number of attempts



34
35
36
# File 'lib/clickhouse_ruby/retry_handler.rb', line 34

def max_attempts
  @max_attempts
end

#max_backoffFloat (readonly)

Returns maximum backoff delay in seconds.

Returns:

  • (Float)

    maximum backoff delay in seconds



40
41
42
# File 'lib/clickhouse_ruby/retry_handler.rb', line 40

def max_backoff
  @max_backoff
end

#multiplierFloat (readonly)

Returns exponential backoff multiplier.

Returns:

  • (Float)

    exponential backoff multiplier



43
44
45
# File 'lib/clickhouse_ruby/retry_handler.rb', line 43

def multiplier
  @multiplier
end

Instance Method Details

#retriable?(error) ⇒ Boolean

Checks if an error is retriable

Parameters:

  • error (Exception)

    the error to check

Returns:

  • (Boolean)

    true if the error should trigger a retry



112
113
114
115
# File 'lib/clickhouse_ruby/retry_handler.rb', line 112

def retriable?(error)
  RETRIABLE_ERRORS.any? { |klass| error.is_a?(klass) } ||
    retriable_http_error?(error)
end

#with_retry(idempotent: true, query_id: nil) {|query_id| ... } ⇒ Object

Executes a block with retry logic

Yields to the block with an optional query_id. If the block raises a retriable error, retries with exponential backoff up to max_attempts. Non-retriable errors are re-raised immediately.

Examples:

Idempotent operation (SELECT)

handler.with_retry { client.execute('SELECT * FROM users') }

Non-idempotent operation (INSERT)

handler.with_retry(idempotent: false) do |qid|
  client.insert('events', data, settings: { query_id: qid })
end

Parameters:

  • idempotent (Boolean) (defaults to: true)

    whether the operation is idempotent (default: true)

  • query_id (String, nil) (defaults to: nil)

    query ID for deduplication (optional)

Yield Parameters:

  • query_id (String)

    generated or provided query_id

Returns:

  • (Object)

    return value from the block

Raises:

  • (Error)

    if all retries are exhausted or error is non-retriable



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/clickhouse_ruby/retry_handler.rb', line 88

def with_retry(idempotent: true, query_id: nil)
  attempts = 0
  generated_query_id = query_id || SecureRandom.uuid

  begin
    attempts += 1
    yield(generated_query_id)
  rescue *RETRIABLE_ERRORS => e
    handle_retry(attempts, e, idempotent)
    retry
  rescue QueryError => e
    # Check if HTTP code is retriable (server error or rate limit)
    if retriable_http_error?(e)
      handle_retry(attempts, e, idempotent)
      retry
    end
    raise
  end
end