Fivetrex.Retry (Fivetrex v0.2.1)
View SourceRetry utilities with exponential backoff for handling transient failures.
This module provides retry logic for Fivetran API calls that may fail due to rate limiting, temporary server errors, or network issues. It implements exponential backoff with optional jitter to prevent thundering herd problems.
Quick Start
# Retry with defaults (3 attempts, exponential backoff)
{:ok, groups} = Fivetrex.Retry.with_backoff(fn ->
Fivetrex.Groups.list(client)
end)
# Custom retry configuration
{:ok, connector} = Fivetrex.Retry.with_backoff(
fn -> Fivetrex.Connectors.get(client, connector_id) end,
max_attempts: 5,
base_delay_ms: 500,
max_delay_ms: 30_000
)How It Works
- Executes the provided function
- If successful, returns the result immediately
- If it fails with a retryable error, waits with exponential backoff
- Repeats until success or max attempts reached
Retryable Errors
By default, these error types are retried:
:rate_limited- Respectsretry_afterheader when available:server_error- 5xx errors are typically transient
Non-retryable errors (returned immediately):
:unauthorized- Invalid credentials won't become valid:not_found- Resource doesn't exist:unknown- Unexpected errors need investigation
Exponential Backoff
Delays increase exponentially: base_delay * 2^attempt
With default settings (base_delay: 1000ms):
- Attempt 1 fails → wait ~1 second
- Attempt 2 fails → wait ~2 seconds
- Attempt 3 fails → wait ~4 seconds
- (capped at max_delay)
Jitter
Optional random jitter prevents synchronized retries when multiple clients hit rate limits simultaneously:
Fivetrex.Retry.with_backoff(func, jitter: true)Examples
Basic Usage
case Fivetrex.Retry.with_backoff(fn -> Fivetrex.Groups.list(client) end) do
{:ok, %{items: groups}} ->
process_groups(groups)
{:error, error} ->
# All retries exhausted
Logger.error("Failed after retries: #{error.message}")
endWith Rate Limit Handling
# Respects Fivetran's retry-after header automatically
{:ok, _} = Fivetrex.Retry.with_backoff(fn ->
Fivetrex.Connectors.sync(client, connector_id)
end)Custom Retry Predicate
# Only retry on specific errors
Fivetrex.Retry.with_backoff(
fn -> Fivetrex.Connectors.get(client, id) end,
retry_if: fn
%Fivetrex.Error{type: :rate_limited} -> true
_ -> false
end
)Fire and Forget with Logging
Fivetrex.Retry.with_backoff(
fn -> Fivetrex.Connectors.sync(client, connector_id) end,
on_retry: fn error, attempt, delay ->
Logger.warn("Retry #{attempt}: #{error.message}, waiting #{delay}ms")
end
)
Summary
Types
Options for configuring retry behavior.
Functions
Calculates the delay before the next retry attempt.
The default retry predicate - determines which errors are retryable.
Executes a function with automatic retry and exponential backoff.
Types
@type retry_opts() :: [ max_attempts: pos_integer(), base_delay_ms: pos_integer(), max_delay_ms: pos_integer(), jitter: boolean(), retry_if: (Fivetrex.Error.t() -> boolean()), on_retry: (Fivetrex.Error.t(), pos_integer(), pos_integer() -> any()) ]
Options for configuring retry behavior.
:max_attempts- Maximum number of attempts (default: 3):base_delay_ms- Initial delay in milliseconds (default: 1000):max_delay_ms- Maximum delay cap in milliseconds (default: 30000):jitter- Add random jitter to delays (default: false):retry_if- Custom function to determine if error is retryable:on_retry- Callback function called before each retry
Functions
@spec calculate_delay( Fivetrex.Error.t(), pos_integer(), pos_integer(), pos_integer(), boolean() ) :: pos_integer()
Calculates the delay before the next retry attempt.
For rate-limited errors with a retry_after value, uses that directly.
Otherwise, uses exponential backoff: base_delay * 2^(attempt-1)
Parameters
error- The error that triggered the retryattempt- The current attempt number (1-based)base_delay_ms- Base delay in millisecondsmax_delay_ms- Maximum delay capjitter- Whether to add random jitter
Examples
iex> error = %Fivetrex.Error{type: :server_error, retry_after: nil}
iex> Fivetrex.Retry.calculate_delay(error, 1, 1000, 30000, false)
1000
iex> error = %Fivetrex.Error{type: :server_error, retry_after: nil}
iex> Fivetrex.Retry.calculate_delay(error, 3, 1000, 30000, false)
4000
iex> error = %Fivetrex.Error{type: :rate_limited, retry_after: 60}
iex> Fivetrex.Retry.calculate_delay(error, 1, 1000, 30000, false)
60000
@spec default_retry_predicate(Fivetrex.Error.t()) :: boolean()
The default retry predicate - determines which errors are retryable.
Returns true for:
:rate_limited- API rate limits are transient:server_error- 5xx errors are typically transient
Returns false for:
:unauthorized- Invalid credentials:not_found- Resource doesn't exist:unknown- Unexpected errors
Examples
iex> Fivetrex.Retry.default_retry_predicate(%Fivetrex.Error{type: :rate_limited})
true
iex> Fivetrex.Retry.default_retry_predicate(%Fivetrex.Error{type: :not_found})
false
@spec with_backoff((-> {:ok, any()} | {:error, Fivetrex.Error.t()}), retry_opts()) :: {:ok, any()} | {:error, Fivetrex.Error.t()}
Executes a function with automatic retry and exponential backoff.
Parameters
func- A zero-arity function that returns{:ok, result}or{:error, %Fivetrex.Error{}}opts- Optional keyword list (see module docs for options)
Returns
{:ok, result}- The successful result fromfunc{:error, %Fivetrex.Error{}}- The last error after all retries exhausted
Examples
# Simple usage
{:ok, groups} = Fivetrex.Retry.with_backoff(fn ->
Fivetrex.Groups.list(client)
end)
# With options
{:ok, connector} = Fivetrex.Retry.with_backoff(
fn -> Fivetrex.Connectors.get(client, id) end,
max_attempts: 5,
jitter: true
)