opensearch-project / opensearch-project/opensearch-ruby

Add exponential backoff to retry logic in perform_request

Open
#324 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Ruby
Stars
114
Forks
60
PR merge metrics
No merged PRs in 30d

Description

Problem

OpenSearch::Transport::Transport::Base#perform_request supports retry_on_failure and retry_on_status, but retries are immediate with no delay between attempts. During write saturation (e.g., bulk indexing overwhelming the write thread pool), instant retries compound the load on the cluster instead of giving it time to recover.

In our case, this caused a cascading failure: OpenSearch's write thread pool (8 threads) became saturated, clients timed out waiting for /_bulk responses, and immediate retries amplified the pressure — resulting in 69,745 HTTP 460 (client closed connection) responses in a 40-minute window.

Current behavior
# base.rb lines 305-313
rescue OpenSearch::Transport::Transport::ServerError => e
  raise e unless response && @retry_on_status.include?(response.status)
  log_warn "[#{e.class}] Attempt #{tries} to get response from #{url}"
  if tries <= (max_retries || DEFAULT_MAX_RETRIES)
    retry  # <-- immediate, no delay
  else
    ...
  end

The same pattern exists for host_unreachable_exceptions (lines 314-331).

Proposed behavior

Add optional retry_backoff and retry_backoff_factor options to the transport configuration:

client = OpenSearch::Client.new(
  retry_on_failure: 3,
  retry_on_status: [429, 503],
  retry_backoff: 1,          # base delay in seconds (default: nil = no backoff)
  retry_backoff_factor: 2    # exponential multiplier (default: 2)
)

When retry_backoff is set, sleep retry_backoff * (retry_backoff_factor ** (tries - 1)) seconds (with optional jitter) before each retry. This is consistent with how elasticsearch-ruby and faraday-retry handle backoff.

Workaround

We're currently monkey-patching perform_request via class_eval to inject sleep calls before each retry — this works but is fragile and version-coupled (pinned to opensearch-ruby 3.4.x).

Environment
  • opensearch-ruby 3.4.0
  • Ruby 3.3+

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in base.rb at OpenSearch::Transport::Transport::Base#perform_request, especially the retry branches around lines 305-331, and trace how transport configuration options are defined and passed through. Define the optional retry_backoff and retry_backoff_factor behavior for both retry paths, then verify that configured retries delay exponentially while the default remains immediate.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
api, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.