Retrying¶
The Retrying subsystem in coredis provides flexible support for retrying operations (including Redis commands) when certain exceptions occur. It’s designed to make transient failures easier to handle in robust applications.
coredis clients can be instantiated with a retry policy using the retry_policy
argument which will be used whenever any redis command is called.
Retry Policies¶
There are two primary built-in retry policies:
For example to ensure any connection errors are automatically retried twice with a delay of 1 second between attempts:
import coredis
client = coredis.Redis(
retry_policy=coredis.retry.ConstantRetryPolicy(
(coredis.exceptions.ConnectionError,),
retries=2,
delay=1.0
)
)
Important
The retries parameter specifies the number of retry attempts after
the initial attempt. For example, retries=2 results in up to
3 total attempts.
You can implement your own retry policy by subclassing the abstract RetryPolicy.
As an example here’s a random retry policy:
import random
from anyio import sleep
class RandomRetryPolicy(coredis.retry.RetryPolicy):
def __init__(
self,
retryable_exceptions: tuple[type[BaseException], ...],
retries: int,
max_delay: float
) -> None:
super().__init__(retryable_exceptions=retryable_exceptions, retries=retries)
self.max_delay = max_delay
async def delay(self, attempt: int) -> None:
await sleep(random.random() * self.max_delay)
A built in CompositeRetryPolicy can be used to construct a comprehensive
retry policy that handles different classes of errors with different policies:
retry_policy = coredis.retry.CompositeRetryPolicy(
coredis.retry.ConstantRetryPolicy(
(coredis.exceptions.RedisError,), retries=2, delay=1
),
coredis.retry.ExponentialBackoffRetryPolicy(
(coredis.exceptions.ConnectionError,), retries=5, initial_delay=0.1
), # etc...
)
Explicitly retry a request¶
There may be scenarios where a global retry policy that applies to every command issued
through the client is not desired. Responses from any redis command can also be chained
with the retry() method.
For example:
async with coredis.Redis() as client:
await client.lpush(
"mylist", [1,2,3]
).retry(coredis.retry.ConstantRetryPolicy(
(coredis.exceptions.RedisError,), 2, 1)
)
The above example can be made more interesting by considering the scenario where you are trying to push to a key which might not be a list:
async with coredis.Redis() as client:
await client.lpush("mylist", [1,2,3]).retry(
coredis.retry.ConstantRetryPolicy((coredis.exceptions.WrongTypeError,), 1, 1),
failure_hook=lambda err: client.delete(["mylist"])
)
You can achieve the same functionality as the retry() chain call
by using the call_with_retries() method directly:
async with coredis.Redis() as client:
policy = coredis.retry.ConstantRetryPolicy((coredis.exceptions.WrongTypeError,), 1, 1)
await policy.call_with_retries(
lambda: client.lpush("mylist", [1, 2, 3]),
failure_hook=lambda err: client.delete(["mylist"]),
)
Retry decorator¶
The retryable() decorator can be used to apply a retry
policy to any coroutine:
@coredis.retry.retryable(
coredis.retry.ConstantRetryPolicy(
(coredis.exceptions.ConnectionError,), retries=2, delay=1
)
)
async def push_values(client):
await client.lpush("mylist", [1, 2, 3])