|
| 1 | +"""Shared construction for aioredis connection pools and clients. |
| 2 | +
|
| 3 | +redis-py defaults leave pooled connections vulnerable to middlebox idle |
| 4 | +timeouts (Istio sidecars, cloud load balancers, NAT): when `retry` and |
| 5 | +`retry_on_error` are both unset, `AbstractConnection.__init__` uses |
| 6 | +`Retry(NoBackoff(), 0)` — zero retries — so the first command issued on a |
| 7 | +silently-closed pooled socket surfaces as `ConnectionError` to the caller. |
| 8 | +
|
| 9 | +The helpers here turn on TCP keepalive, a pre-command PING after idle periods, |
| 10 | +and transparent retry with exponential backoff. Use them for every long-lived |
| 11 | +aioredis pool or client in this repo. |
| 12 | +""" |
| 13 | + |
| 14 | +from typing import Any, Dict |
| 15 | + |
| 16 | +import redis.asyncio as aioredis |
| 17 | +from redis.asyncio.retry import Retry |
| 18 | +from redis.backoff import ExponentialBackoff |
| 19 | +from redis.exceptions import ConnectionError as RedisConnectionError |
| 20 | +from redis.exceptions import TimeoutError as RedisTimeoutError |
| 21 | + |
| 22 | +HEALTH_CHECK_INTERVAL_SECONDS = 30 |
| 23 | +SOCKET_CONNECT_TIMEOUT_SECONDS = 5 |
| 24 | +RETRY_BACKOFF_CAP_SECONDS = 2.0 |
| 25 | +RETRY_BACKOFF_BASE_SECONDS = 0.2 |
| 26 | +RETRY_MAX_ATTEMPTS = 3 |
| 27 | + |
| 28 | + |
| 29 | +def get_aioredis_connection_kwargs() -> Dict[str, Any]: |
| 30 | + """Return kwargs for constructing a resilient aioredis pool or client. |
| 31 | +
|
| 32 | + A fresh Retry and error list are returned on every call so callers can |
| 33 | + never accidentally share mutable state across pools. |
| 34 | + """ |
| 35 | + return { |
| 36 | + "socket_keepalive": True, |
| 37 | + "socket_connect_timeout": SOCKET_CONNECT_TIMEOUT_SECONDS, |
| 38 | + "health_check_interval": HEALTH_CHECK_INTERVAL_SECONDS, |
| 39 | + "retry_on_error": [RedisConnectionError, RedisTimeoutError], |
| 40 | + "retry": Retry( |
| 41 | + ExponentialBackoff(cap=RETRY_BACKOFF_CAP_SECONDS, base=RETRY_BACKOFF_BASE_SECONDS), |
| 42 | + retries=RETRY_MAX_ATTEMPTS, |
| 43 | + ), |
| 44 | + } |
| 45 | + |
| 46 | + |
| 47 | +def build_aioredis_pool(url: str) -> aioredis.ConnectionPool: |
| 48 | + return aioredis.BlockingConnectionPool.from_url(url, **get_aioredis_connection_kwargs()) |
| 49 | + |
| 50 | + |
| 51 | +def build_aioredis_client(url: str) -> aioredis.Redis: |
| 52 | + return aioredis.from_url(url, **get_aioredis_connection_kwargs()) |
0 commit comments