fix(redis): add opt-in health_check_interval for stale pooled connections (#23573)

Introduces REDIS_HEALTH_CHECK_INTERVAL and wires it through to every
Redis client created by get_redis_connection (plain, cluster and
sentinel paths, sync and async). When set, redis-py will PING any
connection idle longer than the interval on checkout, so dead sockets
are surfaced as reconnectable errors before a real command lands on
them.

Defaults to unset (empty string) so existing deployments see no
behavioural change. Operators who want the protection should set it
shorter than the Redis server `timeout` setting and any firewall/LB
idle timeout on the path to Redis.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Classic298
2026-04-12 00:17:19 +02:00
committed by GitHub
parent db7f122cb0
commit 588b81eeda
2 changed files with 27 additions and 0 deletions

View File

@@ -435,6 +435,20 @@ REDIS_SOCKET_KEEPALIVE = (
os.environ.get('REDIS_SOCKET_KEEPALIVE', 'False').lower() == 'true'
)
# How often (in seconds) redis-py should PING an idle pooled connection
# before reusing it. Opt-in: defaults to unset (empty string) so behavior
# is unchanged for existing deployments. When set, should be shorter than
# the Redis server `timeout` setting and any firewall/LB idle timeout on
# the path to Redis, so stale connections are detected before a real
# command lands on them. Set to 0 or empty to disable.
REDIS_HEALTH_CHECK_INTERVAL = os.environ.get('REDIS_HEALTH_CHECK_INTERVAL', '')
try:
REDIS_HEALTH_CHECK_INTERVAL = int(REDIS_HEALTH_CHECK_INTERVAL)
if REDIS_HEALTH_CHECK_INTERVAL <= 0:
REDIS_HEALTH_CHECK_INTERVAL = None
except ValueError:
REDIS_HEALTH_CHECK_INTERVAL = None
REDIS_RECONNECT_DELAY = os.environ.get('REDIS_RECONNECT_DELAY', '')
if REDIS_RECONNECT_DELAY == '':

View File

@@ -9,6 +9,7 @@ import redis
from open_webui.env import (
REDIS_CLUSTER,
REDIS_HEALTH_CHECK_INTERVAL,
REDIS_SOCKET_CONNECT_TIMEOUT,
REDIS_SOCKET_KEEPALIVE,
REDIS_SENTINEL_HOSTS,
@@ -202,6 +203,12 @@ def get_redis_connection(
{'socket_keepalive': True} if REDIS_SOCKET_KEEPALIVE else {}
)
health_check_kwargs = (
{'health_check_interval': REDIS_HEALTH_CHECK_INTERVAL}
if REDIS_HEALTH_CHECK_INTERVAL
else {}
)
if async_mode:
import redis.asyncio as redis
@@ -217,6 +224,7 @@ def get_redis_connection(
decode_responses=decode_responses,
socket_connect_timeout=REDIS_SOCKET_CONNECT_TIMEOUT,
**keepalive_kwargs,
**health_check_kwargs,
)
connection = SentinelRedisProxy(
sentinel,
@@ -231,6 +239,7 @@ def get_redis_connection(
decode_responses=decode_responses,
**connect_timeout_kwargs,
**keepalive_kwargs,
**health_check_kwargs,
)
elif redis_url:
connection = redis.from_url(
@@ -238,6 +247,7 @@ def get_redis_connection(
decode_responses=decode_responses,
**connect_timeout_kwargs,
**keepalive_kwargs,
**health_check_kwargs,
)
else:
import redis
@@ -253,6 +263,7 @@ def get_redis_connection(
decode_responses=decode_responses,
socket_connect_timeout=REDIS_SOCKET_CONNECT_TIMEOUT,
**keepalive_kwargs,
**health_check_kwargs,
)
connection = SentinelRedisProxy(
sentinel,
@@ -267,6 +278,7 @@ def get_redis_connection(
decode_responses=decode_responses,
**connect_timeout_kwargs,
**keepalive_kwargs,
**health_check_kwargs,
)
elif redis_url:
connection = redis.Redis.from_url(
@@ -274,6 +286,7 @@ def get_redis_connection(
decode_responses=decode_responses,
**connect_timeout_kwargs,
**keepalive_kwargs,
**health_check_kwargs,
)
_CONNECTION_CACHE[cache_key] = connection